1
0
Fork 0

converted to MarkDown

This commit is contained in:
Frank Celler 2012-12-18 14:44:50 +01:00
parent 697d09a558
commit d4f10e99eb
4 changed files with 675 additions and 779 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ mr-*.h
.dirstamp
*.o
*.a
*~
.libev-build-64
.v8-build-64

View File

@ -0,0 +1,647 @@
Arango Actions {#UserManualActions}
===================================
@NAVIGATE_UserManualActions
@EMBEDTOC{UserManualActionsTOC}
Please note, that user Actions in ArangoDB are still preliminary and details
are subject to change.
Introduction to User Actions {#UserManualActionsIntro}
======================================================
In some ways the communication layer of the ArangoDB server behaves like a Web
server. Unlike a Web server, it normally responds to HTTP requests by delivering
JSON objects. Remember, documents in the database are just JSON objects. So,
most of the time the HTTP response will contain a JSON document from the
database as body. You can extract the documents stored in the database using
HTTP `GET`. You can store documents using HTTP `POST`.
However, there is something more. You can write small sniplets - so called
actions - to extend the database. The idea of actions is that sometimes it is
better to store parts of the business logic within AnrangoDB.
The simplest example is the age of a person. Assume you store information about
people in your database. It is an anti-pattern to store the age, because it
changes every now and then. Therefore, you normally store the birthday and let
the client decide what to do with it. However, if you have many different
clients, it might be easier to enrich the person document with the age using
actions once on the server side.
Or, for instance, if you want to apply some statistics to large data-sets and
you cannot easily express this as query. You can define a action instead of
transferring the whole data to the client and do the computation on the client.
Actions are also useful if you want to restrict and filter data according to
some complex permission system.
The ArangoDB server can deliver all kinds of information, JSON being only one
possible format. You can also generate HTML or images. However, a Web server is
normally better suited for the task as it also implements various caching
strategies, language selection, compression and so on. Having said that, there
are still situations where it might be suitable to use the ArangoDB to deliver
HTML pages - static or dynamic. An simple example is the built-in administration
interface. You can access it using any modern browser and there is no need for a
separate Apache or IIS.
The following sections will explain actions within ArangoDB and show how to
define them. The examples start with delivering static HTML pages - even if this
is not the primary use-case for actions. The later sections will then show you
how to code some pieces of your business logic and return JSON objects.
The interface is loosely modelled after the JavaScript classes for HTTP request
and responses found in node.js and the middleware/routing aspects of connect.js
and express.js.
Note that unlike node.js, ArangoDB is multi-threaded and there is no easy way to
share state between queries inside the JavaScript engine. If such state
information is required, you need to use the database itself.
A Hello World Example {#UserManualActionsHelloWorld}
====================================================
The client API or browser sends a HTTP request to the ArangoDB server and the
server returns a HTTP response to the client. A HTTP request consists of a
method, normally `GET` or `POST` when using a browser, and a request path like
`/hello/world`. For a real Web server there are a zillion of other thing to
consider, we will ignore this for the moment. The HTTP response contains a
content type, describing how to interpret the returned data, and the data
itself.
In the following example, we want to define an action in ArangoDB, so that the
server returns the HTML document
<html>
<body>
Hello World
</body>
</html>
if asked `GET /hello/world`.
The server needs to know what function to call or what document to deliver if it
receives a request. This is called routing. All the routing information of
ArangoDB is stored in a collection `_routing`. Each entry in this collections
describes how to deal with a particular request path.
For the above example, add the following document to the @{_routing} collection:
arangosh> db._routing.save({
........> url: { match: "/hello/world" },
........> content: {
........> contentType: "text/html",
........> body: "<html><body>Hello World</body></html>" }});
In order to activate the new routing, you must either restart the server or call
the internal reload function.
arangosh> require("internal").reloadRouting()
Now use the browser and access
http://localhost:8529/hello/world
You should see the `Hello World` in our browser.
Matching an URL {#UserManualActionsMatches}
===========================================
There are a lot of options for the `url` attribute. If you define different
routing for the same path, then the following simple rule is applied in order to
determine which match wins: If there are two matches, then the more specific
wins. I. e, if there is a wildcard match and an exact match, the exact match is
prefered. If there is a short and a long match, the longer match wins.
Exact Match {#UserManualActionsMatchesExact}
--------------------------------------------
If the definition is
{ url: { match: "/hello/world" } }
then the match must be exact. Only the request for `/hello/world` will match,
everything else, e. g. `/hello/world/my` or `/hello/world2`, will not match.
The following definition is a short-cut for an exact match.
{ url: "/hello/world" }
Prefix Match {#UserManualActionsMatchesPrefix}
----------------------------------------------
If the definition is
{ url: { match: "/hello/world/*" } }
then the match can be a prefix match. The requests for `/hello/world`,
`/hello/world/my`, and `/hello/world/how/are/you` will all match. However
`/hello/world2` does not match. Prefix matches within an URL part,
i. e. `/hello/world*`, are not allowed. The wildcard must occur at the end,
i. e.
/hello/*/world
is also disallowed.
If you define two routes
{ url: { match: "/hello/world/*" } }
{ url: { match: "/hello/world/emil" } }
then the second route will be used for `/hello/world/emil` because it is more
specific.
Parameterized Match {#UserManualActionsMatchesParameterized}
------------------------------------------------------------
A parameterized match is similar to a prefix match, but the parameters are also
allowed inside the URL path.
If the definition is
{ url: { match: "/hello/:name/world" } }
then the URL must have three parts, the first part being `hello` and the third
part `world`. For example, `/hello/emil/world` will match, while
`/hello/emil/meyer/world` will not.
Constraint Match {#UserManualActionsMatchesConstraint}
------------------------------------------------------
A constraint match is similar to a parameterized match, but the parameters can
carry constraints.
If the definition is
{ url: { match: "/hello/:name/world", constraint: { name: "/[a-z]+/" } }
then the URL must have three parts, the first part being `hello` and the third
part `world`. The second part must be all lowercase.
It is possible to use more then one constraint for the same URL part.
{ url: { match: "/hello/:name|:id/world",
constraint: { name: "/[a-z]+/", id: "/[0-9]+/" } }
Optional Match {#UserManualActionsMatchesOptional}
--------------------------------------------------
An optional match is similar to a parameterized match, but the last parameter is
optional.
If the definition is
{ url: { match: "/hello/:name?", constraint: { name: "/[a-z]+/" } }
then the URL `/hello` and `/hello/emil` will match.
If the definitions are
{ url: { match: "/hello/world" } }
{ url: { match: "/hello/:name", constraint: { name: "/[a-z]+/" } }
{ url: { match: "/hello/*" } }
then the URL `/hello/world` will be matched by the first route, because it is
the most specific. The URL `/hello/you` will be matched by the second route,
because it is more specific than the prefix match.
Method Restriction {#UserManualActionsMatchesMethod}
----------------------------------------------------
You can restrict the match to specific methods.
If the definition is
{ url: { match: "/hello/world", methods: [ "post", "put" ] }
then only `POST` and `PUT` requests will match.
More on Matching {#UserManualActionsMatching}
---------------------------------------------
Remember that the more specific match wins.
- A match without parameter or wildcard is more specific than a match with
parameters or wildcard.
- A match with parameter is more specific than a match with a wildcard.
- If there is more than one parameter, specificity is applied from left to
right.
Consider the following definitions
(1) { url: { match: "/hello/world" } }
(2) { url: { match: "/hello/:name", constraint: { name: "/[a-z]+/" } }
(3) { url: { match: "/:something/world" }
(4) { url: { match: "/hello/*" } }
Then
- `/hello/world` is match by (1)
- `/hello/emil` is match by (2)
- `/your/world` is match by (3)
- `/hello/you` is match by (4)
You can write the following document into the `_routing` collection
to test the above examples.
{
routes: [
{ url: { match: "/hello/world" }, content: "route 1" },
{ url: { match: "/hello/:name|:id", constraint: { name: "/[a-z]+/", id: "/[0-9]+/" } }, content: "route 2" },
{ url: { match: "/:something/world" }, content: "route 3" },
{ url: { match: "/hello/*" }, content: "route 4" },
]
}
A Hello World Example for JSON {#UserManualActionsHelloJson}
============================================================
If you change the example slightly, then a JSON object will be delivered.
arangosh> db._routing.save({
........> url: "/hello/json",
........> content: {
........> contentType: "application/json",
........> body: "{ \"hello\" : \"world\" }" }});
arangosh> require("internal").reloadRouting()
Again check with your browser
http://localhost:8529/hello/json
Depending on your browser and installed add-ons you will either see the JSON
object or a download dialog. If your browser wants to open an external
application to display the JSON object, you can change the `contentType` to
`"text/plain"` for the example. This makes it easier to check the example using
a browser. Or use `curl` to access the server.
bash> curl "http://127.0.0.1:8529/hello/json" && echo
{ "hello" : "world" }
Delivering Content {#UserManualActionsContent}
==============================================
There are a lot of different ways on how to deliver content. We have already
seen the simplest one, where static content is delivered. The fun, however,
starts when delivering dynamic content.
Static Content {#UserManualActionsContentStatic}
------------------------------------------------
You can specify a body and a content-type.
{ content: {
contentType: "text/html",
body: "<html><body>Hallo World</body></html>"
}
}
If the content type is `text/plain` then you can use the short-cut
{ content: "Hallo World" }
A Simple Action {#UserManualActionsContentAction}
=================================================
The simplest dynamic action is:
{ action: { controller: "org/arangodb/actions", do: "echoRequest" } }
It is not possible to store functions directly in the routing table, but you can
call functions defined in modules. In the above example the function can be
accessed from JavaScript as:
require("org/arangodb/actions").echoRequest
The function `echoRequest` is pre-defined. It takes the request objects and
echos it in the response.
The signature of such a function must be
function (req, res, options, next)
For example
arangosh> db._routing.save({
........> url: "/hello/echo",
........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
Reload the routing and check
http://127.0.0.1:8529/hello/echo
You should see something like
{
"request": {
"path": "/hello/echo",
"headers": {
"accept-encoding": "gzip, deflate",
"accept-language": "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3",
"connection": "keep-alive",
"content-length": "0",
"host": "localhost:8529",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0"
},
"requestType": "GET",
"parameters": { }
},
"options": { }
}
The request might contain `path`, `prefix`, `suffix`, and `urlParameters`
attributes. `path` is the complete path as supplied by the user and always
available. If a prefix was matched, then this prefix is stored in the attribute
`prefix` and the remaining URL parts are stored as an array in `suffix`. If one
or more parameters were matched, then the parameter values are stored in
`urlParameters`.
For example, if the url description is
{ url: { match: "/hello/:name/:action" } }
and you request the path `/hello/emil/jump`, then the request object
will contain the following attribute
urlParameters: { name: "emil", action: "jump" } }
Action Controller {#UserManualActionsContentController}
-------------------------------------------------------
As an alternative to the simple action, you can use controllers. A
controller is a module, defines the function `get`, `put`,
`post`, `delete`, `head`, `patch`. If a request of
the corresponding type is matched, the function will be called.
For example
arangosh> db._routing.save({
........> url: "/hello/echo",
........> action: { controller: "org/arangodb/actions/echoController" } });
Prefix Action Controller {#UserManualActionsContentPrefix}
----------------------------------------------------------
The controller is selected when the definition is read. There is a
more flexible, but slower and maybe insecure variant, the prefix
controller.
Assume that the url is a prefix match
{ url: { match: /hello/*" } }
You can use
{ action: { prefixController: "org/arangodb/actions" } }
to define a prefix controller. If the URL `/hello/echoController` is
given, then the module `org/arangodb/actions/echoController` is used.
If you use an prefix controller, you should make certain that no unwanted
actions are available under the prefix.
The definition
{ action: "org/arangodb/actions" }
is a short-cut for a prefix controller definition.
Requests and Responses {#UserManualActionsReqRes}
=================================================
The controller must define handler functions which take a request object and
fill the response object.
A very simple example is the function `echoRequest` defined in
the module `org/arangodb/actions`.
function (req, res, options, next) {
var result;
result = { request: req, options: options };
res.responseCode = exports.HTTP_OK;
res.contentType = "application/json";
res.body = JSON.stringify(result);
}
Install it as
arangosh> db._routing.save({
........> url: "/echo",
........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
Reload the routing and check
http://127.0.0.1:8529/hello/echo
You should see something like
{
"request": {
"prefix": "/hello/echo",
"suffix": [
"hello",
"echo"
],
"path": "/hello/echo",
"headers": {
"accept-encoding": "gzip, deflate",
"accept-language": "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3",
"connection": "keep-alive",
"content-length": "0",
"host": "localhost:8529",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0"
},
"requestType": "GET",
"parameters": { }
},
"options": { }
}
You may also pass options to the called function:
arangosh> db._routing.save({
........> url: "/echo",
........> action: {
........> controller: "org/arangodb/actions",
........> do: "echoRequest",
........> options: { "Hallo": "World" } } });
You should now see the options in the result.
{
"request": {
...
},
"options": {
"Hallo": "World"
}
}
Modifying Request and Response {#UserManualActionsModify}
=========================================================
As we've seen in the previous examples, actions get called with the request and
response objects (named `req` and `res` in the examples) passed as parameters to
their handler functions.
The `req` object contains the incoming HTTP request, which might or might not
have been modified by a previous action (if actions were chained).
A handler can modify the request object in place if desired. This might be
useful when writing middleware (see below) that is used to intercept incoming
requests, modify them and pass them to the actual handlers.
While modifying the request object might not be that relevant for non-middleware
actions, modifying the response object definitely is. Modifying the response
object is an action's only way to return data to the caller of the action.
We've already seen how to set the HTTP status code, the content type, and the
result body. The `res` object has the following properties for these:
- contentType: MIME type of the body as defined in the HTTP standard (e.g.
`text/html`, `text/plain`, `application/json`, ...)
- responsecode: the HTTP status code of the response as defined in the HTTP
standard. Common values for actions that succeed are `200` or `201`.
Please refer to the HTTP standard for more information.
- body: the actual response data
To set or modify arbitrary headers of the response object, the `headers`
property can be used. For example, to add a user-defined header to the response,
the following code will do:
res.headers = res.headers || { }; // headers might or might not be present
res.headers['X-Test'] = 'someValue'; // set header X-Test to "someValue"
This will set the additional HTTP header `X-Test` to value `someValue`. Other
headers can be set as well. Note that ArangoDB might change the case of the
header names to lower case when assembling the overall response that is sent to
the caller.
It is not necessary to explicitly set a `Content-Length` header for the response
as ArangoDB will calculate the content length automatically and add this header
itself. ArangoDB might also add a `Connection` header itself to handle HTTP
keep-alive.
ArangoDB also supports automatic transformation of the body data to another
format. Currently, the only supported transformations are base64-encoding and
base64-decoding. Using the transformations, an action can create a base64
encoded body and still let ArangoDB send the non-encoded version, for example:
res.body = 'VGhpcyBpcyBhIHRlc3Q=';
res.transformations = res.transformations || [ ]; // initialise
res.transformations.push('base64decode'); // will base64 decode the response body
When ArangoDB processes the response, it will base64-decode what's in `res.body`
and set the HTTP header `Content-Encoding: binary`. The opposite can be achieved
with the `base64encode` transformation: ArangoDB will then automatically
base64-encode the body and set a `Content-Encoding: base64` HTTP header.
Writing dynamic action handlers {#UserManualActionsHandlers}
============================================================
To write your own dynamic action handlers, you must put them into modules.
Modules are a means of organising action handlers and making them loadable under
specific names.
To start, we'll define a simple action handler in a module `/own/test`:
arangosh> db._modules.save({
........> path: "/own/test",
........> content: "exports.do = function(req, res, options, next) { res.body = 'test'; res.responseCode = 200; res.contentType = 'text/html'; };",
........> autoload: true });
This does nothing but register a do action handler in a module `/own/test`. The
action handler is not yet callable, but must be mapped to a route first. To map
the action to the route `/ourtest`, execute the following command:
arangosh> db._routing.save({
........> url: "/ourtest",
........> action: { controller: "/own/test" } });
In order to see the module in action, you must either restart the server or call
the internal reload function.
arangosh> require("internal").reloadRouting()
Now use the browser and access
http://localhost:8529/ourtest
You will see that the module's do function has been executed.
Advanced Usages {#UserManualActionsAdvanced}
============================================
For detailed information see the reference manual.
Redirects {#UserManualActionsAdvancedRedirects}
-----------------------------------------------
Use the following for a permanent redirect:
arangosh> db._routing.save({
........> url: "/",
........> action: {
........> controller: "org/arangodb/actions",
........> do: "redirectRequest",
........> options: {
........> permanently: true,
........> destination: "http://somewhere.else/" } } });
Routing Bundles {#UserManualActionsAdvancedBundles}
---------------------------------------------------
Instead of adding all routes for package separately, you can
specify a bundle.
{
routes: [
{ url: "/url1", content: "..." },
{ url: "/url2", content: "..." },
{ url: "/url3", content: "..." },
...
]
}
The advantage is, that you can put all your routes into one document
and use a common prefix.
{
urlPrefix: "/test",
routes: [
{ url: "/url1", content: "..." },
{ url: "/url2", content: "..." },
{ url: "/url3", content: "..." },
...
]
}
will define the URL `/test/url1`, `/test/url2`, and `/test/url3`.
Writing Middleware {#UserManualActionsAdvancedMiddleware}
---------------------------------------------------------
Assume, you want to log every request. In this case you can easily define an
action for the whole url-space `/`. This action simply logs the requests, calls
the next in line, and logs the response.
exports.logRequest = function (req, res, options, next) {
console.log("received request: %s", JSON.stringify(req));
next();
console.log("produced response: %s", JSON.stringify(res));
};
This function is available as `org/arangodb/actions/logRequest`. You need to
tell ArangoDB that it is should use a prefix match and that the shortest match
should win in this case:
arangosh> db._routing.save({
........> middleware: [
........> { url: { match: "/*" }, action: { controller: "org/arangodb/actions", do: "logRequest" } }
........> ]
........> });

View File

@ -0,0 +1,27 @@
TOC {#UserManualActionsTOC}
===========================
- @ref UserManualActions
- @ref UserManualActionsIntro
- @ref UserManualActionsHelloWorld
- @ref UserManualActionsMatches
- @ref UserManualActionsMatchesExact
- @ref UserManualActionsMatchesPrefix
- @ref UserManualActionsMatchesParameterized
- @ref UserManualActionsMatchesConstraint
- @ref UserManualActionsMatchesOptional
- @ref UserManualActionsMatchesMethod
- @ref UserManualActionsMatching
- @ref UserManualActionsHelloJson
- @ref UserManualActionsContent
- @ref UserManualActionsContentStatic
- @ref UserManualActionsContentAction
- @ref UserManualActionsContentController
- @ref UserManualActionsContentPrefix
- @ref UserManualActionsReqRes
- @ref UserManualActionsModify
- @ref UserManualActionsHandlers
- @ref UserManualActionsAdvanced
- @ref UserManualActionsAdvancedRedirects
- @ref UserManualActionsAdvancedBundles
- @ref UserManualActionsAdvancedMiddleware

View File

@ -100,785 +100,6 @@
/// JSON output format.
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- USER MANUAL ACTIONS
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @page UserManualActionsTOC
///
/// <ul>
/// <li>@ref UserManualActions
/// <ul>
/// <li>@ref UserManualActionsIntro</li>
/// <li>@ref UserManualActionsHelloWorld</li>
/// <li>@ref UserManualActionsMatches
/// <ul>
/// <li>@ref UserManualActionsMatchesExact</li>
/// <li>@ref UserManualActionsMatchesPrefix</li>
/// <li>@ref UserManualActionsMatchesParameterized</li>
/// <li>@ref UserManualActionsMatchesConstraint</li>
/// <li>@ref UserManualActionsMatchesOptional</li>
/// <li>@ref UserManualActionsMatchesMethod</li>
/// <li>@ref UserManualActionsMatching</li>
/// </ul>
/// </li>
/// <li>@ref UserManualActionsHelloJson</li>
/// <li>@ref UserManualActionsContent
/// <ul>
/// <li>@ref UserManualActionsContentStatic</li>
/// <li>@ref UserManualActionsContentAction</li>
/// <li>@ref UserManualActionsContentController</li>
/// <li>@ref UserManualActionsContentPrefix</li>
/// </ul>
/// </li>
/// <li>@ref UserManualActionsReqRes</li>
/// <li>@ref UserManualActionsModify</li>
/// <li>@ref UserManualActionsHandlers</li>
/// <li>@ref UserManualActionsAdvanced
/// <ul>
/// <li>@ref UserManualActionsAdvancedRedirects</li>
/// <li>@ref UserManualActionsAdvancedBundles</li>
/// <li>@ref UserManualActionsAdvancedMiddleware</li>
/// </ul>
/// </li>
/// </ul>
/// </li>
/// </ul>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @page UserManualActions Arango Actions
///
/// @NAVIGATE_UserManualActions
///
/// Please note, that user Actions in ArangoDB are still preliminary and details
/// are subject to change.
///
/// @EMBEDTOC{UserManualActionsTOC}
///
/// @section UserManualActionsIntro Introduction to User Actions
////////////////////////////////////////////////////////////////
///
/// In some ways the communication layer of the ArangoDB server behaves like a
/// Web server. Unlike a Web server, it normally responds to HTTP requests by
/// delivering JSON objects. Remember, documents in the database are just JSON
/// objects. So, most of the time the HTTP response will contain a JSON document
/// from the database as body. You can extract the documents stored in the
/// database using HTTP @LIT{GET}. You can store documents using HTTP
/// @LIT{POST}.
///
/// However, there is something more. You can write small sniplets - so called
/// actions - to extend the database. The idea of actions is that sometimes it
/// is better to store parts of the business logic within AnrangoDB.
///
/// The simplest example is the age of a person. Assume you store information
/// about people in your database. It is an anti-pattern to store the age,
/// because it changes every now and then. Therefore, you normally store the
/// birthday and let the client decide what to do with it. However, if you have
/// many different clients, it might be easier to enrich the person document
/// with the age using actions once on the server side.
///
/// Or, for instance, if you want to apply some statistics to large data-sets
/// and you cannot easily express this as query. You can define a action instead
/// of transferring the whole data to the client and do the computation on the
/// client.
///
/// Actions are also useful if you want to restrict and filter data according to
/// some complex permission system.
///
/// The ArangoDB server can deliver all kinds of information, JSON being only
/// one possible format. You can also generate HTML or images. However, a Web
/// server is normally better suited for the task as it also implements various
/// caching strategies, language selection, compression and so on. Having said
/// that, there are still situations where it might be suitable to use the
/// ArangoDB to deliver HTML pages - static or dynamic. An simple example is the
/// built-in administration interface. You can access it using any modern
/// browser and there is no need for a separate Apache or IIS.
///
/// The following sections will explain actions within ArangoDB and show how to
/// define them. The examples start with delivering static HTML pages - even if
/// this is not the primary use-case for actions. The later sections will then
/// show you how to code some pieces of your business logic and return JSON
/// objects.
///
/// The interface is loosely modelled after the JavaScript classes for HTTP
/// request and responses found in node.js and the middleware/routing aspects
/// of connect.js and express.js.
///
/// Note that unlike node.js, ArangoDB is multi-threaded and there is no easy
/// way to share state between queries inside the JavaScript engine. If such
/// state information is required, you need to use the database itself.
///
/// @section UserManualActionsHelloWorld A Hello World Example
//////////////////////////////////////////////////////////////
///
/// The client API or browser sends a HTTP request to the ArangoDB server and
/// the server returns a HTTP response to the client. A HTTP request consists
/// of a method, normally @LIT{GET} or @LIT{POST} when using a browser, and a
/// request path like @LIT{/hello/world}. For a real Web server there are a zillion
/// of other thing to consider, we will ignore this for the moment. The HTTP
/// response contains a content type, describing how to interpret the returned
/// data, and the data itself.
///
/// In the following example, we want to define an action in ArangoDB, so that the
/// server returns the HTML document
///
/// @code
/// <html>
/// <body>
/// Hello World
/// </body>
/// </html>
/// @endcode
///
/// if asked @LIT{GET /hello/world}.
///
/// The server needs to know what function to call or what document to deliver
/// if it receives a request. This is called routing. All the routing information
/// of ArangoDB is stored in a collection @LIT{_routing}. Each entry in this
/// collections describes how to deal with a particular request path.
///
/// For the above example, add the following document to the @{_routing}
/// collection:
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: { match: "/hello/world" },
/// ........> content: {
/// ........> contentType: "text/html",
/// ........> body: "<html><body>Hello World</body></html>" }});
/// @endcode
///
/// In order to activate the new routing, you must either restart the server
/// or call the internal reload function.
///
/// @code
/// arangosh> require("internal").reloadRouting()
/// @endcode
///
/// Now use the browser and access
///
/// @LIT{http://localhost:8529/hello/world}
///
/// You should see the @LIT{Hello World} in our browser.
///
/// @section UserManualActionsMatches Matching an URL
/////////////////////////////////////////////////////
///
/// There are a lot of options for the @LIT{url} attribute. If you define
/// different routing for the same path, then the following simple rule is
/// applied in order to determine which match wins: If there are two matches,
/// then the more specific wins. I. e, if there is a wildcard match and an exact
/// match, the exact match is prefered. If there is a short and a long match,
/// the longer match wins.
///
/// @subsection UserManualActionsMatchesExact Exact Match
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/world" } }
/// @endcode
///
/// then the match must be exact. Only the request for @LIT{/hello/world} will
/// match, everything else, e. g. @LIT{/hello/world/my} or @LIT{/hello/world2},
/// will not match.
///
/// The following definition is a short-cut for an exact match.
///
/// @code
/// { url: "/hello/world" }
/// @endcode
///
/// @subsection UserManualActionsMatchesPrefix Prefix Match
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/world/*" } }
/// @endcode
///
/// then the match can be a prefix match. The requests for @LIT{/hello/world},
/// @LIT{/hello/world/my}, and @LIT{/hello/world/how/are/you} will all
/// match. However @LIT{/hello/world2} does not match. Prefix matches within an
/// URL part, i. e. @LIT{/hello/world*}, are not allowed. The wildcard must
/// occur at the end, i. e.
///
/// @code
/// /hello/*/world
/// @endcode
///
/// is also disallowed.
///
/// If you define two routes
///
/// @code
/// { url: { match: "/hello/world/*" } }
/// { url: { match: "/hello/world/emil" } }
/// @endcode
///
/// then the second route will be used for @LIT{/hello/world/emil} because it is
/// more specific.
///
/// @subsection UserManualActionsMatchesParameterized Parameterized Match
///
/// A parameterized match is similar to a prefix match, but the parameters are
/// also allowed inside the URL path.
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/:name/world" } }
/// @endcode
///
/// then the URL must have three parts, the first part being @LIT{hello} and the
/// third part @LIT{world}. For example, @LIT{/hello/emil/world} will match,
/// while @LIT{/hello/emil/meyer/world} will not.
///
/// @subsection UserManualActionsMatchesConstraint Constraint Match
///
/// A constraint match is similar to a parameterized match, but the parameters
/// can carry constraints.
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/:name/world", constraint: { name: "/[a-z]+/" } }
/// @endcode
///
/// then the URL must have three parts, the first part being @LIT{hello} and the
/// third part @LIT{world}. The second part must be all lowercase.
///
/// It is possible to use more then one constraint for the same URL part.
///
/// @code
/// { url: { match: "/hello/:name|:id/world",
/// constraint: { name: "/[a-z]+/", id: "/[0-9]+/" } }
/// @endcode
///
/// @subsection UserManualActionsMatchesOptional Optional Match
///
/// An optional match is similar to a parameterized match, but the last
/// parameter is optional.
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/:name?", constraint: { name: "/[a-z]+/" } }
/// @endcode
///
/// then the URL @LIT{/hello} and @LIT{/hello/emil} will match.
///
/// If the definitions are
///
/// @code
/// { url: { match: "/hello/world" } }
/// { url: { match: "/hello/:name", constraint: { name: "/[a-z]+/" } }
/// { url: { match: "/hello/*" } }
/// @endcode
///
/// then the URL @LIT{/hello/world} will be matched by the first route, because it
/// is the most specific. The URL @LIT{/hello/you} will be matched by the second
/// route, because it is more specific than the prefix match.
///
/// @subsection UserManualActionsMatchesMethod Method Restriction
///
/// You can restrict the match to specific methods.
///
/// If the definition is
///
/// @code
/// { url: { match: "/hello/world", methods: [ "post", "put" ] }
/// @endcode
///
/// then only @LIT{POST} and @LIT{PUT} requests will match.
///
/// @subsection UserManualActionsMatching More on Matching
///
/// Remember that the more specific match wins.
///
/// - A match without parameter or wildcard is more specific than a match with
/// parameters or wildcard.
/// - A match with parameter is more specific than a match with a wildcard.
/// - If there is more than one parameter, specificity is applied from left to
/// right.
///
/// Consider the following definitions
///
/// @code
/// (1) { url: { match: "/hello/world" } }
/// (2) { url: { match: "/hello/:name", constraint: { name: "/[a-z]+/" } }
/// (3) { url: { match: "/:something/world" }
/// (4) { url: { match: "/hello/*" } }
/// @endcode
///
/// Then
///
/// - @LIT{/hello/world} is match by (1)
/// - @LIT{/hello/emil} is match by (2)
/// - @LIT{/your/world} is match by (3)
/// - @LIT{/hello/you} is match by (4)
///
/// You can write the following document into the @LIT{_routing} collection
/// to test the above examples.
///
/// @code
/// {
/// routes: [
/// { url: { match: "/hello/world" }, content: "route 1" },
/// { url: { match: "/hello/:name|:id", constraint: { name: "/[a-z]+/", id: "/[0-9]+/" } }, content: "route 2" },
/// { url: { match: "/:something/world" }, content: "route 3" },
/// { url: { match: "/hello/*" }, content: "route 4" },
/// ]
/// }
/// @endcode
///
/// @section UserManualActionsHelloJson A Hello World Example for JSON
//////////////////////////////////////////////////////////////////////
///
/// If you change the example slightly, then a JSON object will be delivered.
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/hello/json",
/// ........> content: {
/// ........> contentType: "application/json",
/// ........> body: "{ \"hello\" : \"world\" }" }});
/// arangosh> require("internal").reloadRouting()
/// @endcode
///
/// Again check with your browser
///
/// @LIT{http://localhost:8529/hello/json}
///
/// Depending on your browser and installed add-ons you will either see the
/// JSON object or a download dialog. If your browser wants to open an external
/// application to display the JSON object, you can change the @LIT{contentType}
/// to @LIT{"text/plain"} for the example. This makes it easier to check the
/// example using a browser. Or use @LIT{curl} to access the server.
///
/// @code
/// bash> curl "http://127.0.0.1:8529/hello/json" && echo
/// { "hello" : "world" }
/// @endcode
///
/// @section UserManualActionsContent Delivering Content
////////////////////////////////////////////////////////
///
/// There are a lot of different ways on how to deliver content. We have already
/// seen the simplest one, where static content is delivered. The fun, however,
/// starts when delivering dynamic content.
///
/// @subsection UserManualActionsContentStatic Static Content
///
/// You can specify a body and a content-type.
///
/// @code
/// { content: {
/// contentType: "text/html",
/// body: "<html><body>Hallo World</body></html>"
/// }
/// }
/// @endcode
///
/// If the content type is @LIT{text/plain} then you can use the short-cut
///
/// @code
/// { content: "Hallo World" }
/// @endcode
///
/// @subsection UserManualActionsContentAction A Simple Action
///
/// The simplest dynamic action is:
///
/// @code
/// { action: { controller: "org/arangodb/actions", do: "echoRequest" } }
/// @endcode
///
/// It is not possible to store functions directly in the routing table, but you
/// can call functions defined in modules. In the above example the function can
/// be accessed from JavaScript as:
///
/// @LIT{require("org/arangodb/actions").echoRequest}
///
/// The function @LIT{echoRequest} is pre-defined. It takes the request objects
/// and echos it in the response.
///
/// The signature of such a function must be
///
/// @code
/// function (req, res, options, next)
/// @endcode
///
/// For example
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/hello/echo",
/// ........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
/// @endcode
///
/// Reload the routing and check
///
/// @LIT{http://127.0.0.1:8529/hello/echo}
///
/// You should see something like
///
/// @code
/// {
/// "request": {
/// "path": "/hello/echo",
/// "headers": {
/// "accept-encoding": "gzip, deflate",
/// "accept-language": "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3",
/// "connection": "keep-alive",
/// "content-length": "0",
/// "host": "localhost:8529",
/// "user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0"
/// },
/// "requestType": "GET",
/// "parameters": { }
/// },
/// "options": { }
/// }
/// @endcode
///
/// The request might contain @LIT{path}, @LIT{prefix}, @LIT{suffix}, and
/// @LIT{urlParameters} attributes. @LIT{path} is the complete path as supplied
/// by the user and always available. If a prefix was matched, then this prefix
/// is stored in the attribute @LIT{prefix} and the remaining URL parts are
/// stored as an array in @LIT{suffix}. If one or more parameters were matched,
/// then the parameter values are stored in @LIT{urlParameters}.
///
/// For example, if the url description is
///
/// @code
/// { url: { match: "/hello/:name/:action" } }
/// @endcode
///
/// and you request the path @LIT{/hello/emil/jump}, then the request object
/// will contain the following attribute
///
/// @code
/// urlParameters: { name: "emil", action: "jump" } }
/// @endcode
///
/// @subsection UserManualActionsContentController Action Controller
///
/// As an alternative to the simple action, you can use controllers. A
/// controller is a module, defines the function @LIT{get}, @LIT{put},
/// @LIT{post}, @LIT{delete}, @LIT{head}, @LIT{patch}. If a request of
/// the corresponding type is matched, the function will be called.
///
/// For example
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/hello/echo",
/// ........> action: { controller: "org/arangodb/actions/echoController" } });
/// @endcode
///
/// @subsection UserManualActionsContentPrefix Prefix Action Controller
///
/// The controller is selected when the definition is read. There is a
/// more flexible, but slower and maybe insecure variant, the prefix
/// controller.
///
/// Assume that the url is a prefix match
///
/// @code
/// { url: { match: /hello/*" } }
/// @endcode
///
/// You can use
///
/// @code
/// { action: { prefixController: "org/arangodb/actions" } }
/// @endcode
///
/// to define a prefix controller. If the URL @LIT{/hello/echoController} is
/// given, then the module @LIT{org/arangodb/actions/echoController} is used.
///
/// If you use an prefix controller, you should make certain that no unwanted
/// actions are available under the prefix.
///
/// The definition
///
/// @code
/// { action: "org/arangodb/actions" }
/// @endcode
///
/// is a short-cut for a prefix controller definition.
///
/// @section UserManualActionsReqRes Requests and Responses
///////////////////////////////////////////////////////////
///
/// The controller must define handler functions which take a request object and
/// fill the response object.
///
/// A very simple example is the function @LIT{echoRequest} defined in
/// the module @LIT{org/arangodb/actions}.
///
/// @code
/// function (req, res, options, next) {
/// var result;
///
/// result = { request: req, options: options };
///
/// res.responseCode = exports.HTTP_OK;
/// res.contentType = "application/json";
/// res.body = JSON.stringify(result);
/// }
/// @endcode
///
/// Install it as
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
/// @endcode
///
/// Reload the routing and check
///
/// @LIT{http://127.0.0.1:8529/hello/echo}
///
/// You should see something like
///
/// @code
/// {
/// "request": {
/// "prefix": "/hello/echo",
/// "suffix": [
/// "hello",
/// "echo"
/// ],
/// "path": "/hello/echo",
/// "headers": {
/// "accept-encoding": "gzip, deflate",
/// "accept-language": "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3",
/// "connection": "keep-alive",
/// "content-length": "0",
/// "host": "localhost:8529",
/// "user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0"
/// },
/// "requestType": "GET",
/// "parameters": { }
/// },
/// "options": { }
/// }
/// @endcode
///
/// You may also pass options to the called function:
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: {
/// ........> controller: "org/arangodb/actions",
/// ........> do: "echoRequest",
/// ........> options: { "Hallo": "World" } } });
/// @endcode
///
/// You should now see the options in the result.
///
/// @code
/// {
/// "request": {
/// ...
/// },
/// "options": {
/// "Hallo": "World"
/// }
/// }
/// @endcode
///
/// @section UserManualActionsModify Modifying Request and Response
///
/// As we've seen in the previous examples, actions get called
/// with the request and response objects (named @LIT{req} and @LIT{res} in the
/// examples) passed as parameters to their handler functions.
///
/// The @LIT{req} object contains the incoming HTTP request, which
/// might or might not have been modified by a previous action (if actions were
/// chained).
///
/// A handler can modify the request object in place if desired. This might
/// be useful when writing middleware (see below) that is used to intercept
/// incoming requests, modify them and pass them to the actual handlers.
///
/// While modifying the request object might not be that relevant for
/// non-middleware actions, modifying the response object definitely is. Modifying
/// the response object is an action's only way to return data to the caller of
/// the action.
///
/// We've already seen how to set the HTTP status code, the content type, and the
/// result body. The @LIT{res} object has the following properties for these:
/// - contentType: MIME type of the body as defined in the HTTP standard (e.g.
/// @LIT{text/html}, @LIT{text/plain}, @LIT{application/json}, ...)
/// - responsecode: the HTTP status code of the response as defined in the HTTP
/// standard. Common values for actions that succeed are @LIT{200} or @LIT{201}.
/// Please refer to the HTTP standard for more information.
/// - body: the actual response data
///
/// To set or modify arbitrary headers of the response object, the @LIT{headers}
/// property can be used. For example, to add a user-defined header to the response,
/// the following code will do:
///
/// @code
/// res.headers = res.headers || { }; // headers might or might not be present
/// res.headers['X-Test'] = 'someValue'; // set header X-Test to "someValue"
/// @endcode
///
/// This will set the additional HTTP header @LIT{X-Test} to value @LIT{someValue}.
/// Other headers can be set as well. Note that ArangoDB might change the case
/// of the header names to lower case when assembling the overall response that
/// is sent to the caller.
///
/// It is not necessary to explicitly set a @LIT{Content-Length} header for the
/// response as ArangoDB will calculate the content length automatically and add
/// this header itself. ArangoDB might also add a @LIT{Connection} header itself
/// to handle HTTP keep-alive.
///
/// ArangoDB also supports automatic transformation of the body data to another
/// format. Currently, the only supported transformations are base64-encoding and
/// base64-decoding. Using the transformations, an action can create a base64
/// encoded body and still let ArangoDB send the non-encoded version, for example:
///
/// @code
/// res.body = 'VGhpcyBpcyBhIHRlc3Q=';
/// res.transformations = res.transformations || [ ]; // initialise
/// res.transformations.push('base64decode'); // will base64 decode the response body
/// @endcode
///
/// When ArangoDB processes the response, it will base64-decode what's in @LIT{res.body}
/// and set the HTTP header @LIT{Content-Encoding: binary}. The opposite can be
/// achieved with the @LIT{base64encode} transformation: ArangoDB will then automatically
/// base64-encode the body and set a @LIT{Content-Encoding: base64} HTTP header.
///
/// @section UserManualActionsHandlers Writing dynamic action handlers
/////////////////////////////////////////////////////////////////////
///
/// To write your own dynamic action handlers, you must put them into modules.
///
/// Modules are a means of organising action handlers and making them loadable
/// under specific names.
///
/// To start, we'll define a simple action handler in a module @LIT{/own/test}:
///
/// @code
/// arangosh> db._modules.save({
/// ........> path: "/own/test",
/// ........> content: "exports.do = function(req, res, options, next) { res.body = 'test'; res.responseCode = 200; res.contentType = 'text/html'; };",
/// ........> autoload: true });
/// @endcode
///
/// This does nothing but register a do action handler in a module @LIT{/own/test}.
/// The action handler is not yet callable, but must be mapped to a route first.
/// To map the action to the route @LIT{/ourtest}, execute the following command:
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/ourtest",
/// ........> action: { controller: "/own/test" } });
/// @endcode
///
/// In order to see the module in action, you must either restart the server
/// or call the internal reload function.
///
/// @code
/// arangosh> require("internal").reloadRouting()
/// @endcode
///
/// Now use the browser and access
///
/// @LIT{http://localhost:8529/ourtest}
///
/// You will see that the module's do function has been executed.
///
/// @section UserManualActionsAdvanced Advanced Usages
//////////////////////////////////////////////////////
///
/// For detailed information see the reference manual.
///
/// @subsection UserManualActionsAdvancedRedirects Redirects
///
/// Use the following for a permanent redirect:
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/",
/// ........> action: {
/// ........> controller: "org/arangodb/actions",
/// ........> do: "redirectRequest",
/// ........> options: {
/// ........> permanently: true,
/// ........> destination: "http://somewhere.else/" } } });
/// @endcode
///
/// @subsection UserManualActionsAdvancedBundles Routing Bundles
///
/// Instead of adding all routes for package separately, you can
/// specify a bundle.
///
/// @code
/// {
/// routes: [
/// { url: "/url1", content: "..." },
/// { url: "/url2", content: "..." },
/// { url: "/url3", content: "..." },
/// ...
/// ]
/// }
/// @endcode
///
/// The advantage is, that you can put all your routes into one document
/// and use a common prefix.
///
/// @code
/// {
/// urlPrefix: "/test",
///
/// routes: [
/// { url: "/url1", content: "..." },
/// { url: "/url2", content: "..." },
/// { url: "/url3", content: "..." },
/// ...
/// ]
/// }
/// @endcode
///
/// will define the URL @LIT{/test/url1}, @LIT{/test/url2}, and
/// @LIT{/test/url3}.
///
/// @subsection UserManualActionsAdvancedMiddleware Writing Middleware
///
/// Assume, you want to log every request. In this case you can easily define
/// an action for the whole url-space @LIT{/}. This action simply logs
/// the requests, calls the next in line, and logs the response.
///
/// @code
/// exports.logRequest = function (req, res, options, next) {
/// console.log("received request: %s", JSON.stringify(req));
/// next();
/// console.log("produced response: %s", JSON.stringify(res));
/// };
/// @endcode
///
/// This function is available as @LIT{org/arangodb/actions/logRequest}.
/// You need to tell ArangoDB that it is should use a prefix match and
/// that the shortest match should win in this case:
///
/// @code
/// arangosh> db._routing.save({
/// ........> middleware: [
/// ........> { url: { match: "/*" }, action: { controller: "org/arangodb/actions", do: "logRequest" } }
/// ........> ]
/// ........> });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------