mirror of https://gitee.com/bigwinds/arangodb
converted to MarkDown
This commit is contained in:
parent
697d09a558
commit
d4f10e99eb
|
@ -4,6 +4,7 @@ mr-*.h
|
|||
.dirstamp
|
||||
*.o
|
||||
*.a
|
||||
*~
|
||||
|
||||
.libev-build-64
|
||||
.v8-build-64
|
||||
|
|
|
@ -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" } }
|
||||
........> ]
|
||||
........> });
|
||||
|
|
@ -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
|
|
@ -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
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue