Difference between revisions of "RESTXQ"

From BaseX Documentation
Jump to navigation Jump to search
(46 intermediate revisions by 4 users not shown)
Line 7: Line 7:
 
HTTP responses.
 
HTTP responses.
  
Please note that BaseX provides various extensions to the official draft of the specification:
+
Please note that BaseX provides various extensions to the original draft of the specification:
  
 
* Multipart types are supported, including {{Code|multipart/form-data}}
 
* Multipart types are supported, including {{Code|multipart/form-data}}
Line 15: Line 15:
 
* Parameters are implicitly cast to the type of the function argument
 
* Parameters are implicitly cast to the type of the function argument
 
* The [[#Paths|Path Annotation]] can contain regular expressions
 
* The [[#Paths|Path Annotation]] can contain regular expressions
 +
* <code>%input</code> annotations, support for input-specific content-type parameters
 +
* <code>%rest:single</code> annotation to cancel running RESTXQ functions
 
* Quality factors in the [[#Content Negotiation|Accept header]] will be evaluated
 
* Quality factors in the [[#Content Negotiation|Accept header]] will be evaluated
* <code>%input</code> annotations, support for input-specific content-type parameters
+
* Support for server-side quality factors in the [[#Content Negotiation|<code>%rest:produces</code>]] annotation
 
<br />
 
<br />
  
Line 27: Line 29:
 
All RESTXQ [[XQuery 3.0#Annotations|annotations]] are assigned to the <code><nowiki>http://exquery.org/ns/restxq</nowiki></code> namespace, which is statically bound to the {{Code|rest}} prefix. 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.
 
All RESTXQ [[XQuery 3.0#Annotations|annotations]] are assigned to the <code><nowiki>http://exquery.org/ns/restxq</nowiki></code> namespace, which is statically bound to the {{Code|rest}} prefix. 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 [[Options#RESTXQPATH|RESTXQPATH]] module directory and its sub-directories will be parsed for functions with RESTXQ annotations in library modules (detected by the extension {{Code|.xqm}}) and main modules (detected by {{Code|.xq}}). In main expressions, the main module will never be evaluated. All modules will be cached and parsed again when their timestamp changes.
+
If a RESTXQ URL is requested, the {{Option|RESTXQPATH}} module directory and its sub-directories will be traversed, and all [[XQuery Extensions#Suffixes|XQuery files]] will be parsed for functions with RESTXQ annotations. Sub-directories that include an {{Code|.ignore}} file will be skipped.
 
 
The following features will be available with {{Version|8.3}}:
 
  
* Module caching can be turned on by setting [[Options#CACHERESTXQ|CACHERESTXQ]] to true. This option is particularly helpful in productive environments with a high load.
+
To speed up processing, the functions of the existing XQuery modules are automatically cached in main memory:
* The static root path {{Code|/.init}} can be called to invalidate the RESTXQ module cache.
+
* Functions will be invalidated and parsed again if the timestamp of their module changes.
* A sub-directory will be skipped if it contains a file named {{Code|.ignore}}.
+
* File monitoring can be adjusted via the {{Option|PARSERESTXQ}} option. In productive environments with a high load, it may be recommendable to change the timeout, or completely disable monitoring.
 +
* If files are replaced while the web server is running, the RESTXQ module cache should be explicitly invalidated by calling the static root path {{Code|/.init}} or by calling the [[RESTXQ Module#rest:init|rest:init]] function.
  
 
==Examples==
 
==Examples==
Line 64: Line 65:
 
   %rest:form-param("message","{$message}", "(no message)")
 
   %rest:form-param("message","{$message}", "(no message)")
 
   %rest:header-param("User-Agent", "{$agent}")
 
   %rest:header-param("User-Agent", "{$agent}")
  function page:hello-postman(
+
function page:hello-postman(
    $message as xs:string,
+
  $message as xs:string,
    $agent  as xs:string*)
+
  $agent  as xs:string*
    as element(response)
+
) as element(response) {
{
 
 
   &lt;response type='form'&gt;
 
   &lt;response type='form'&gt;
 
     &lt;message&gt;{ $message }&lt;/message&gt;
 
     &lt;message&gt;{ $message }&lt;/message&gt;
Line 135: Line 135:
 
Two following annotations can be used to restrict functions to specific content types:
 
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 {{Code|Content-Type}} header of the request matches one of the given mime types. Example:
+
* '''HTTP Content Types''': A function will only be invoked if the HTTP {{Code|Content-Type}} header of the request matches one of the given content types. Example:
 
<pre class="brush:xquery">%rest:consumes("application/xml", "text/xml")</pre>
 
<pre class="brush:xquery">%rest:consumes("application/xml", "text/xml")</pre>
* '''HTTP Accept''': a function will only be invoked if the HTTP {{Code|Accept}} header of the request matches one of the defined mime types. Example:
+
* '''HTTP Accept''': A function will only be invoked if the HTTP {{Code|Accept}} header of the request matches one of the defined content types. Example:
 
<pre class="brush:xquery">%rest:produces("application/atom+xml")</pre>
 
<pre class="brush:xquery">%rest:produces("application/atom+xml")</pre>
  
By default, both mime types are {{Code|*/*}}. Quality factors supplied by a client will also be considered in the path selection process.
+
By default, both content types are {{Code|*/*}}. Quality factors supplied by a client will also be considered in the path selection process. If a client supplies the following accept header…
If a client supplies the following accept header…
 
  
 
<pre>
 
<pre>
Line 147: Line 146:
 
</pre>
 
</pre>
  
…and if two RESTXQ functions exist with the same {{Code|path}} annotation and the {{Code|produces}} annotations <code>*/*</code> and <code>text/html</code>, respectively, the function with the second annotation will be called, because the quality factor for <code>text/html</code> documents is higher than the one for arbitrary other mime types.
+
…and if two RESTXQ functions exist with the same {{Code|path}} annotation, one with the {{Code|produces}} annotation <code>*/*</code>, and another with <code>text/html</code>, the second function will be called, because the quality factor for <code>text/html</code> documents is highest.
  
Note that this annotation will ''not'' affect the content-type of the HTTP response. Instead, you will need to add a <code>[[#Output|%output:media-type]]</code> annotation.
+
Server-side quality factors are supported as well: If multiple function candidates are left over after the above steps, the <code>qs</code> parameter will be considered. The function with the highest quality factor will be favored:
 +
 
 +
<pre class="brush:xquery">
 +
%rest:produces("text/html;qs=1")
 +
%rest:produces("*/*;qs=0.8")
 +
</pre>
 +
 
 +
Note that the annotation will ''not'' affect the content type of the actual response. You will need to supply an additional <code>[[#Output|%output:media-type]]</code> annotation.
  
 
===HTTP Methods===
 
===HTTP Methods===
Line 225: Line 231:
 
===Input options===
 
===Input options===
  
Conversion options for JSON, CSV and HTML can also be specified via annotations using the <code>input</code> prefix. The following function treats the first line of the textual input as CSV header:
+
Conversion options for [[Options#JSONPARSER|JSON]], [[Options#CSVPARSER|CSV]] and [[Options#HTMLPARSER|HTML]] can also be specified via annotations with the <code>input</code> prefix. The following function interprets the input as text with the CP1252 encoding and treats the first line as header:
  
 
<pre class="brush:xquery">
 
<pre class="brush:xquery">
Line 231: Line 237:
 
   %rest:path("/store.csv")
 
   %rest:path("/store.csv")
 
   %rest:POST("{$csv}")
 
   %rest:POST("{$csv}")
   %input:csv("header=true")
+
   %input:csv("header=true,encoding=CP1252")
  function page:store-csv($csv as document-node())
+
function page:store-csv($csv as document-node()) {
{
 
 
   "Number of rows: " || count($csv/csv/record)
 
   "Number of rows: " || count($csv/csv/record)
 
};
 
};
Line 250: Line 255:
 
   %rest:POST("{$data}")
 
   %rest:POST("{$data}")
 
   %rest:consumes("multipart/mixed") (: optional :)
 
   %rest:consumes("multipart/mixed") (: optional :)
  function page:multipart($data as item()*)
+
function page:multipart($data as item()*) {
{
 
 
   "Number of items: " || count($data)
 
   "Number of items: " || count($data)
 
};
 
};
Line 269: Line 273:
 
   %rest:query-param("id", "{$id}")
 
   %rest:query-param("id", "{$id}")
 
   %rest:query-param("add", "{$add}", 42, 43, 44)
 
   %rest:query-param("add", "{$add}", 42, 43, 44)
  function page:params($id as xs:string?, $add as xs:integer+)
+
function page:params($id as xs:string?, $add as xs:integer+) {
{
 
 
   <result id="{ $id }" sum="{ sum($add) }"/>
 
   <result id="{ $id }" sum="{ sum($add) }"/>
 
};
 
};
Line 301: Line 304:
 
   %rest:path("/upload")
 
   %rest:path("/upload")
 
   %rest:form-param("files", "{$files}")
 
   %rest:form-param("files", "{$files}")
  function page:upload($files)
+
function page:upload($files) {
{
 
 
   for $name    in map:keys($files)
 
   for $name    in map:keys($files)
 
   let $content := $files($name)
 
   let $content := $files($name)
Line 330: Line 332:
 
%rest:cookie-param("authentication", "{$auth}", "no_auth")
 
%rest:cookie-param("authentication", "{$auth}", "no_auth")
 
</pre>
 
</pre>
 +
 +
==Query Execution==
 +
 +
In many RESTXQ search scenarios, input from browser forms is processed and search results are returned. User experience can generally be made more interactive if an updated search request is triggered with each key click. However, this may lead to many expensive parallel requests, from which only the result of the last request will be relevant for the client.
 +
 +
With the <code>%rest:single</code> annotation, it can be enforced that only one instance of a function will be executed for the same client. If the same function will be called for the second time, the already running query will be stopped, and the HTTP error code {{Code|460}} will be returned instead:
 +
 +
<pre class="brush:xquery">
 +
(: If fast enough, returns the result. Otherwise, if called again, raises 460 :)
 +
declare
 +
  %rest:path("/search")
 +
  %rest:query-param("term", "{$term}")
 +
  %rest:single
 +
function page:search($term as xs:string) {
 +
  <ul>{
 +
    for $result in db:open('large-db')//*[text() = $term]
 +
    return <li>{ $result }</li>
 +
  }</ul>
 +
};
 +
</pre>
 +
 +
By specifying a string along with the annotation, functions can be bundled together, and one request can be canceled by calling another one.
 +
 +
This is shown by another example, in which the first function can be interrupted by the second one. If you call both functions in separate browser tabs, you will note that the first tab will return <code>460</code>, and the second one will return <xml>stopped</xml>.
 +
 +
<pre class="brush:xquery">
 +
declare
 +
  %rest:path("/compute")
 +
  %rest:single("EXPENSIVE")
 +
function local:compute() {
 +
  (1 to 100000000000000)[. = 0]
 +
};
 +
 +
declare
 +
  %rest:path("/stop")
 +
  %rest:single("EXPENSIVE")
 +
function local:stop() {
 +
  <xml>stopped</xml>
 +
};
 +
</pre>
 +
 +
The following things should be noted:
 +
 +
* If a query will be canceled, there will be no undesirable side-effects. For example, it won’t be possible to kill a query if it is currenly updating the database or perfoming any other I/O operations. As a result, the termination of a running query can take some more time as expected.
 +
* The currently executed function is bound to the current session. This way, a client will not be able to cancel requests from other clients. As a result, functions can only be stopped if there was at least one previous successful response, in which initial session data was returned to the client.
  
 
=Response=
 
=Response=
Line 412: Line 459:
 
   %output:method("json")
 
   %output:method("json")
 
   %output:json("format=jsonml")
 
   %output:json("format=jsonml")
  function page:cities()
+
function page:cities() {
{
 
 
   element cities {
 
   element cities {
 
     db:open('factbook')//city/name
 
     db:open('factbook')//city/name
Line 424: Line 470:
 
<pre class="brush:xquery">
 
<pre class="brush:xquery">
 
declare
 
declare
   %rest:path("")
+
   %rest:path("done")
 
   %output:method("xhtml")
 
   %output:method("xhtml")
 
   %output:omit-xml-declaration("no")
 
   %output:omit-xml-declaration("no")
 
   %output:doctype-public("-//W3C//DTD XHTML 1.0 Transitional//EN")   
 
   %output:doctype-public("-//W3C//DTD XHTML 1.0 Transitional//EN")   
 
   %output:doctype-system("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
 
   %output:doctype-system("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
  function page:html()
+
function page:html() {
{
 
 
   <html xmlns="http://www.w3.org/1999/xhtml">
 
   <html xmlns="http://www.w3.org/1999/xhtml">
 
     <body>done</body>
 
     <body>done</body>
Line 462: Line 507:
 
{| class="wikitable"
 
{| class="wikitable"
 
|- valign="top"
 
|- valign="top"
! Priority
+
! Precedence
 
! Syntax
 
! Syntax
 
! Example
 
! Example
Line 483: Line 528:
 
|}
 
|}
  
All error codes that are specified for a function must be of the same priority.
+
All error codes that are specified for a function must have the same precedence.
 
The following rules apply when catching errors:
 
The following rules apply when catching errors:
  
* Codes with a higher priority will be preferred.
+
* Codes with a higher precedence (smaller number) will be given preference.
 
* A global RESTXQ error will be raised if two functions with conflicting codes are found.
 
* A global RESTXQ error will be raised if two functions with conflicting codes are found.
  
Line 496: Line 541:
 
declare
 
declare
 
   %rest:path("/check/{$user}")
 
   %rest:path("/check/{$user}")
  function page:check($user)
+
function page:check($user) {
{
 
 
   if($user = ('jack', 'lisa'))
 
   if($user = ('jack', 'lisa'))
 
   then 'User exists'
 
   then 'User exists'
Line 506: Line 550:
 
   %rest:error("err:user")
 
   %rest:error("err:user")
 
   %rest:error-param("description", "{$user}")
 
   %rest:error-param("description", "{$user}")
  function page:user-error($user)
+
function page:user-error($user) {
{
 
 
   'User "' || $user || '" is unknown'
 
   'User "' || $user || '" is unknown'
 +
};
 +
</pre>
 +
 +
An XQuery error in a RESTXQ context delivers by default a HTTP status code 400 error back to the client. However, you can also define a custom error code by using the third argument of the error function:
 +
 +
<pre class="brush:xquery">
 +
declare
 +
  %rest:path("/teapot")
 +
function page:teapot() {
 +
  fn:error(xs:QName('error'), "I'm a teapot", 418)
 
};
 
};
 
</pre>
 
</pre>
Line 531: Line 584:
 
};
 
};
 
</pre>
 
</pre>
 +
 +
=User Authentication=
 +
 +
If you want to provide restricted access to parts of a web applications, you will need to check permissions before returning a response to the client. The [[Permissions]] layer is a nice abstraction for defining permission checks.
  
 
=Functions=
 
=Functions=
Line 548: Line 605:
 
=References=
 
=References=
  
Currently, the following external resources on RESTXQ exist:
+
Documentation:
  
 
* [http://exquery.org/spec/restxq RESTXQ Specification], First Draft
 
* [http://exquery.org/spec/restxq RESTXQ Specification], First Draft
Line 554: Line 611:
 
* [http://www.adamretter.org.uk/presentations/restxq_mugl_20120308.pdf RESTXQ]. Slides, MarkLogic User Group London, 2012
 
* [http://www.adamretter.org.uk/presentations/restxq_mugl_20120308.pdf RESTXQ]. Slides, MarkLogic User Group London, 2012
 
* [http://files.basex.org/publications/xmlprague/2013/Develop-RESTXQ-WebApps-with-BaseX.pdf Web Application Development]. Slides from XMLPrague 2013
 
* [http://files.basex.org/publications/xmlprague/2013/Develop-RESTXQ-WebApps-with-BaseX.pdf Web Application Development]. Slides from XMLPrague 2013
 +
 +
Examples:
 +
 +
* Sample code combining XQuery and JavaScript: [http://balisage.net/Proceedings/vol17/author-pkg/Galtman01/BalisageVol17-Galtman01.html Materials] and [http://balisage.net/Proceedings/vol17/html/Galtman01/BalisageVol17-Galtman01.html paper] from Amanda Galtman, Balisage 2016.
 +
* [[DBA]]: The Database Administration interface, bundled with the full distributions of BaseX.
  
 
=Changelog=
 
=Changelog=
 +
 +
;Version 9.0
 +
 +
* Added: Support for server-side quality factors in the [[#Content Negotiation|<code>%rest:produces</code>]] annotation
 +
* Updated: Status code {{Code|410}} was replaced with {{Code|460}}
 +
* Removed: {{Code|restxq}} prefix
 +
 +
;Version 8.4
 +
 +
* Added: <code>%rest:single</code> annotation
  
 
;Version 8.1
 
;Version 8.1
Line 583: Line 655:
  
 
* Added: new XML elements {{Code|<rest:redirect/>}} and {{Code|<rest:forward/>}}
 
* Added: new XML elements {{Code|<rest:redirect/>}} and {{Code|<rest:forward/>}}
 
[[Category:HTTP]]
 
[[Category:Developer]]
 

Revision as of 19:05, 7 June 2018

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 an 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.

Please note that BaseX provides various extensions to the original draft of the specification:

  • Multipart types are supported, including multipart/form-data
  • A %rest:error annotation can be used to catch XQuery errors
  • Servlet errors can be redirected to other RESTXQ pages
  • A RESTXQ Module provides some helper functions
  • Parameters are implicitly cast to the type of the function argument
  • The Path Annotation can contain regular expressions
  • %input annotations, support for input-specific content-type parameters
  • %rest:single annotation to cancel running RESTXQ functions
  • Quality factors in the Accept header will be evaluated
  • Support for server-side quality factors in the %rest:produces annotation


Introduction

Preliminaries

The RESTXQ service is accessible via http://localhost:8984/.

All RESTXQ annotations are assigned to the http://exquery.org/ns/restxq namespace, which is statically bound to the rest prefix. 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.

If a RESTXQ URL is requested, the RESTXQPATH module directory and its sub-directories will be traversed, and all XQuery files will be parsed for functions with RESTXQ annotations. Sub-directories that include an .ignore file will be skipped.

To speed up processing, the functions of the existing XQuery modules are automatically cached in main memory:

  • Functions will be invalidated and parsed again if the timestamp of their module changes.
  • File monitoring can be adjusted via the PARSERESTXQ option. In productive environments with a high load, it may be recommendable to change the timeout, or completely disable monitoring.
  • If files are replaced while the web server is running, the RESTXQ module cache should be explicitly invalidated by calling the static root path /.init or by calling the rest:init function.

Examples

A first RESTXQ function is shown below:

module namespace page = 'http://basex.org/examples/web-page';

declare %rest:path("hello/{$who}") %rest:GET function page:hello($who) {
  <response>
    <title>Hello { $who }!</title>
  </response>
};

If the URI http://localhost:8984/hello/World is accessed, the result will be:

<response>
  <title>Hello World!</title>
</response>

The next function demonstrates a POST request:

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 "message='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>'CONTENT'</message>
  <user-agent>curl/7.31.0</user-agent>
</response>

Request

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 first slash in the path is optional.

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) { ... };

Variables can be enhanced by regular expressions:

(: Matches all paths with "app" as first, a number as second, and "order" as third segment :)
declare %rest:path("app/{$code=[0-9]+}/order")
  function page:order($full-path) { ... };

(: Matches all other all paths starting with "app/" :)
declare %rest:path("app/{$path=.+}")
  function page:others($path) { ... };

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 content 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 content types. Example:
%rest:produces("application/atom+xml")

By default, both content types are */*. Quality factors supplied by a client will also be considered in the path selection process. If a client supplies the following accept header…

*/*;q=0.5,text/html;q=1.0

…and if two RESTXQ functions exist with the same path annotation, one with the produces annotation */*, and another with text/html, the second function will be called, because the quality factor for text/html documents is highest.

Server-side quality factors are supported as well: If multiple function candidates are left over after the above steps, the qs parameter will be considered. The function with the highest quality factor will be favored:

%rest:produces("text/html;qs=1")
%rest:produces("*/*;qs=0.8")

Note that the annotation will not affect the content type of the actual response. You will need to supply an additional %output:media-type annotation.

HTTP Methods

Default Methods

The HTTP method annotations are equivalent to all HTTP request methods except 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 };

Custom Methods

Custom HTTP methods can be specified with the %rest:method annotation:

declare %rest:method("RETRIEVE")
  function page:retrieve() { "RETRIEVE was specified as request method." };

Content Types

The body of a POST or PUT request will be converted to an XQuery item. Conversion can be controlled by specifying a content type. It can be further influenced by specifying additional content-type parameters:

Content-Type Parameters (;name=value) Type of resulting XQuery item
text/xml, application/xml document-node()
text/* xs:string
application/json JSON Options document-node() or map(*)
text/html HTML Options document-node()
text/comma-separated-values CSV Options document-node() or map(*)
others xs:base64Binary
multipart/* sequence (see next paragraph)

For example, if application/json;lax=yes is specified as content type, the input will be transformed to JSON, and the lax QName conversion rules will be applied, as described in the JSON Module.

Input options

Conversion options for JSON, CSV and HTML can also be specified via annotations with the input prefix. The following function interprets the input as text with the CP1252 encoding and treats the first line as header:

declare
  %rest:path("/store.csv")
  %rest:POST("{$csv}")
  %input:csv("header=true,encoding=CP1252")
function page:store-csv($csv as document-node()) {
  "Number of rows: " || count($csv/csv/record)
};

Multipart Types

The single 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.

A function that is capable of handling multipart types is identical to other RESTXQ functions:

declare
  %rest:path("/multipart")
  %rest:POST("{$data}")
  %rest:consumes("multipart/mixed") (: optional :)
function page:multipart($data as item()*) {
  "Number of items: " || count($data)
};

Parameters

The following annotations can be used to bind request values to function arguments. Values will implicitly be cast to the type of the argument.

Query Parameters

The value of the first parameter, if found in the query component, will be assigned to the variable specified as second parameter. If no value is specified in the HTTP request, all additional parameters will be bound to the variable (if no additional parameter is given, an empty sequence will be bound):

declare
  %rest:path("/params")
  %rest:query-param("id", "{$id}")
  %rest:query-param("add", "{$add}", 42, 43, 44)
function page:params($id as xs:string?, $add as xs:integer+) {
  <result id="{ $id }" sum="{ sum($add) }"/>
};

HTML Form Fields

Form parameters are specified the same way as query parameters. Their values are the result of HTML forms submitted with the content type application/x-www-form-urlencoded.

%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 action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="files"  multiple="multiple"/>
  <input type="submit"/>
</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")

Query Execution

In many RESTXQ search scenarios, input from browser forms is processed and search results are returned. User experience can generally be made more interactive if an updated search request is triggered with each key click. However, this may lead to many expensive parallel requests, from which only the result of the last request will be relevant for the client.

With the %rest:single annotation, it can be enforced that only one instance of a function will be executed for the same client. If the same function will be called for the second time, the already running query will be stopped, and the HTTP error code 460 will be returned instead:

(: If fast enough, returns the result. Otherwise, if called again, raises 460 :)
declare
  %rest:path("/search")
  %rest:query-param("term", "{$term}")
  %rest:single
function page:search($term as xs:string) {
  <ul>{
    for $result in db:open('large-db')//*[text() = $term]
    return <li>{ $result }</li>
  }</ul>
};

By specifying a string along with the annotation, functions can be bundled together, and one request can be canceled by calling another one.

This is shown by another example, in which the first function can be interrupted by the second one. If you call both functions in separate browser tabs, you will note that the first tab will return 460, and the second one will return <xml>stopped</xml>.

declare 
  %rest:path("/compute")
  %rest:single("EXPENSIVE")
function local:compute() {
  (1 to 100000000000000)[. = 0]
};

declare 
  %rest:path("/stop")
  %rest:single("EXPENSIVE")
function local:stop() {
  <xml>stopped</xml>
};

The following things should be noted:

  • If a query will be canceled, there will be no undesirable side-effects. For example, it won’t be possible to kill a query if it is currenly updating the database or perfoming any other I/O operations. As a result, the termination of a running query can take some more time as expected.
  • The currently executed function is bound to the current session. This way, a client will not be able to cancel requests from other clients. As a result, functions can only be stopped if there was at least one previous successful response, in which initial session data was returned to the client.

Response

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 Response

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

The function web:redirect can be used to create a redirect response element. Alternatively, the following element can be sent:

<rest:redirect>{ $location }</rest:redirect>

It is an abbreviation for:

<rest:response>
  <http:response status="302">
    <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

The content-type of a response can be influenced by the user via Serialization Parameters. The steps are described in the REST chapter. In RESTXQ, serialization parameters can be specified in the query prolog, via annotations, or within the REST response element:

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

Global serialization parameters can be overwritten via %output annotations. The following example serializes XML nodes as JSON, using the JsonML format:

declare
  %rest:path("cities")
  %output:method("json")
  %output:json("format=jsonml")
function page:cities() {
  element cities {
    db:open('factbook')//city/name
  }
};

The next function, when called, generates XHTML headers, and text/html will be set as content type:

declare
  %rest:path("done")
  %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:html() {
  <html xmlns="http://www.w3.org/1999/xhtml">
    <body>done</body>
  </html>
};

Response Element

Serialization parameters can also be specified in a REST reponse element in a query. Serialization parameters will be overwritten:

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'
};

Error Handling

XQuery Errors

XQuery runtime errors can be processed via error annotations. Error annotations have one or more arguments, which represent the error codes to be caught. The codes equal the names of the XQuery 3.0 try/catch construct:

Precedence Syntax Example
1 prefix:name
Q{uri}name
err:FORG0001
Q{http://www.w3.org/2005/xqt-errors}FORG0001
2 prefix:*
Q{uri}*
err:*
Q{http://www.w3.org/2005/xqt-errors}*
3 *:name *:FORG0001
4 * *

All error codes that are specified for a function must have the same precedence. The following rules apply when catching errors:

  • Codes with a higher precedence (smaller number) will be given preference.
  • A global RESTXQ error will be raised if two functions with conflicting codes are found.

Similar to try/catch, the pre-defined variables (code, description, value, module, line-number, column-number, additional) 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 demonstrated by the following example:

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'
};

An XQuery error in a RESTXQ context delivers by default a HTTP status code 400 error back to the client. However, you can also define a custom error code by using the third argument of the error function:

declare
  %rest:path("/teapot")
function page:teapot() {
  fn:error(xs:QName('error'), "I'm a teapot", 418)
};

HTTP Errors

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")
};

User Authentication

If you want to provide restricted access to parts of a web applications, you will need to check permissions before returning a response to the client. The Permissions layer is a nice abstraction for defining permission checks.

Functions

The Request Module contains functions for accessing data related to the current HTTP request. Two 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). The RESTXQ Module 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

Documentation:

Examples:

  • Sample code combining XQuery and JavaScript: Materials and paper from Amanda Galtman, Balisage 2016.
  • DBA: The Database Administration interface, bundled with the full distributions of BaseX.

Changelog

Version 9.0
  • Added: Support for server-side quality factors in the %rest:produces annotation
  • Updated: Status code 410 was replaced with 460
  • Removed: restxq prefix
Version 8.4
  • Added: %rest:single annotation
Version 8.1
  • Added: support for input-specific content-type parameters
  • Added: %input annotations
Version 8.0
Version 7.9
  • Updated: XQuery Errors, extended error annotations
  • Added: %rest:method
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 to rest.
  • Updated: parameters are implicitly cast to the type of the function argument
  • Updated: the RESTXQ root url has been changed to http://localhost:8984/
Version 7.5
  • Added: new XML elements <rest:redirect/> and <rest:forward/>