1
0
Fork 0

actions and documentation updates

This commit is contained in:
Jan Steemann 2012-10-24 09:31:55 +02:00
parent f37af419f2
commit 61806473d0
3 changed files with 98 additions and 89 deletions

View File

@ -394,7 +394,7 @@
////////////////////////////////////////////////////////////////
///
/// In some ways the communication layer of the ArangoDB server behaves like a
/// Web server. Unlike a Web server, it normally responses to HTTP requests by
/// 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
@ -432,7 +432,7 @@
/// 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
/// 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
@ -447,14 +447,14 @@
//////////////////////////////////////////////////////////////
///
/// 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 requests consists
/// the server returns a HTTP response to the client. A HTTP requests consist
/// 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 action in ArangoDB, so that the
/// In the following example, we want to define an action in ArangoDB, so that the
/// server returns the HTML document
///
/// @code
@ -572,7 +572,7 @@
/// @subsection UserManualActionsMatchesConstraint Constraint Match
///
/// A constraint match is similar to a parameterized match, but the parameters
/// carries a constraint.
/// can carry constraints.
///
/// If the definition is
///
@ -749,7 +749,7 @@
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/hello/echo",
/// ........> action: "org/arangodb/actions/echoRequest" });
/// ........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
/// @endcode
///
/// Reload the routing and check
@ -870,7 +870,7 @@
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: "org/arangodb/actions/echoRequest" });
/// ........> action: { controller: "org/arangodb/actions", do: "echoRequest" } });
/// @endcode
///
/// Reload the routing and check
@ -903,29 +903,14 @@
/// }
/// @endcode
///
/// Note that
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: "org/arangodb/actions/echoRequest" });
/// @endcode
///
/// is a short-cut for
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: { do: "org/arangodb/actions/echoRequest" } });
/// @endcode
///
/// The latter form allows you to pass options to the called function:
/// You may also pass options to the called function:
///
/// @code
/// arangosh> db._routing.save({
/// ........> url: "/echo",
/// ........> action: {
/// ........> do: "org/arangodb/actions/echoRequest",
/// ........> controller: "org/arangodb/actions",
/// ........> do: "echoRequest",
/// ........> options: { "Hallo": "World" } } });
/// @endcode
///
@ -934,11 +919,10 @@
/// @code
/// {
/// "request": {
/// "prefix": "/hello/echo-long",
/// ...
/// },
/// "options": {
/// "option": "my option1"
/// "Hallo": "World"
/// }
/// }
/// @endcode
@ -956,7 +940,8 @@
/// arangosh> db._routing.save({
/// ........> url: "/",
/// ........> action: {
/// ........> do: "org/arangodb/actions/redirectRequest",
/// ........> controller: "org/arangodb/actions",
/// ........> do: "redirectRequest",
/// ........> options: {
/// ........> permanently: true,
/// ........> destination: "http://somewhere.else/" } } });
@ -1011,14 +996,14 @@
/// };
/// @endcode
///
/// This functions is available as @LIT{org/arangodb/actions/logRequest}.
/// 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: "org/arangodb/actions/logRequest" }
/// ........> { url: { match: "/*" }, action: { controller: "org/arangodb/actions", do: "logRequest" } }
/// ........> ]
/// ........> });
/// @endcode

View File

