Difference between revisions of "RESTXQ"
m (Text replacement - "8984" to "8080") |
|||
(32 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
This page presents one of the [[Web Application]] services. It describes how to use the RESTXQ API of BaseX. | This page presents one of the [[Web Application]] services. It describes how to use the RESTXQ API of BaseX. | ||
− | RESTXQ, introduced by [http://www.adamretter.org.uk/ Adam Retter], is an API that facilitates the use of XQuery | + | RESTXQ, introduced by [http://www.adamretter.org.uk/ Adam Retter], is an API that facilitates the use of XQuery as a server-side processing language for the Web. It has been inspired by the Java [https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services JAX-RS API]: It provides a pre-defined set of XQuery 3.0 annotations for mapping HTTP requests to XQuery functions, which in turn generate and return HTTP responses. |
− | as a server-side processing language for the Web. | ||
− | [ | ||
− | 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: | Please note that BaseX provides various extensions to the original draft of the specification: | ||
Line 26: | Line 22: | ||
==Preliminaries== | ==Preliminaries== | ||
− | The RESTXQ service is accessible via {{Code|http://localhost: | + | The RESTXQ service is accessible via {{Code|http://localhost:8080/}}. |
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. | ||
− | If a RESTXQ URL is requested, the {{Option|RESTXQPATH}} module directory and its | + | If a RESTXQ URL is requested, the {{Option|RESTXQPATH}} module directory and its subdirectories will be traversed, and all [[XQuery Extensions#Suffixes|XQuery files]] will be parsed for functions with RESTXQ annotations. Subdirectories that include an {{Code|.ignore}} file will be skipped. |
To speed up processing, the functions of the existing XQuery modules are automatically cached in main memory: | 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. | * Functions will be invalidated and parsed again if the timestamp of their module changes. | ||
* 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. | * 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 | + | * 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 {{Function|RESTXQ|rest:init}} function. |
==Examples== | ==Examples== | ||
Line 41: | Line 37: | ||
A first RESTXQ function is shown below: | A first RESTXQ function is shown below: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
module namespace page = 'http://basex.org/examples/web-page'; | module namespace page = 'http://basex.org/examples/web-page'; | ||
Line 48: | Line 44: | ||
<title>Hello { $who }!</title> | <title>Hello { $who }!</title> | ||
</response> | </response> | ||
− | };</ | + | }; |
+ | </syntaxhighlight> | ||
− | If the URI http://localhost: | + | If the URI http://localhost:8080/hello/World is accessed, the result will be: |
− | < | + | <syntaxhighlight lang="xml"> |
− | + | <response> | |
− | + | <title>Hello World!</title> | |
− | + | </response> | |
− | </ | + | </syntaxhighlight> |
The next function demonstrates a POST request: | The next function demonstrates a POST request: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/form") | %rest:path("/form") | ||
Line 70: | Line 67: | ||
$agent as xs:string* | $agent as xs:string* | ||
) as element(response) { | ) as element(response) { | ||
− | + | <response type='form'> | |
− | + | <message>{ $message }</message> | |
− | + | <user-agent>{ $agent }</user-agent> | |
− | + | </response> | |
}; | }; | ||
− | </ | + | </syntaxhighlight> |
− | If you post something (e.g. using curl or the embedded form at http://localhost: | + | If you post something (e.g. using curl or the embedded form at http://localhost:8080/)... |
− | < | + | <syntaxhighlight lang="shell"> |
− | curl -i -X POST --data "message='CONTENT'" http://localhost: | + | curl -i -X POST --data "message='CONTENT'" http://localhost:8080/form |
− | </ | + | </syntaxhighlight> |
...you will receive something similar to the following result: | ...you will receive something similar to the following result: | ||
− | < | + | <syntaxhighlight lang="shell"> |
HTTP/1.1 200 OK | HTTP/1.1 200 OK | ||
Content-Type: application/xml; charset=UTF-8 | Content-Type: application/xml; charset=UTF-8 | ||
Content-Length: 107 | Content-Length: 107 | ||
Server: Jetty(8.1.11.v20130520) | Server: Jetty(8.1.11.v20130520) | ||
− | </ | + | </syntaxhighlight> |
− | < | + | <syntaxhighlight lang="xml"> |
<response type="form"> | <response type="form"> | ||
<message>'CONTENT'</message> | <message>'CONTENT'</message> | ||
<user-agent>curl/7.31.0</user-agent> | <user-agent>curl/7.31.0</user-agent> | ||
</response> | </response> | ||
− | </ | + | </syntaxhighlight> |
=Request= | =Request= | ||
Line 113: | Line 110: | ||
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 <code>$variable</code> will be cast to an <code>xs:integer</code> before being bound: | 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 <code>$variable</code> will be cast to an <code>xs:integer</code> before being bound: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %rest:path("/a/path/{$with}/some/{$variable}") | declare %rest:path("/a/path/{$with}/some/{$variable}") | ||
function page:test($with, $variable as xs:integer) { ... }; | function page:test($with, $variable as xs:integer) { ... }; | ||
− | </ | + | </syntaxhighlight> |
<!-- TODO how matching works --> | <!-- TODO how matching works --> | ||
Variables can be enhanced by regular expressions: | Variables can be enhanced by regular expressions: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
(: Matches all paths with "app" as first, a number as second, and "order" as third segment :) | (: Matches all paths with "app" as first, a number as second, and "order" as third segment :) | ||
declare %rest:path("app/{$code=[0-9]+}/order") | declare %rest:path("app/{$code=[0-9]+}/order") | ||
Line 129: | Line 126: | ||
declare %rest:path("app/{$path=.+}") | declare %rest:path("app/{$path=.+}") | ||
function page:others($path) { ... }; | function page:others($path) { ... }; | ||
− | </ | + | </syntaxhighlight> |
<!-- TODO how matching works --> | <!-- TODO how matching works --> | ||
Line 136: | Line 133: | ||
===Content Negotiation=== | ===Content Negotiation=== | ||
− | + | Functions can be restricted to specific Media Types. The default type is {{Code|*/*}}. Multiple types can either be specified by a single or by multiple annotations. | |
− | + | ====Consuming Data==== | |
− | |||
− | |||
− | |||
− | + | A function will only be taken into consideration if the HTTP {{Code|Content-Type}} header of the request matches one of the given types: | |
+ | |||
+ | <syntaxhighlight lang="xquery"> | ||
+ | declare | ||
+ | %rest:POST("{$body}") | ||
+ | %rest:path("/xml") | ||
+ | %rest:consumes("application/xml") | ||
+ | %rest:consumes("text/xml") | ||
+ | function page:xml($body) { $body }; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ====Producing Data==== | ||
+ | |||
+ | A function will only be chosen if the HTTP {{Code|Accept}} header of the request matches one of the given types: | ||
+ | |||
+ | <syntaxhighlight lang="xquery"> | ||
+ | declare | ||
+ | %rest:path("/xml") | ||
+ | %rest:produces("application/xml", "text/xml") | ||
+ | function page:xml() { <xml/> }; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Note that the annotations will ''not'' affect the type of the actual response: You will need to supply an additional <code>[[#Output|%output:media-type]]</code> annotation or (if a single function may produce results of different types) generate an apt [[#Custom_Response|Custom Response]]. | ||
+ | |||
+ | ====Quality Factors==== | ||
+ | |||
+ | A client can supply quality factors to influence the server-side function selection process. If a client sends the following HTTP header with quality factors… | ||
<pre> | <pre> | ||
− | */*;q=0.5,text/html;q=1.0 | + | Accept: */*;q=0.5,text/html;q=1.0 |
</pre> | </pre> | ||
− | …and if two RESTXQ functions exist | + | …and if two RESTXQ functions exist for the addressed path with two different annotations for producing data… |
− | + | <syntaxhighlight lang="xquery"> | |
+ | declare function %rest:produces("text/html") ... | ||
+ | ... | ||
+ | declare function %rest:produces("*/*") ... | ||
+ | </syntaxhighlight> | ||
− | < | + | …the first of these function will be chosen, as the quality factor for <code>text/html</code> documents is highest. |
− | + | ||
− | + | As we cannot ensure that the client may supply quality factors, the selection process can also be controlled server-side. The <code>qs</code> parameter can be attached server-side to the Media Type. If multiple functions are left in the selection process, the one with the highest quality factor will be favored: | |
− | </ | ||
− | + | <syntaxhighlight lang="xquery"> | |
+ | declare function %rest:produces("application/json;qs=1") ... | ||
+ | ... | ||
+ | declare function %rest:produces("*/*;qs=0.5") ... | ||
+ | </syntaxhighlight> | ||
===HTTP Methods=== | ===HTTP Methods=== | ||
Line 164: | Line 191: | ||
====Default Methods==== | ====Default Methods==== | ||
− | The HTTP method annotations are equivalent to all [ | + | The HTTP method annotations are equivalent to all [https://en.wikipedia.org/wiki/HTTP#Request_methods 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: | The following function will be called if GET or POST is used as request method: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %rest:GET %rest:POST %rest:path("/post") | declare %rest:GET %rest:POST %rest:path("/post") | ||
function page:post() { "This was a GET or POST request" }; | function page:post() { "This was a GET or POST request" }; | ||
− | </ | + | </syntaxhighlight> |
The POST and PUT annotations may optionally take a string literal in order to map the HTTP request body to a [[#Parameters|function argument]]. Once again, the target variable must be embraced by curly brackets: | The POST and PUT annotations may optionally take a string literal in order to map the HTTP request body to a [[#Parameters|function argument]]. Once again, the target variable must be embraced by curly brackets: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %rest:PUT("{$body}") %rest:path("/put") | declare %rest:PUT("{$body}") %rest:path("/put") | ||
function page:put($body) { "Request body: " || $body }; | function page:put($body) { "Request body: " || $body }; | ||
− | </ | + | </syntaxhighlight> |
====Custom Methods==== | ====Custom Methods==== | ||
Line 184: | Line 211: | ||
Custom HTTP methods can be specified with the {{Code|%rest:method}} annotation. An optional body variable can be supplied as second argument: | Custom HTTP methods can be specified with the {{Code|%rest:method}} annotation. An optional body variable can be supplied as second argument: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("binary-size") | %rest:path("binary-size") | ||
Line 194: | Line 221: | ||
"Size of body: " || bin:length($body) | "Size of body: " || bin:length($body) | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
− | |||
− | |||
If an OPTIONS request is received, and if no function is defined, an automatic response will be generated, which includes an <code>Allow</code> header with all supported methods. | If an OPTIONS request is received, and if no function is defined, an automatic response will be generated, which includes an <code>Allow</code> header with all supported methods. | ||
Line 208: | Line 233: | ||
by specifying additional content-type parameters: | by specifying additional content-type parameters: | ||
− | {| class="wikitable | + | {| class="wikitable" |
|- valign="top" | |- valign="top" | ||
! Content-Type | ! Content-Type | ||
! Parameters (<code>;name=value</code>) | ! Parameters (<code>;name=value</code>) | ||
! Type of resulting XQuery item | ! Type of resulting XQuery item | ||
− | |- | + | |- valign="top" |
| {{Code|text/xml}}, {{Code|application/xml}} | | {{Code|text/xml}}, {{Code|application/xml}} | ||
| | | | ||
| {{Code|document-node()}} | | {{Code|document-node()}} | ||
− | |- | + | |- valign="top" |
| {{Code|text/*}} | | {{Code|text/*}} | ||
| | | | ||
| {{Code|xs:string}} | | {{Code|xs:string}} | ||
− | |- | + | |- valign="top" |
| {{Code|application/json}} | | {{Code|application/json}} | ||
| [[JSON Module#Options|JSON Options]] | | [[JSON Module#Options|JSON Options]] | ||
| {{Code|document-node()}} or {{Code|map(*)}} | | {{Code|document-node()}} or {{Code|map(*)}} | ||
− | |- | + | |- valign="top" |
| {{Code|text/html}} | | {{Code|text/html}} | ||
| [[HTML Module#Options|HTML Options]] | | [[HTML Module#Options|HTML Options]] | ||
| {{Code|document-node()}} | | {{Code|document-node()}} | ||
− | |- | + | |- valign="top" |
| {{Code|text/comma-separated-values}} | | {{Code|text/comma-separated-values}} | ||
| [[CSV Module#Options|CSV Options]] | | [[CSV Module#Options|CSV Options]] | ||
| {{Code|document-node()}} or {{Code|map(*)}} | | {{Code|document-node()}} or {{Code|map(*)}} | ||
− | |- | + | |- valign="top" |
| ''others'' | | ''others'' | ||
| | | | ||
| {{Code|xs:base64Binary}} | | {{Code|xs:base64Binary}} | ||
− | |- | + | |- valign="top" |
| {{Code|multipart/*}} | | {{Code|multipart/*}} | ||
| | | | ||
Line 249: | Line 274: | ||
Conversion options for {{Option|JSON}}, {{Option|CSV}} and {{Option|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: | Conversion options for {{Option|JSON}}, {{Option|CSV}} and {{Option|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: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/store.csv") | %rest:path("/store.csv") | ||
Line 257: | Line 282: | ||
"Number of rows: " || count($csv/csv/record) | "Number of rows: " || count($csv/csv/record) | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===Multipart Types=== | ===Multipart Types=== | ||
Line 266: | Line 291: | ||
A function that is capable of handling multipart types is identical to other RESTXQ functions: | A function that is capable of handling multipart types is identical to other RESTXQ functions: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/multipart") | %rest:path("/multipart") | ||
Line 274: | Line 299: | ||
"Number of items: " || count($data) | "Number of items: " || count($data) | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
==Parameters== | ==Parameters== | ||
Line 284: | Line 309: | ||
The value of the ''first parameter'', if found in the [[Request_Module#Conventions|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): | The value of the ''first parameter'', if found in the [[Request_Module#Conventions|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): | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/params") | %rest:path("/params") | ||
Line 292: | Line 317: | ||
<result id="{ $id }" sum="{ sum($add) }"/> | <result id="{ $id }" sum="{ sum($add) }"/> | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===HTML Form Fields=== | ===HTML Form Fields=== | ||
− | Form parameters are specified the same way as [[#Query Parameters|query parameters]] | + | Form parameters are specified the same way as [[#Query Parameters|query parameters]]: |
+ | |||
+ | <syntaxhighlight lang="xquery"> | ||
+ | %rest:form-param("city", "{$city}", "no-city-specified") | ||
+ | </syntaxhighlight> | ||
− | < | + | The values are the result of HTML forms submitted with the (default) content type <code>application/x-www-form-urlencoded</code>: |
− | + | ||
− | </ | + | <syntaxhighlight lang="xml"> |
+ | <form action="/process" method="POST" enctype="application/x-www-form-urlencoded"> | ||
+ | <input type="text" name="city"/> | ||
+ | <input type="submit"/> | ||
+ | </form> | ||
+ | </syntaxhighlight> | ||
====File Uploads==== | ====File Uploads==== | ||
Line 306: | Line 340: | ||
Files can be uploaded to the server by using the content type {{Code|multipart/form-data}} (the HTML5 {{Code|multiple}} attribute enables the upload of multiple files): | Files can be uploaded to the server by using the content type {{Code|multipart/form-data}} (the HTML5 {{Code|multiple}} attribute enables the upload of multiple files): | ||
− | < | + | <syntaxhighlight lang="xml"> |
<form action="/upload" method="POST" enctype="multipart/form-data"> | <form action="/upload" method="POST" enctype="multipart/form-data"> | ||
− | <input type="file" name="files" | + | <input type="file" name="files" multiple="multiple"/> |
<input type="submit"/> | <input type="submit"/> | ||
</form> | </form> | ||
− | </ | + | </syntaxhighlight> |
The file contents are placed in a [[Map Module|map]], with the filename serving as key. The following example shows how uploaded files can be stored in a temporary directory: | The file contents are placed in a [[Map Module|map]], with the filename serving as key. The following example shows how uploaded files can be stored in a temporary directory: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:POST | %rest:POST | ||
Line 329: | Line 363: | ||
) | ) | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===HTTP Headers=== | ===HTTP Headers=== | ||
Line 335: | Line 369: | ||
Header parameters are specified the same way as [[#Query Parameters|query parameters]]: | Header parameters are specified the same way as [[#Query Parameters|query parameters]]: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
%rest:header-param("User-Agent", "{$user-agent}") | %rest:header-param("User-Agent", "{$user-agent}") | ||
%rest:header-param("Referer", "{$referer}", "none") | %rest:header-param("Referer", "{$referer}", "none") | ||
− | </ | + | </syntaxhighlight> |
===Cookies=== | ===Cookies=== | ||
Line 344: | Line 378: | ||
Cookie parameters are specified the same way as [[#Query Parameters|query parameters]]: | Cookie parameters are specified the same way as [[#Query Parameters|query parameters]]: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
%rest:cookie-param("username", "{$user}") | %rest:cookie-param("username", "{$user}") | ||
%rest:cookie-param("authentication", "{$auth}", "no_auth") | %rest:cookie-param("authentication", "{$auth}", "no_auth") | ||
− | </ | + | </syntaxhighlight> |
==Query Execution== | ==Query Execution== | ||
− | In many | + | In many web search scenarios, user input from browser forms is processed and search results are returned. Such operations can be made more interactive by sending a new search request to the server with each key click. However, this may lead to many parallel server-side 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 | + | With the <code>%rest:single</code> annotation, it can be enforced that only one instance of a function will run at the same time and for the same client. If the same function will be called for the second time, a currently executed query will be stopped, and the HTTP error code {{Code|460}} will be returned instead: |
− | < | + | <syntaxhighlight lang="xquery"> |
(: If fast enough, returns the result. Otherwise, if called again, raises 460 :) | (: If fast enough, returns the result. Otherwise, if called again, raises 460 :) | ||
declare | declare | ||
Line 363: | Line 397: | ||
function page:search($term as xs:string) { | function page:search($term as xs:string) { | ||
<ul>{ | <ul>{ | ||
− | for $result in db: | + | for $result in db:get('large-db')//*[text() = $term] |
return <li>{ $result }</li> | return <li>{ $result }</li> | ||
}</ul> | }</ul> | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
− | By | + | By adding a string value to with the annotation, functions can be bundled together, and a running query can be canceled by calling another one that has the same annotation value. 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>. |
− | + | <syntaxhighlight lang="xquery"> | |
− | |||
− | |||
declare | declare | ||
%rest:path("/compute") | %rest:path("/compute") | ||
Line 387: | Line 419: | ||
<xml>stopped</xml> | <xml>stopped</xml> | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
The following things should be noted: | The following things should be noted: | ||
− | * If a query will be canceled, there will be no undesirable side | + | * If a query will be canceled, there will be no undesirable side effects. For example, it won’t be possible to abort a query if it is currently updating the database or performing 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. | * 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. | ||
Line 397: | Line 429: | ||
By default, a successful request is answered with the HTTP status code {{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. {{Code|404}} for “resource not found”). | By default, a successful request is answered with the HTTP status code {{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. {{Code|404}} for “resource not found”). | ||
+ | |||
+ | A {{Code|Server-Timing}} HTTP header is attached to each response. It indicates how much time was spent for parsing, compiling, evaluating and serializing the query. The last value will not necessarily reflect the full time for serializing the result, as the header is generated before the result is sent to the client. Server-side serialization can be enforced by annotating a function with the <code>[[#Query Execution|%rest:single]]</code> annotation. | ||
==Custom Response== | ==Custom Response== | ||
Line 402: | Line 436: | ||
Custom responses can be generated in XQuery by returning an <code>rest:response</code> element, an <code>http:response</code> child node that matches the syntax of the [http://expath.org/spec/http-client EXPath HTTP Client Module] specification, and optional child nodes that will be serialized as usual. A function that yields a response on an unknown resource may look as follows: | Custom responses can be generated in XQuery by returning an <code>rest:response</code> element, an <code>http:response</code> child node that matches the syntax of the [http://expath.org/spec/http-client EXPath HTTP Client Module] specification, and optional child nodes that will be serialized as usual. A function that yields a response on an unknown resource may look as follows: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %output:method("text") %rest:path("") function page:error404() { | declare %output:method("text") %rest:path("") function page:error404() { | ||
<rest:response> | <rest:response> | ||
Line 412: | Line 446: | ||
"The requested resource is not available." | "The requested resource is not available." | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
==Forwards and Redirects== | ==Forwards and Redirects== | ||
===Redirects=== | ===Redirects=== | ||
− | |||
− | |||
The server can invite the client (e.g., the web browser) to make a second request to another URL by sending a 302 response: | The server can invite the client (e.g., the web browser) to make a second request to another URL by sending a 302 response: | ||
− | < | + | <syntaxhighlight lang="xml"> |
<rest:response> | <rest:response> | ||
<http:response status="302"> | <http:response status="302"> | ||
Line 428: | Line 460: | ||
</http:response> | </http:response> | ||
</rest:response> | </rest:response> | ||
− | </ | + | </syntaxhighlight> |
The convenience function {{Function|Web|web:redirect}} can be called to create such a response. | The convenience function {{Function|Web|web:redirect}} can be called to create such a response. | ||
Line 434: | Line 466: | ||
In the XQuery context, redirects are particularly helpful if [[XQuery Update|Updates]] are performed. An updating request may send a redirect to a second function that generates a success message, or evaluates an updated database: | In the XQuery context, redirects are particularly helpful if [[XQuery Update|Updates]] are performed. An updating request may send a redirect to a second function that generates a success message, or evaluates an updated database: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %updating %rest:path('/app/init') function local:create() { | declare %updating %rest:path('/app/init') function local:create() { | ||
db:create('app', <root/>, 'root.xml'), | db:create('app', <root/>, 'root.xml'), | ||
Line 441: | Line 473: | ||
declare %rest:path('/app/ok') function local:ok() { | declare %rest:path('/app/ok') function local:ok() { | ||
− | 'Stored documents: ' || count(db: | + | 'Stored documents: ' || count(db:get('app')) |
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===Forwards=== | ===Forwards=== | ||
Line 449: | Line 481: | ||
A server-side redirect is called forwarding. It reduces traffic among client and server, and the forwarding will not change the URL seen from the client’s perspective: | A server-side redirect is called forwarding. It reduces traffic among client and server, and the forwarding will not change the URL seen from the client’s perspective: | ||
− | < | + | <syntaxhighlight lang="xml"> |
<rest:forward>new-location</rest:forward> | <rest:forward>new-location</rest:forward> | ||
− | </ | + | </syntaxhighlight> |
The fragment can also be created with the convenience function {{Function|Web|web:forward}}. | The fragment can also be created with the convenience function {{Function|Web|web:forward}}. | ||
Line 463: | Line 495: | ||
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 {{Code|media-type}} parameter: | 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 {{Code|media-type}} parameter: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare option output:media-type 'text/plain'; | declare option output:media-type 'text/plain'; | ||
Line 469: | Line 501: | ||
'Keep it simple, stupid' | 'Keep it simple, stupid' | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===Annotations=== | ===Annotations=== | ||
Line 475: | Line 507: | ||
Global serialization parameters can be overwritten via <code>%output</code> annotations. The following example serializes XML nodes as JSON, using the [[JSON Module|JsonML]] format: | Global serialization parameters can be overwritten via <code>%output</code> annotations. The following example serializes XML nodes as JSON, using the [[JSON Module|JsonML]] format: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("cities") | %rest:path("cities") | ||
Line 482: | Line 514: | ||
function page:cities() { | function page:cities() { | ||
element cities { | element cities { | ||
− | db: | + | db:get('factbook')//city/name |
} | } | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
The next function, when called, generates XHTML headers, and {{Code|text/html}} will be set as content type: | The next function, when called, generates XHTML headers, and {{Code|text/html}} will be set as content type: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("done") | %rest:path("done") | ||
Line 501: | Line 533: | ||
</html> | </html> | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
===Response Element=== | ===Response Element=== | ||
Line 507: | Line 539: | ||
Serialization parameters can also be specified in a REST reponse element in a query. Serialization parameters will be overwritten: | Serialization parameters can also be specified in a REST reponse element in a query. Serialization parameters will be overwritten: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare %rest:path("version3") function page:version3() { | declare %rest:path("version3") function page:version3() { | ||
<rest:response> | <rest:response> | ||
Line 516: | Line 548: | ||
'Not that simple anymore' | 'Not that simple anymore' | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
=Error Handling= | =Error Handling= | ||
− | + | If an error is raised when RESTXQ code is parsed, compiled or evaluated, an HTTP response with the status code 500 is generated. | |
+ | |||
+ | By default, all server-side errors will be passed on to the client. This is particularly helpful during the development process. In a productive environment, however, it is advisable not to expose errors to the client. This can be realized via the {{Option|RESTXQERRORS}} option. If disabled, | ||
+ | |||
+ | * XQuery modules that cannot be parsed will be ignored and | ||
+ | * full error messages and stack traces will be suppressed and not included in the HTTP response. | ||
− | + | The full error information can still be looked up in the database logs. | |
− | + | ==Raise Errors== | |
− | With | + | With {{Function|Web|web:error}}, you can abort query evaluation, enforce a premature HTTP response and report errors back to the client: |
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/teapot") | %rest:path("/teapot") | ||
Line 534: | Line 571: | ||
web:error(418, "I'm a pretty teapot") | web:error(418, "I'm a pretty teapot") | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
− | + | In contrast to the standard <code>fn:error</code> function, a status code can be supplied, and the response body will only contain the specified error message and no stack trace. | |
==Catch XQuery Errors== | ==Catch XQuery Errors== | ||
− | XQuery runtime errors can be processed via ''error annotations''. | + | 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.2FCatch|try/catch]] construct: |
− | Error annotations have one or more arguments, which represent the error codes to be caught. | ||
− | The codes equal the names of the | ||
{| class="wikitable" | {| class="wikitable" | ||
Line 549: | Line 584: | ||
! Syntax | ! Syntax | ||
! Example | ! Example | ||
− | |- | + | |- valign="top" |
| 1 | | 1 | ||
| <code>prefix:name</code><br/><code>Q{uri}name</code> | | <code>prefix:name</code><br/><code>Q{uri}name</code> | ||
| <code>err:FORG0001</code><br/><code><nowiki>Q{http://www.w3.org/2005/xqt-errors}FORG0001</nowiki></code> | | <code>err:FORG0001</code><br/><code><nowiki>Q{http://www.w3.org/2005/xqt-errors}FORG0001</nowiki></code> | ||
− | |- | + | |- valign="top" |
| 2 | | 2 | ||
| <code>prefix:*</code><br/><code>Q{uri}*</code> | | <code>prefix:*</code><br/><code>Q{uri}*</code> | ||
| <code>err:*</code><br/><code><nowiki>Q{http://www.w3.org/2005/xqt-errors}*</nowiki></code> | | <code>err:*</code><br/><code><nowiki>Q{http://www.w3.org/2005/xqt-errors}*</nowiki></code> | ||
− | |- | + | |- valign="top" |
| 3 | | 3 | ||
| <code>*:name</code> | | <code>*:name</code> | ||
| <code>*:FORG0001</code> | | <code>*:FORG0001</code> | ||
− | |- | + | |- valign="top" |
| 4 | | 4 | ||
| <code>*</code> | | <code>*</code> | ||
Line 577: | Line 612: | ||
Errors may occur unexpectedly. However, they can also be triggered by a query, as demonstrated by the following example: | Errors may occur unexpectedly. However, they can also be triggered by a query, as demonstrated by the following example: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
declare | declare | ||
%rest:path("/check/{$user}") | %rest:path("/check/{$user}") | ||
Line 592: | Line 627: | ||
'User "' || $user || '" is unknown' | 'User "' || $user || '" is unknown' | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
==Catch HTTP Errors== | ==Catch HTTP Errors== | ||
Line 598: | Line 633: | ||
Errors that occur outside RESTXQ can be caught by adding {{Code|error-page}} elements with an error code and a target location to the {{Code|web.xml}} configuration file (find more details in the [http://www.eclipse.org/jetty/documentation/current/custom-error-pages.html Jetty Documentation]): | Errors that occur outside RESTXQ can be caught by adding {{Code|error-page}} elements with an error code and a target location to the {{Code|web.xml}} configuration file (find more details in the [http://www.eclipse.org/jetty/documentation/current/custom-error-pages.html Jetty Documentation]): | ||
− | < | + | <syntaxhighlight lang="xml"> |
<error-page> | <error-page> | ||
<error-code>404</error-code> | <error-code>404</error-code> | ||
<location>/error404</location> | <location>/error404</location> | ||
</error-page> | </error-page> | ||
− | </ | + | </syntaxhighlight> |
− | The target location may be another RESTXQ function. The | + | The target location may be another RESTXQ function. The {{Function|Request|request:attribute}} function can be used to request details on the caught error: |
− | < | + | <syntaxhighlight lang="xquery"> |
declare %rest:path("/error404") function page:error404() { | declare %rest:path("/error404") function page:error404() { | ||
"URL: " || request:attribute("javax.servlet.error.request_uri") || ", " || | "URL: " || request:attribute("javax.servlet.error.request_uri") || ", " || | ||
"Error message: " || request:attribute("javax.servlet.error.message") | "Error message: " || request:attribute("javax.servlet.error.message") | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
=User Authentication= | =User Authentication= | ||
Line 620: | Line 655: | ||
=Functions= | =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 [ | + | 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 [https://www.w3.org/Submission/wadl/ 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: | The following example returns the current host name: | ||
− | < | + | <syntaxhighlight lang="xquery"> |
import module namespace request = "http://exquery.org/ns/request"; | import module namespace request = "http://exquery.org/ns/request"; | ||
Line 630: | Line 665: | ||
'Remote host name: ' || request:remote-hostname() | 'Remote host name: ' || request:remote-hostname() | ||
}; | }; | ||
− | </ | + | </syntaxhighlight> |
=References= | =References= | ||
Line 639: | Line 674: | ||
* [http://www.adamretter.org.uk/papers/restful-xquery_january-2012.pdf RESTful XQuery, Standardised XQuery 3.0 Annotations for REST]. Paper, XMLPrague, 2012 | * [http://www.adamretter.org.uk/papers/restful-xquery_january-2012.pdf RESTful XQuery, Standardised XQuery 3.0 Annotations for REST]. Paper, XMLPrague, 2012 | ||
* [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 | ||
− | * [ | + | * [https://files.basex.org/publications/xmlprague/2013/Develop-RESTXQ-WebApps-with-BaseX.pdf Web Application Development]. Slides from XMLPrague 2013 |
Examples: | Examples: | ||
− | * Sample code combining XQuery and JavaScript: [ | + | * Sample code combining XQuery and JavaScript: [https://www.balisage.net/Proceedings/vol17/author-pkg/Galtman01/BalisageVol17-Galtman01.html Materials] and [https://www.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. | * [[DBA]]: The Database Administration interface, bundled with the full distributions of BaseX. | ||
=Changelog= | =Changelog= | ||
+ | |||
+ | ;Version 9.6 | ||
+ | * Updated: [[#Response|Response]]: {{Code|Server-Timing}} HTTP header. | ||
+ | |||
+ | ;Version 9.5 | ||
+ | * Updated: [[#Raise Errors|Raise Errors]]: Status code {{Code|400}} changed to {{Code|500}}, omit stack trace. | ||
;Version 9.3 | ;Version 9.3 | ||
− | |||
* Updated: [[#Custom Methods|Custom Methods]]: Better support for the OPTIONS and HEAD methods. | * Updated: [[#Custom Methods|Custom Methods]]: Better support for the OPTIONS and HEAD methods. | ||
* Updated: [[#Catch XQuery Errors|XQuery Errors]]: Suppress stack trace and error code in the HTTP response. | * Updated: [[#Catch XQuery Errors|XQuery Errors]]: Suppress stack trace and error code in the HTTP response. | ||
Line 655: | Line 695: | ||
;Version 9.2 | ;Version 9.2 | ||
− | |||
* Updated: Ignore XQuery modules that cannot be parsed | * Updated: Ignore XQuery modules that cannot be parsed | ||
;Version 9.0 | ;Version 9.0 | ||
− | |||
* Added: Support for server-side quality factors in the [[#Content Negotiation|<code>%rest:produces</code>]] annotation | * 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}} | * Updated: Status code {{Code|410}} was replaced with {{Code|460}} | ||
Line 665: | Line 703: | ||
;Version 8.4 | ;Version 8.4 | ||
− | |||
* Added: <code>%rest:single</code> annotation | * Added: <code>%rest:single</code> annotation | ||
;Version 8.1 | ;Version 8.1 | ||
− | |||
* Added: support for input-specific content-type parameters | * Added: support for input-specific content-type parameters | ||
* Added: <code>%input</code> annotations | * Added: <code>%input</code> annotations | ||
;Version 8.0 | ;Version 8.0 | ||
− | |||
* Added: Support for regular expresssions in the [[#Paths|Path Annotation]] | * Added: Support for regular expresssions in the [[#Paths|Path Annotation]] | ||
* Added: Evaluation of quality factors that are supplied in the [[#Content Negotiation|Accept header]] | * Added: Evaluation of quality factors that are supplied in the [[#Content Negotiation|Accept header]] | ||
;Version 7.9 | ;Version 7.9 | ||
− | |||
* Updated: [[#Catch XQuery Errors|XQuery Errors]], extended error annotations | * Updated: [[#Catch XQuery Errors|XQuery Errors]], extended error annotations | ||
* Added: {{Code|%rest:method}} | * Added: {{Code|%rest:method}} | ||
;Version 7.7 | ;Version 7.7 | ||
− | |||
* Added: [[#Error Handling|Error Handling]], [[#File Uploads|File Uploads]], [[#Multipart Types|Multipart Types]] | * Added: [[#Error Handling|Error Handling]], [[#File Uploads|File Uploads]], [[#Multipart Types|Multipart Types]] | ||
* Updated: RESTXQ function may now also be specified in main modules (suffix: {{Code|*.xq}}). | * Updated: RESTXQ function may now also be specified in main modules (suffix: {{Code|*.xq}}). | ||
* Updated: the RESTXQ prefix has been changed from {{Code|restxq}} to {{Code|rest}}. | * Updated: the RESTXQ prefix has been changed from {{Code|restxq}} to {{Code|rest}}. | ||
* Updated: parameters are implicitly cast to the type of the function argument | * Updated: parameters are implicitly cast to the type of the function argument | ||
− | * Updated: the RESTXQ root url has been changed to {{Code|http://localhost: | + | * Updated: the RESTXQ root url has been changed to {{Code|http://localhost:8080/}} |
;Version 7.5 | ;Version 7.5 | ||
− | |||
* Added: new XML elements {{Code|<rest:redirect/>}} and {{Code|<rest:forward/>}} | * Added: new XML elements {{Code|<rest:redirect/>}} and {{Code|<rest:forward/>}} |
Revision as of 10:30, 3 August 2022
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. It has been inspired by the Java JAX-RS API: It provides 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 - Better support for the OPTIONS and HEAD methods
Contents
Introduction
Preliminaries
The RESTXQ service is accessible via http://localhost:8080/
.
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 subdirectories will be traversed, and all XQuery files will be parsed for functions with RESTXQ annotations. Subdirectories 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 therest:init
function.
Examples
A first RESTXQ function is shown below:
<syntaxhighlight lang="xquery"> 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>
}; </syntaxhighlight>
If the URI http://localhost:8080/hello/World is accessed, the result will be:
<syntaxhighlight lang="xml"> <response>
<title>Hello World!</title>
</response> </syntaxhighlight>
The next function demonstrates a POST request:
<syntaxhighlight lang="xquery"> 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>
}; </syntaxhighlight>
If you post something (e.g. using curl or the embedded form at http://localhost:8080/)...
<syntaxhighlight lang="shell"> curl -i -X POST --data "message='CONTENT'" http://localhost:8080/form </syntaxhighlight>
...you will receive something similar to the following result:
<syntaxhighlight lang="shell"> HTTP/1.1 200 OK Content-Type: application/xml; charset=UTF-8 Content-Length: 107 Server: Jetty(8.1.11.v20130520) </syntaxhighlight>
<syntaxhighlight lang="xml"> <response type="form">
<message>'CONTENT'</message> <user-agent>curl/7.31.0</user-agent>
</response> </syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> declare %rest:path("/a/path/{$with}/some/{$variable}")
function page:test($with, $variable as xs:integer) { ... };
</syntaxhighlight>
Variables can be enhanced by regular expressions:
<syntaxhighlight lang="xquery"> (: 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($code) { ... };
(: Matches all other all paths starting with "app/" :) declare %rest:path("app/{$path=.+}")
function page:others($path) { ... };
</syntaxhighlight>
If multiple path candidates are found for the request, the one with more segments will be preferred.
Content Negotiation
Functions can be restricted to specific Media Types. The default type is */*
. Multiple types can either be specified by a single or by multiple annotations.
Consuming Data
A function will only be taken into consideration if the HTTP Content-Type
header of the request matches one of the given types:
<syntaxhighlight lang="xquery"> declare
%rest:POST("{$body}") %rest:path("/xml") %rest:consumes("application/xml") %rest:consumes("text/xml")
function page:xml($body) { $body }; </syntaxhighlight>
Producing Data
A function will only be chosen if the HTTP Accept
header of the request matches one of the given types:
<syntaxhighlight lang="xquery"> declare
%rest:path("/xml") %rest:produces("application/xml", "text/xml")
function page:xml() { <xml/> }; </syntaxhighlight>
Note that the annotations will not affect the type of the actual response: You will need to supply an additional %output:media-type
annotation or (if a single function may produce results of different types) generate an apt Custom Response.
Quality Factors
A client can supply quality factors to influence the server-side function selection process. If a client sends the following HTTP header with quality factors…
Accept: */*;q=0.5,text/html;q=1.0
…and if two RESTXQ functions exist for the addressed path with two different annotations for producing data…
<syntaxhighlight lang="xquery"> declare function %rest:produces("text/html") ... ... declare function %rest:produces("*/*") ... </syntaxhighlight>
…the first of these function will be chosen, as the quality factor for text/html
documents is highest.
As we cannot ensure that the client may supply quality factors, the selection process can also be controlled server-side. The qs
parameter can be attached server-side to the Media Type. If multiple functions are left in the selection process, the one with the highest quality factor will be favored:
<syntaxhighlight lang="xquery"> declare function %rest:produces("application/json;qs=1") ... ... declare function %rest:produces("*/*;qs=0.5") ... </syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> declare %rest:GET %rest:POST %rest:path("/post")
function page:post() { "This was a GET or POST request" };
</syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> declare %rest:PUT("{$body}") %rest:path("/put")
function page:put($body) { "Request body: " || $body };
</syntaxhighlight>
Custom Methods
Custom HTTP methods can be specified with the %rest:method
annotation. An optional body variable can be supplied as second argument:
<syntaxhighlight lang="xquery"> declare
%rest:path("binary-size") %rest:method("SIZE", "{$body}")
function page:patch(
$body as xs:base64Binary
) {
"Request method: " || request:method(), "Size of body: " || bin:length($body)
}; </syntaxhighlight>
If an OPTIONS request is received, and if no function is defined, an automatic response will be generated, which includes an Allow
header with all supported methods.
If a HEAD request is received, and if no function is defined, the corresponding GET function will be processed, but the response body will be discarded.
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:
<syntaxhighlight lang="xquery"> 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)
}; </syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> declare
%rest:path("/multipart") %rest:POST("{$data}") %rest:consumes("multipart/mixed") (: optional :)
function page:multipart($data as item()*) {
"Number of items: " || count($data)
}; </syntaxhighlight>
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):
<syntaxhighlight lang="xquery"> 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) }"/>
}; </syntaxhighlight>
HTML Form Fields
Form parameters are specified the same way as query parameters:
<syntaxhighlight lang="xquery"> %rest:form-param("city", "{$city}", "no-city-specified") </syntaxhighlight>
The values are the result of HTML forms submitted with the (default) content type application/x-www-form-urlencoded
:
<syntaxhighlight lang="xml"> <form action="/process" method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="city"/> <input type="submit"/>
</form> </syntaxhighlight>
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):
<syntaxhighlight lang="xml"> <form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="files" multiple="multiple"/> <input type="submit"/>
</form> </syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> 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) }"/> )
}; </syntaxhighlight>
HTTP Headers
Header parameters are specified the same way as query parameters:
<syntaxhighlight lang="xquery"> %rest:header-param("User-Agent", "{$user-agent}") %rest:header-param("Referer", "{$referer}", "none") </syntaxhighlight>
Cookies
Cookie parameters are specified the same way as query parameters:
<syntaxhighlight lang="xquery"> %rest:cookie-param("username", "{$user}") %rest:cookie-param("authentication", "{$auth}", "no_auth") </syntaxhighlight>
Query Execution
In many web search scenarios, user input from browser forms is processed and search results are returned. Such operations can be made more interactive by sending a new search request to the server with each key click. However, this may lead to many parallel server-side 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 run at the same time and for the same client. If the same function will be called for the second time, a currently executed query will be stopped, and the HTTP error code 460
will be returned instead:
<syntaxhighlight lang="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) {
- {
for $result in db:get('large-db')//*[text() = $term]
return
- { $result } }
}; </syntaxhighlight>
By adding a string value to with the annotation, functions can be bundled together, and a running query can be canceled by calling another one that has the same annotation value. 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>.
<syntaxhighlight lang="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>
}; </syntaxhighlight>
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 abort a query if it is currently updating the database or performing 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”).
A Server-Timing
HTTP header is attached to each response. It indicates how much time was spent for parsing, compiling, evaluating and serializing the query. The last value will not necessarily reflect the full time for serializing the result, as the header is generated before the result is sent to the client. Server-side serialization can be enforced by annotating a function with the %rest:single
annotation.
Custom Response
Custom responses can be generated in XQuery by returning an rest:response
element, an http:response
child node that matches the syntax of the EXPath HTTP Client Module specification, and optional child nodes that will be serialized as usual. A function that yields a response on an unknown resource may look as follows:
<syntaxhighlight lang="xquery"> declare %output:method("text") %rest:path("") function page:error404() {
<rest:response> <http:response status="404"> <http:header name="Content-Language" value="en"/> <http:header name="Content-Type" value="text/plain; charset=utf-8"/> </http:response> </rest:response>, "The requested resource is not available."
}; </syntaxhighlight>
Forwards and Redirects
Redirects
The server can invite the client (e.g., the web browser) to make a second request to another URL by sending a 302 response:
<syntaxhighlight lang="xml"> <rest:response>
<http:response status="302"> <http:header name="Location" value="new-location"/> </http:response>
</rest:response> </syntaxhighlight>
The convenience function web:redirect
can be called to create such a response.
In the XQuery context, redirects are particularly helpful if Updates are performed. An updating request may send a redirect to a second function that generates a success message, or evaluates an updated database:
<syntaxhighlight lang="xquery"> declare %updating %rest:path('/app/init') function local:create() {
db:create('app', <root/>, 'root.xml'), db:output(web:redirect('/app/ok'))
};
declare %rest:path('/app/ok') function local:ok() {
'Stored documents: ' || count(db:get('app'))
}; </syntaxhighlight>
Forwards
A server-side redirect is called forwarding. It reduces traffic among client and server, and the forwarding will not change the URL seen from the client’s perspective:
<syntaxhighlight lang="xml"> <rest:forward>new-location</rest:forward> </syntaxhighlight>
The fragment can also be created with the convenience function web:forward
.
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:
<syntaxhighlight lang="xquery"> declare option output:media-type 'text/plain';
declare %rest:path("version1") function page:version1() {
'Keep it simple, stupid'
}; </syntaxhighlight>
Annotations
Global serialization parameters can be overwritten via %output
annotations. The following example serializes XML nodes as JSON, using the JsonML format:
<syntaxhighlight lang="xquery"> declare
%rest:path("cities") %output:method("json") %output:json("format=jsonml")
function page:cities() {
element cities { db:get('factbook')//city/name }
}; </syntaxhighlight>
The next function, when called, generates XHTML headers, and text/html
will be set as content type:
<syntaxhighlight lang="xquery"> 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>
}; </syntaxhighlight>
Response Element
Serialization parameters can also be specified in a REST reponse element in a query. Serialization parameters will be overwritten:
<syntaxhighlight lang="xquery"> 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'
}; </syntaxhighlight>
Error Handling
If an error is raised when RESTXQ code is parsed, compiled or evaluated, an HTTP response with the status code 500 is generated.
By default, all server-side errors will be passed on to the client. This is particularly helpful during the development process. In a productive environment, however, it is advisable not to expose errors to the client. This can be realized via the RESTXQERRORS
option. If disabled,
- XQuery modules that cannot be parsed will be ignored and
- full error messages and stack traces will be suppressed and not included in the HTTP response.
The full error information can still be looked up in the database logs.
Raise Errors
With web:error
, you can abort query evaluation, enforce a premature HTTP response and report errors back to the client:
<syntaxhighlight lang="xquery"> declare
%rest:path("/teapot")
function page:teapot() {
web:error(418, "I'm a pretty teapot")
}; </syntaxhighlight>
In contrast to the standard fn:error
function, a status code can be supplied, and the response body will only contain the specified error message and no stack trace.
Catch 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 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:
<syntaxhighlight lang="xquery"> 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'
}; </syntaxhighlight>
Catch 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):
<syntaxhighlight lang="xml"> <error-page>
<error-code>404</error-code> <location>/error404</location>
</error-page> </syntaxhighlight>
The target location may be another RESTXQ function. The request:attribute
function can be used to request details on the caught error:
<syntaxhighlight lang="xquery"> declare %rest:path("/error404") function page:error404() {
"URL: " || request:attribute("javax.servlet.error.request_uri") || ", " || "Error message: " || request:attribute("javax.servlet.error.message")
}; </syntaxhighlight>
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:
<syntaxhighlight lang="xquery"> import module namespace request = "http://exquery.org/ns/request";
declare %rest:path("/host-name") function page:host() {
'Remote host name: ' || request:remote-hostname()
}; </syntaxhighlight>
References
Documentation:
- RESTXQ Specification, First Draft
- RESTful XQuery, Standardised XQuery 3.0 Annotations for REST. Paper, XMLPrague, 2012
- RESTXQ. Slides, MarkLogic User Group London, 2012
- Web Application Development. Slides from XMLPrague 2013
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.6
- Updated: Response:
Server-Timing
HTTP header.
- Version 9.5
- Updated: Raise Errors: Status code
400
changed to500
, omit stack trace.
- Version 9.3
- Updated: Custom Methods: Better support for the OPTIONS and HEAD methods.
- Updated: XQuery Errors: Suppress stack trace and error code in the HTTP response.
- Removed:
rest:redirect
element (web:redirect
can be used instead)
- Version 9.2
- Updated: Ignore XQuery modules that cannot be parsed
- Version 9.0
- Added: Support for server-side quality factors in the
%rest:produces
annotation - Updated: Status code
410
was replaced with460
- 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
- Added: Support for regular expresssions in the Path Annotation
- Added: Evaluation of quality factors that are supplied in the Accept header
- 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
torest
. - Updated: parameters are implicitly cast to the type of the function argument
- Updated: the RESTXQ root url has been changed to
http://localhost:8080/
- Version 7.5
- Added: new XML elements
<rest:redirect/>
and<rest:forward/>