Difference between revisions of "RESTXQ"
Line 132: | Line 132: | ||
</pre> | </pre> | ||
− | If | + | If a content-type is specified in the request, the content is converted to the following XQuery type: |
{| class="wikitable" width="100%" | {| class="wikitable" width="100%" | ||
|- valign="top" | |- valign="top" | ||
! Content-Type | ! Content-Type | ||
− | ! | + | ! XQuery type |
|- | |- | ||
| {{Code|application/json}}, {{Code|application/jsonml+json}} | | {{Code|application/json}}, {{Code|application/jsonml+json}} | ||
− | | | + | | {{Code|document-node()}} (conversion is described in the [[JSON Module]]) |
|- | |- | ||
| {{Code|text/html}} | | {{Code|text/html}} | ||
− | | | + | | {{Code|document-node()}} (conversion is described in the [[HTML Module]]) |
|- | |- | ||
| {{Code|text/comma-separated-values}} | | {{Code|text/comma-separated-values}} | ||
− | | | + | | {{Code|document-node()}} |
|- | |- | ||
| {{Code|text/xml}}, {{Code|application/xml}} | | {{Code|text/xml}}, {{Code|application/xml}} | ||
− | | | + | | {{Code|document-node()}} |
|- | |- | ||
| {{Code|text/*}} | | {{Code|text/*}} | ||
| {{Code|xs:string}} | | {{Code|xs:string}} | ||
+ | |- | ||
+ | | ''others'' | ||
+ | | {{Code|xs:base64Binary}} | ||
|- | |- | ||
| {{Code|multipart/*}} | | {{Code|multipart/*}} | ||
Line 162: | Line 165: | ||
{{Mark|Introduced with Version 7.7:}} | {{Mark|Introduced with Version 7.7:}} | ||
− | Some first support for {{Code|multipart}} content-types has been added: | + | Some first support for {{Code|multipart}} content-types has been added: the parts of a multipart message are represented as a sequence, |
− | and | + | and each part is converted to an XQuery item as described in the last paragraph. An example: |
<pre class="brush:xquery"> | <pre class="brush:xquery"> | ||
Line 176: | Line 179: | ||
</pre> | </pre> | ||
− | <b>Note:</b> | + | <b>Note:</b> Support for multipart types is still experimental, and it may change in a future version BaseX. Your feedback is welcome. |
==Parameters== | ==Parameters== |
Revision as of 23:09, 3 August 2013
This page presents one of the Web Application services. It describes how to use the RESTXQ API of BaseX.
RESTXQ, introduced by Adam Retter, is a new API that facilitates the use of XQuery as a Server Side processing language for the Web. RESTXQ has been inspired by Java’s JAX-RS API: it defines a pre-defined set of XQuery 3.0 annotations for mapping HTTP requests to XQuery functions, which in turn generate and return HTTP responses.
Note that various details of the specification may be subject to change due to the early state of the API. Next, with Version 7.7, the RESTXQ prefix has been changed from restxq
to rest
.
Usage
Since Version 7.7, the RESTXQ service is by default available at http://localhost:8984/
.
All RESTXQ annotations are assigned to the http://exquery.org/ns/restxq
namespace, which is statically bound to the restxq
prefix (and since Version 7.7: rest
). A Resource Function is an XQuery function that has been marked up with RESTXQ annotations. When an HTTP request comes in, a resource function will be invoked that matches the constraints indicated by its annotations.
Whenever a RESTXQ URL is requested, the RESTXQPATH module directory and its sub-directories will be parsed for library modules (detected by the extension .xqm
) and functions with RESTXQ annotations. Since Version 7.7, also main modules (detected by .xq
) will be parsed. All modules will be cached and parsed again when their timestamp changes.
A simple RESTXQ module is shown below. It is part of a clean installation and available at http://localhost:8984/ .
(: simplified version of the function found in webapp/restxq.xqm :) module namespace page = 'http://basex.org/examples/web-page'; declare %rest:path("hello/{$world}") %rest:GET %rest:header-param("User-Agent", "{$agent}") function page:hello($world as xs:string, $agent as xs:string*) { <response> <title>Hello { $world }!</title> <info>You requested this page with { $agent }.</info> </response> };
If the URI http://localhost:8984/hello/World is accessed, the result will be similar to:
<response> <title>Hello World!</title> <time>The current time is: 18:42:02.306+02:00</time> </response>
The RESTXQ module contains yet another function:
declare %rest:path("/form") %rest:POST %rest:form-param("message","{$message}", "(no message)") %rest:header-param("User-Agent", "{$agent}") function page:hello-postman( $message as xs:string, $agent as xs:string*) as element(response) { <response type='form'> <message>{ $message }</message> <user-agent>{ $agent }</user-agent> </response> };
If you post something (e.g. using curl or the embedded form at http://localhost:8984/)...
curl -i -X POST --data "content='CONTENT'" http://localhost:8984/form
...you will receive something similar to the following result:
HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 Content-Length: 107 Server: Jetty(8.1.11.v20130520)
<response type="form"> <message>(no message)</message> <user-agent>curl/7.31.0</user-agent> </response>
Requests
This section shows how annotations are used to handle and process HTTP requests.
Constraints
Constraints restrict the HTTP requests that a resource function may process.
Paths
A resource function must have a single Path Annotation with a single string as argument. The function will be called if a URL matches the path segments and templates of the argument. Path templates contain variables in curly brackets, and map the corresponding segments of the request path to the arguments of the resource function.
The following example contains a path annotation with three segments and two templates. One of the function arguments is further specified with a data type, which means that the value for $variable
will be cast to an xs:integer
before being bound:
declare %rest:path("/a/path/{$with}/some/{$variable}") function page:test($with, $variable as xs:integer) { ... };
Content Negotiation
Two following annotations can be used to restrict functions to specific content types:
- HTTP Content Types: a function will only be invoked if the HTTP
Content-Type
header of the request matches one of the given mime types. Example:
%rest:consumes("application/xml", "text/xml")
- HTTP Accept: a function will only be invoked if the HTTP
Accept
header of the request matches one of the defined mime types. Example:
%rest:produces("application/atom+xml")
By default, both mime types are */*
. Note that this annotation will not affect the content-type of the HTTP response. Instead, you will need to add a %output:media-type
annotation.
HTTP Methods
The HTTP method annotations are equivalent to all HTTP request methods except for TRACE and CONNECT. Zero or more methods may be used on a function; if none is specified, the function will be invoked for each method.
The following function will be called if GET or POST is used as request method:
declare %rest:GET %rest:POST %rest:path("/post") function page:post() { "This was a GET or POST request" };
The POST and PUT annotations may optionally take a string literal in order to map the HTTP request body to a function argument. Once again, the target variable must be embraced by curly brackets:
declare %rest:PUT("{$body}") %rest:path("/put") function page:put($body) { "Request body: " || $body };
If a content-type is specified in the request, the content is converted to the following XQuery type:
Content-Type | XQuery type |
---|---|
application/json , application/jsonml+json
|
document-node() (conversion is described in the JSON Module)
|
text/html
|
document-node() (conversion is described in the HTML Module)
|
text/comma-separated-values
|
document-node()
|
text/xml , application/xml
|
document-node()
|
text/*
|
xs:string
|
others | xs:base64Binary
|
multipart/*
|
sequence (see next paragraph) |
Multipart Types
Some first support for multipart
content-types has been added: the parts of a multipart message are represented as a sequence,
and each part is converted to an XQuery item as described in the last paragraph. An example:
declare %rest:path("/multipart") %rest:consumes("multipart/mixed") %rest:POST("{$data}") function page:multipart($data) { "Number of items: " || count($data) };
Note: Support for multipart types is still experimental, and it may change in a future version BaseX. Your feedback is welcome.
Parameters
The following annotations can be used to bind request values to function arguments. Values will implicitly be cast to the target type.
Query Parameters
The value of the first parameter, if found in the query string, will be assigned to the variable specified as second parameter. If no value is specified in the HTTP request, all additional parameters, if available, will be bound to the variable:
%rest:query-param("parameter", "{$value}", "default") %rest:query-param("answer", "{$answer}", 42, 43, 44) %rest:query-param("search", "{$search-param}")
HTML Form Fields
Form parameters are specified the same way as query parameters. Their values are extracted from GET or POST requests.
%rest:form-param("parameter", "{$value}", "default")
File Uploads
Files can be uploaded to the server by using the content type multipart/form-data
(the HTML5 multiple
attribute enables the upload of multiple files):
<form name="myForm" action="/upload" method="POST" enctype="multipart/form-data"> <input type="file" name="files[]" multiple="multiple" /> </form>
The file contents are placed in a map, with the filename serving as key. The following example shows how uploaded files can be stored in a temporary directory:
declare %rest:POST %rest:path("/upload") %rest:form-param("files", "{$files}") function page:upload($files) { for $name in map:keys($files) let $content := $files($name) let $path := file:temp-dir() || $name return ( file:write-binary($path, $content), <file name="{ $name }" size="{ file:size($path) }"/> ) };
HTTP Headers
Header parameters are specified the same way as query parameters:
%rest:header-param("User-Agent", "{$user-agent}") %rest:header-param("Referer", "{$referer}", "none")
Cookies
Cookie parameters are specified the same way as query parameters:
%rest:cookie-param("username", "{$user}") %rest:cookie-param("authentication", "{$auth}", "no_auth")
Responses
By default, a successful request is answered with the HTTP status code 200
(OK), and is followed by the given content; an erroneous request leads to an error code and an optional error message (e.g. 404
for “resource not found”).
Custom Responses
Custom responses can be built from within XQuery by returning an rest:response
element, an http:response
child node that matches the syntax of the EXPath HTTP Client Module specification, and more optional child nodes that will be serialized as usual. A function that reacts on an unknown resource may look as follows:
declare %rest:path("") function page:error404() { <rest:response> <http:response status="404" message="I was not found."> <http:header name="Content-Language" value="en"/> <http:header name="Content-Type" value="text/html; charset=utf-8"/> </http:response> </rest:response> };
Forwards and Redirects
The two XML elements rest:forward
and rest:redirect
can be used in the context of Web Applications, precisely in the context of RESTXQ. These nodes allow e.g. multiple XQuery Updates in a row by redirecting to the RESTXQ path of updating functions. Both wrap a URL to a RESTXQ path. The wrapped URL should be properly encoded via fn:encode-for-uri()
.
Note that, currently, these elements are not part of RESTXQ specification.
rest:forward
Usage: wrap the location as follows
<rest:forward>{ $location }</rest:forward>
This results in a server-side forwarding, which as well reduces traffic among client and server. A forwarding of this kind will not change the URL seen from the client's perspective.
As an example, returning
<rest:forward>/hello/universe</rest:forward>
would internally forward to http://localhost:8984/hello/universe
rest:redirect
<rest:redirect>{ $location }</rest:redirect>
…is basically an abbreviation for…
<rest:response> <http:response status="302" message="Temporary Redirect"> <http:header name="location" value="{ $location }"/> </http:response> </rest:response>
The client decides whether to follow this redirection. Browsers usually will, tools like curl won’t unless -L
is specified.
Output
Similar to the REST interface, result serialization can be modified via XQuery 3.0 serialization parameters; in RESTXQ, serialization parameters may be specified in the query prolog, via annotations, or within REST response element. Global parameters are overwritten by more local parameters.
Query Prolog
In main modules, serialization parameters may be specified in the query prolog. These parameters will then apply to all functions in a module. In the following example, the content type of the response is overwritten with the media-type
parameter:
declare option output:media-type 'text/plain'; declare %rest:path("version1") function page:version1() { 'Keep it simple, stupid' };
Annotations
The serialization can also be parameterized via annotations:
declare %output:media-type("text/plain") %rest:path("version2") function page:version2() { 'Still somewhat simple.' };
Response Element
The following example demonstrates how serialization parameters can be dynamically set within a query:
declare %rest:path("version3") function page:version3() { <rest:response> <output:serialization-parameters> <output:media-type value='text/plain'/> </output:serialization-parameters> </rest:response>, 'Not that simple anymore' };
The content type can also be overwritten by specifying an output method
. The following method mappings are available:
xml
→application/xml
xhtml
→text/html
html
→text/html
text
→text/plain
raw
→application/octet-stream
json
orjsonml
→application/json
By default, application/xml
is returned as content type. In the following example, XHTML headers will be generated, and text/html
will be set as content type:
declare %rest:path("") %output:method("xhtml") %output:omit-xml-declaration("no") %output:doctype-public("-//W3C//DTD XHTML 1.0 Transitional//EN") %output:doctype-system("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") function page:start() { <html xmlns="http://www.w3.org/1999/xhtml"> <body>done</body> </html> };
Error Handling
XQuery runtime errors can be processed via error annotations. A single argument must be supplied, which represents the QName of the error to be caught. A wildcard *
may be specified to catch all possible errors. A function can only have a single error annotation:
declare %rest:error("*") function page:error() { "An error occurred while processing your RESTXQ code!" };
The XQuery try/catch construct assigns error information to a number of pre-defined variables (code
, description
, value
, module
, line-number
, column-number
, additional
). These variables can be bound to variables via error parameter annotations, which are specified the same way as query parameters.
Errors may occur unexpectedly. However, they can also be triggered by a query, as the following example shows:
declare %rest:path("/check/{$user}") function page:check($user) { if($user = ('jack', 'lisa')) then 'User exists' else fn:error(xs:QName('err:user'), $user) }; declare %rest:error("err:user") %rest:error-param("description", "{$user}") function page:user-error($user) { 'User "' || $user || '" is unknown' };
Errors that occur outside RESTXQ can be caught by adding error-page
elements with an error code and a target location to the web.xml
configuration file (find more details in the Jetty Documentation):
<error-page> <error-code>404</error-code> <location>/error404</location> </error-page>
The target location may be another RESTXQ function. The request:attribute function can be used to request details on the caught error:
declare %rest:path("/error404") function page:error404() { "URL: " || request:attribute("javax.servlet.error.request_uri") || ", " || "Error message: " || request:attribute("javax.servlet.error.message") };
Functions
The Request Module contains functions for accessing data related to the current HTTP request. Two additional modules exist for setting and retrieving server-side session data of the current user (Session Module) and all users known to the HTTP server (Sessions Module). With Version 7.7, a RESTXQ Module was added that provides functions for requesting RESTXQ base URIs and generating a WADL description of all services. Please note that the namespaces of all of these modules must be explicitly specified via module imports in the query prolog.
The following example returns the current host name:
import module namespace request = "http://exquery.org/ns/request"; declare %rest:path("/host-name") function page:host() { 'Remote host name: ' || request:remote-hostname() };
References
RESTXQ has been proposed by Adam Retter. More information on all specifics can be found in the following two documents:
- RESTful XQuery, Standardised XQuery 3.0 Annotations for REST. Paper, XMLPrague, 2012
- RESTXQ. Slides, MarkLogic User Group London, 2012
- RESTXQ Specification, Unofficial Draft
- Web Application, RESTXQ Development. Web Application Development with RESTXQ Slides from XMLPrague 2013
Changelog
- Version 7.7
- Added: Error Handling, File Uploads, Multipart Types
- Updated: RESTXQ function may now also be specified in main modules (suffix:
*.xq
). - Updated: the RESTXQ prefix has been changed from
restxq
torest
.
- Version 7.5
- Added: new XML elements
<rest:redirect/>
and<rest:forward/>