@ -65,6 +65,38 @@ var RoutingCache;
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief function that's returned for non-implemented actions
////////////////////////////////////////////////////////////////////////////////
function notImplementedFunction (triggerRoute, message) {
message += "\nThis error is triggered by the following route " + JSON.stringify(triggerRoute);
console.error(message);
return function (req, res, options, next) {
res.responseCode = exports.HTTP_NOT_IMPLEMENTED;
res.contentType = "text/plain";
res.body = message;
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief function that's returned for actions that produce an error
////////////////////////////////////////////////////////////////////////////////
function errorFunction (triggerRoute, message) {
message += "\nThis error is triggered by the following route " + JSON.stringify(triggerRoute);
console.error(message);
return function (req, res, options, next) {
res.responseCode = exports.HTTP_SERVER_ERROR;
res.contentType = "text/plain";
res.body = message;
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief splits an URL into parts
////////////////////////////////////////////////////////////////////////////////
@ -181,14 +213,22 @@ function lookupCallbackStatic (content) {
/// @brief looks up a callback for an action
////////////////////////////////////////////////////////////////////////////////
function lookupCallbackAction (action) {
function lookupCallbackAction (route, action) {
var path;
var name;
var func;
var module;
var httpMethods = {
'get': exports.GET,
'head': exports.HEAD,
'put': exports.PUT,
'post': exports.POST,
'delete': exports.DELETE,
'patch': exports.PATCH
};
if (typeof action === 'string') {
return lookupCallbackAction({ prefixController: action });
return lookupCallbackAction(route, { prefixController: action });
}
if (action.hasOwnProperty('do')) {
@ -203,17 +243,15 @@ function lookupCallbackAction (action) {
func = module[name];
}
else {
console.error("cannot find action named '%s' in module '%s'", name, path.join("/"));
func = notImplementedFunction(route, "Could not find action named '" + name + "' in module '" + path.join("/") + "'");
}
}
catch (err) {
console.error("cannot find action named '%s' in module '%s': %s",
name, path.join("/"), String(err));
return null;
func = errorFunction(route, "An error occurred while loading action named '" + name + "' in module '" + path.join("/") + "': " + String(err));
}
if (func === null || typeof func !== 'function') {
return null;
func = errorFunction(route, "Invalid definition for the action named '" + name + "' in module '" + path.join("/") + "'");
}
return {
@ -229,32 +267,25 @@ function lookupCallbackAction (action) {
return {
controller: function (req, res, options, next) {
if (req.requestType === exports.GET && module.hasOwnProperty('get')) {
return module['get'](req, res, options, next);
}
// enum all HTTP methods
for (var m in httpMethods) {
if (! httpMethods.hasOwnProperty(m)) {
continue;
}
if (req.requestType === exports.HEAD && module.hasOwnProperty('head')) {
return module['head'](req, res, options, next);
}
if (req.requestType == httpMethods[m] && module.hasOwnProperty(m)) {
func = module[m] ||
errorFunction(route, "Invalid definition for " + m + " action in action controller module '" + action.controller + "'");
if (req.requestType === exports.PUT && module.hasOwnProperty('put')) {
return module['put'](req, res, options, next);
}
if (req.requestType === exports.POST && module.hasOwnProperty('post')) {
return module['post'](req, res, options, next);
}
if (req.requestType === exports.DELETE && module.hasOwnProperty('delete')) {
return module['delete'](req, res, options, next);
}
if (req.requestType === exports.PATCH && module.hasOwnProperty('patch')) {
return module['patch'](req, res, options, next);
return func(req, res, options, next);
}
}
if (module.hasOwnProperty('do')) {
return module['do'](req, res, options, next);
func = module['do'] ||
errorFunction(route, "Invalid definition for do action in action controller module '" + action.controller + "'");
return func(req, res, options, next);
}
next();
@ -264,9 +295,7 @@ function lookupCallbackAction (action) {
};
}
catch (err) {
console.error("cannot load action controller module '%s': %s",
action.controller, String(err));
return null;
return errorFunction(route, "cannot load/execute action controller module '" + action.controller + ": " + String(err))(req, res, options, next);
}
}
@ -286,37 +315,32 @@ function lookupCallbackAction (action) {
}
}
catch (err) {
console.error("cannot load prefix controller: %s", String(err));
next();
return;
return errorFunction(route, "cannot load prefix controller: " + String(err))(req, res, options, next);
}
if (req.requestType === exports.GET && module.hasOwnProperty('get')) {
return module['get'](req, res, options, next);
}
try {
// enum all HTTP methods
for (var m in httpMethods) {
if (! httpMethods.hasOwnProperty(m)) {
continue;
}
if (req.requestType === exports.HEAD && module.hasOwnProperty('head')) {
return module['head'](req, res, options, next);
}
if (req.requestType == httpMethods[m] && module.hasOwnProperty(m)) {
func = module[m] ||
errorFunction(route, "Invalid definition for " + m + " action in prefix controller '" + action.prefixController + "'");
if (req.requestType === exports.PUT && module.hasOwnProperty('put')) {
return module['put'](req, res, options, next);
return func(req, res, options, next);
}
}
if (module.hasOwnProperty('do')) {
func = module['do'] ||
errorFunction(route, "Invalid definition for do action in prefix controller '" + action.prefixController + "'");
return func(req, res, options, next);
}
}
if (req.requestType === exports.POST && module.hasOwnProperty('post')) {
return module['post'](req, res, options, next);
}
if (req.requestType === exports.DELETE && module.hasOwnProperty('delete')) {
return module['delete'](req, res, options, next);
}
if (req.requestType === exports.PATCH && module.hasOwnProperty('patch')) {
return module['patch'](req, res, options, next);
}
if (module.hasOwnProperty('do')) {
return module['do'](req, res, options, next);
catch (err) {
return errorFunction(route, "Cannot load/execute prefix controller '" + action.prefixController + "': " + String(err))(req, res, options, next);
}
next();
@ -340,7 +364,7 @@ function lookupCallback (route) {
result = lookupCallbackStatic(route.content);
}
else if (route.hasOwnProperty('action')) {
result = lookupCallbackAction(route.action);
result = lookupCallbackAction(route, route.action);
}
return result;

View File

@ -36,7 +36,7 @@ exports.head = function (req, res, next, options) {
exports.do = function (req, res, next, options) {
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
res.body = JSON.stringify(req);
res.body = JSON.stringify( { "request" : req, "options" : options });
}
// -----------------------------------------------------------------------------