You can extend the REST Client API in a variety of ways, including resource service extensions and evaluation of ad-hoc queries and server-side modules. This chapter covers the following topics:
The REST Client API offers several ways to extend and customize the available services with user-defined code that is either pre-installed on MarkLogic Server or supplied at request time. The following extension points are available:
Use the resource service extension capability to make new REST service endpoints available through a REST Client API instance. To make a resource service extension available, follow these steps, which are discussed in detail in this chapter.
/resources/{extensionName}
service endpoint. For details see Using a Resource Service Extension.The /resources
service also supports dynamic discovery of installed extensions. When you install an extension, you can specify extension metadata, including method parameter name and type information to make it easier to use dynamically discovered extensions.
If your extension depends on other modules or assets, you can install them in the modules database using the /ext service. For details, see Managing Dependent Libraries and Other Assets.
JavaScript modules are not available from REST extensions because resource service extensions are imported dynamically by the REST API. If your work requires the use of JavaScript library modules, the preferred method is to use Data Service endpoints rather than resource service extensions. See Creating Data Services and Developer Actions in Node.js for further information.
This section covers the following topics related to authoring a Resource Service Extension using server-side JavaScript:
Resource service extensions, transforms, row mappers and reducers, and other hooks on the REST API cannot be implemented as JavaScript MJS modules.
Keep the following guidelines in mind when authoring a resource extension using JavaScript:
put
and post
that accept an input document receive an immutable in-memory representation. MarkLogic Server builtin functions that operate on in-database documents, such as xdmp.nodeReplace
, cannot be applied directly to the input document. To modify the structure of the input document, you must create a mutable instance of the document by calling toObject
on it.input
parameter passed to your implementation is a document node. When the client sends multiple documents, the input parameter is a Sequence
over document nodes. You should be prepared to handle either.Sequence
over document nodes or JavaScript objects. Use Sequence.from
to create a Sequence
from an array.xdmp.invokeFunction
, xdmp.eval
, and xdmp.invoke
to perform operations in a different transaction.fn.error
, not through an Error object or other mechanism. For details, see Reporting Errors.Many of these points are illustrated by Example: JavaScript Resource Service Extension.
To create a resource service extension using server-side JavaScript, implement a module based on the template below. The module exports and function signatures must be exactly as they appear in the template, with the following exceptions:
Base your extension implementation on the following template.
function get(context, params) { // return zero or more document nodes }; function post(context, params, input) { // return zero or more document nodes }; function put(context, params, input) { // return at most one document node }; function deleteFunction(context, params) { // return at most one document node }; exports.GET = get; exports.POST = post; exports.PUT = put; exports.DELETE = deleteFunction;
The following template summarizes the meaning of the function parameters passed to your implementation functions:
Parameter | Description |
---|---|
context |
An object containing service request context information such as input document types and URIs, and output types accepted by the caller. For details, see Context Object Properties. |
params |
An object containing extension-specific parameter values supplied by the client, if any. The object contains a property for each resource parameter included in the request to the extension service. Properties specified more than once by the caller have array value. For example: If the request is You can optionally define parameter name and type metadata when installing the extension. For details, see Installing a Resource Service Extension. |
input |
The data from the request body. For a request with only part, input is a single document node. For a multipart request, input is a Sequence over document nodes. |
The input
parameter can be a single document node or a Sequence
, depending on whether the request body contains a single part or multiple parts. You can normalize the input by converting the single document to a Sequence
, if desired. For example:
function normalizeInput(item) { return (item instanceof Sequence) ? item // many : Sequence.from([item]); // one };
The input
document(s) are immutable. Use toObject
to create a mutable copy. For example, assuming input a single JavaScript object created from passing JSON data in the request body, the following code enables you to modify the input object:
const modifiableDoc = input.toObject(); modifiableDoc.newProperty = someValue;
Your methods can accept input arguments in addition to the input documents in the request body. Arguments are passed to your methods through the params
object. For an example, see the get
implementation in Example: JavaScript Resource Service Extension.
Your extension can return JavaScript objects and XML, JSON, text, or binary document nodes. A JavaScript object is serialized as JSON before returning it to the client. Set context.outputTypes
in the $context
map to the MIME type appropriate for the returned content. Except for the JavaScript to JSON conversion, the content is returned as-is, with the MIME type you specify.
To return multiple documents, create a Sequence
over your JavaScript objects or document nodes. Use xdmp.arrayValues
or Sequence.from
to create a Sequence
from an array. For example, the following code snippet results in a multi-part response containing 2 parts, with each part containing a JSON document.
context.outputTypes = [ 'application/json', 'application/json' ]; return Sequence.from([{one: 1}, {two:2}];
If you want to report errors back to the client application, you must use the fn.error
function, with a very specific calling convention. For details, see Reporting Errors.
This section describes how to report errors back to the client application. Extensions and transforms use the same mechanism to report errors to the calling application. If you attempt to return an error in any other fashion, such as through an Error
object, the client just receives a 500 Internal Error response.
To report an error from your implementation, call the fn.error
function with the RESTAPI-SRVEXERR
error code, and provide the HTTP response code and additional error information in the 3rd parameter to fn.error
. Calling fn.error
interrupts execution of your code. Control does not return.
Best practice is to use RESTAPI-SRVEXERR
. If you report any other error or raise any other exception, it is reported to the calling application as a 500 Server Internal Error.
The 3rd parameter to fn:error
should be a Sequence
containing a sequence of the form (
status-code,
status-message,
response-payload)
. You can use xdmp.arrayValues
or Sequence.from
to construct a sequence from a JavaScript array.
For example, if you wrap the fn.error
call in a function, then you can call it as shown below:
function returnErrToClient(statusCode, statusMsg, body) { fn.error(null, 'RESTAPI-SRVEXERR', Sequence.from([statusCode, statusMsg, body])); // unreachable - control does not return from fn.error. }; // ... use the func ... returnErrToClient(400, 'Bad Request', 'additional info'); // unreachable
If you include an error response payload, it should either be plain text or compatible with the error format configured for the REST instance. You cannot override the default error payload format.
By default, your resource service extension does not need to specify the HTTP response status code. The default behavior is as follows:
To return a status code different from the default, set the numeric code and message in the context
object. Use the outputStatus
property with [
code,
message]
ts value.
Do not use output-status
to report an error to the client. Instead, use fn.error
. For details, see Reporting Errors.
The following example returns a 201 Created
status to the client instead of 200 OK
:
function put(context, params, input) { ... context.outputStatus = [201, 'Created']; ... };
The REST API sets headers such as Content-type and Content-Length for you when constructing the response from a resource extension request. You can specify additional headers by setting context.outputHeaders
.
Do not use context.outputHeaders
to set the response Content-type header. Use context.outputTypes
to specify the response MIME type(s) instead.
If you use context.outputHeaders
, the value should be an object of the following form:
{
header-name:
header-value,
header-name:
header-value, ...}
The following example adds headers named X-MyHeader1 and X-MyHeader2 to the response by setting context.outputHeaders
.
function put(context, params, input) { ... context.outputHeaders = {"X-MyHeader1" : "header1 value", "X-MyHeader2" : "header2 value"}; ... };
The resulting response headers are similar to the following:
HTTP/1.1 200 OK X-MyHeader1: header1 value X-MyHeader2: header2 value Server: MarkLogic Content-Type: text/plain; charset=UTF-8 Content-Length: 4 Connection: Keep-Alive Keep-Alive: timeout=5
The context
parameter to resource service extension functions is an object that can contain the following properties. Unless otherwise noted, all the property values are arrays. Not all properties are always present. For example, the inputTypes
property is not meaningful for a GET request.
Key | Description |
---|---|
inputTypes |
The MIME type of the input document(s). There is an array element for each item in the input parameter passed to your method implementation. |
acceptTypes |
The MIME types accepted by the requestor. |
outputHeaders |
Extra headers to include in the response to the client. Optional. Specify headers as a JavaScript object of the form: {header1: value1, header2: value2, ...} . For details, see Setting Additional Response Headers. |
outputStatus |
The HTTP response status code and message to return to the client. Optional. For example, to return a 201 Created status, set outputStatus to [201, "Created"] . For details, see Setting the Response Status Code. |
outputTypes |
The output document MIME type(s). The service must set outputTypes for all output documents. |
You can create a resource extension to probe context
. For example, the following extension probes the context
and params
parameters of PUT extension requests:
function put(context, params, input) { context.outputTypes = ["application/json"]; return { context: context, params: params }; }; exports.PUT = put;
If the above example is installed as an extension named dumper, then you can probe the context as follows. There are two items in context.inputTypes
because the multipart body contained one JSON part and one XML part.
curl --anyauth --user user:password -X PUT -i \ -H "Content-type: multipart/mixed; boundary=BOUNDARY" -H "Accept: application/json" \ --data-binary @./my-multipart-body \ http://localhost:8000/LATEST/resources/dumper?rs:myparam=foo HTTP/1.1 200 OK Content-type: application/json; charset=UTF-8 Server: MarkLogic Content-Length: 176 Connection: Keep-Alive Keep-Alive: timeout=5 {"context": { "acceptTypes": [], "inputBoundary": "BOUNDARY", "inputTypes": [ "application/json", "application/xml" ], "outputTypes": [ "application/json" ] }, "params": { "myparam": "foo" } }
For a complete example of a resource service extension, see Example: JavaScript Resource Service Extension.
In a JavaScript extension, you cannot control the transaction mode in which your get
, put
, post
, or delete
function runs. Transaction mode is controlled by the REST API App Server.
GET
- query for a single statement transaction, update for a multi-statement transaction; that is, if your request includes a transaction id, the function runs in update mode.PUT
- updatePOST
- query for a single statement transaction, update for a multi-statement transaction; that is, if your request includes a transaction id, the function runs in update mode.DELETE
- updateIf you need to execute code in a transaction context different from the default, use one of the following options:
xdmp.invokeFunction
, xdmp.eval
, or xdmp.invoke
. For details, see the JavaScript Reference Guide.To learn more about transaction mode and the MarkLogic transaction model, see Understanding Transactions in MarkLogic Server in the Application Developer's Guide.
This section covers the following topics related to authoring a Resource Service Extension using XQuery:
Keep the following guidelines in mind when authoring a resource extension:
put
and post
that accept an input document receive an in-memory representation. MarkLogic Server builtin functions that operate on in-database documents, such as xdmp:node-replace
, cannot be applied directly to the input document. To modify the structure of the input document, perform the modifications during construction of a copy of the input content.xdmp:eval
and xdmp:invoke
to perform operations in a different transaction.To create a resource service extension, implement an XQuery library module based on the template below. The module namespace, function names, and function signatures must be exactly as they appear in the template, with the following exceptions:
Base your extension implementation on the following template. You must use the function local names shown (get
, put
, etc.).
xquery version "1.0-ml"; module namespace yourNSPrefix = "http://marklogic.com/rest-api/resource/extensionName"; declare function yourNSPrefix:get( $context as map:map, $params as map:map ) as document-node()* declare function yourNSPrefix:put( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()? declare function yourNSPrefix:post( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()* declare function yourNSPrefix:delete( $context as map:map, $params as map:map ) as document-node()?
The map:map
type is a MarkLogic Server extended XQuery type representing a key-value map. For details, see Using the map Functions to Create Name-Value Maps in the Application Developer's Guide.
Your methods can accept input arguments. You can define the argument names and types when installing your extension; for details see Installing a Resource Service Extension. Arguments are passed to your methods through the $params
map. See the get
implementation in Example: XQuery Resource Service Extension.
The following template summarizes the meaning of the function parameters:
Parameter | Description |
---|---|
$context |
Service request context information such as input document type and URI, and output types accepted by the caller. For details, see Context Map Keys. |
$params |
Extension-specific parameters. The map contains a key for each parameter on a request to the extension service. You can optionally define parameters when installing the extension. For details, see Installing a Resource Service Extension. |
$input |
The input document to which to apply the transformation. |
Your extension can return XML, JSON, text, or binary documents. Set output-type
in the $context
map to the MIME type appropriate for the returned content. The content is returned as-is, with the MIME type you specify.
If your extension function returns data, it must be in the form document nodes. In addition to getting documents from the database or a builtin or library function, you can create documents using node constructors. The following expression demonstrate various ways of constructing XML and JSON document nodes:
(: JSON, from a serialized string representation:) xdmp:unquote('{"from": "string"}') (: JSON, from a node constructor :) document { object-node {"from": "constructor"} } (: XML from a constructor :) document { element from { text {"constructor"} } } (: XML from a document constructor and XML :) document { <from>XML literal</from> },
For details, see XML and XQuery in the XQuery and XSLT Reference Guide and Working With JSON in XQuery in the Application Developer's Guide.
Extensions and transforms use the same mechanism to report errors to the calling application: Use fn:error
to report a RESTAPI-SRVEXERR
error, and provide additional information in the $data
parameter of fn:error
. You can control the response status code, status message, and provide an additional error reporting response payload. For example:
fn:error((),"RESTAPI-SRVEXERR", (415, "Unsupported Input Type", "Only application/xml is supported"))
The 3rd parameter to fn:error
should be a sequence of the form ("
status-code", "
status-message", "
response-payload")
. That is, when using fn:error
to raise RESTAPI-SRVEXERR
, the $data
parameter to fn:error
is a sequence with the following members, all optional:
Best practice is to use RESTAPI-SRVEXERR
. If you report any other error or raise any other exception, it is reported to the calling application as a 500 Server Internal Error.
For example, this resource extension function raises RESTAPI-SRVEXERR
if the input content type is not as expected:
declare function example:put( $context as map:map, $params as map:map, $input as document-node() ) as document-node() { (: get 'input-types' to use in content negotiation :) let $input-types := map:get($context,"input-types") let $negotiate := if ($input-types = "application/xml") then () (: process, insert/update :) else fn:error((),"RESTAPI-SRVEXERR", ("415","Raven","nevermore")) return document { "Done"} (: may return a document node :) };
If a PUT request is made to the extension with an unexpected content type, the fn:error
call causes the request to fail with a status 415 and to include the additional error description in the response body:
HTTP/1.1 415 Raven Content-type: application/json Server: MarkLogic Set-Cookie: SessionID=714070bdf4076536; path=/ Content-Length: 62 Connection: close { "message": "js-example: response with invalid 400 status", "statusCode": 415, "body": { "errorResponse": { "statusCode": 415, "status": "Raven", "messageCode": "RESTAPI-SRVEXERR", "message": "nevermore" } } }
By default, your resource service extension does not need to specify the HTTP response status code. The default behavior is as follows:
To return a different status code other than the default, set the numeric code and message in $context
. Use the key output-status
with a (
code,
message)
pair as its value.
Do not use output-status
to report an error to the client. Instead, use fn:error
. For details, see Reporting Errors.
The following example returns a 201 Created
status to the client instead of 200 OK
:
declare function example:put( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()? { ... map:put($context, "output-status", (201, "Created")); };
The REST API sets headers such as Content-type and Content-Length for you when constructing the response from a resource extension request. You can specify additional headers by setting output-headers
in the $context
map.
Do not use output-headers
to set Content-type. Use output-type
to specify MIME type(s) instead.
The following example adds headers named X-My-Header1 and X-My-Header2 to the response by setting the output-headers to a sequence of the form (header-name, header-value, header-name, header-value, ...)
declare function example:put( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()? { ... map:put($context, "output-headers", ("X-MyHeader1", "header1 value", "X-MyHeader2", "header2 value")) 2")); };
You can also set output-headers
using a map with the header names as keys. The following code snippet has the same effect on the response headers as the previous example.
let $headers := map:map() ... (map:put($headers, "X-MyHeader1", "header1 value"), map:put($headers, "X-MyHeader2", "header2 value"), map:put($context, "output-headers", $headers))
In both cases, the resulting response headers are similar to the following:
HTTP/1.1 200 OK X-MyHeader1: header1 value X-MyHeader2: header2 value Server: MarkLogic Content-Type: text/plain; charset=UTF-8 Content-Length: 4 Connection: Keep-Alive Keep-Alive: timeout=5
The $context
parameter to resource service extension functions can contain the following keys. Not all keys are always present. For example, there is no input-type on a GET request.
Key | Description |
---|---|
input-types |
The MIME type of the input document(s). If there are multiple input documents, the value is a sequence of strings, one for the MIME type of each input document. |
accept-types |
For a GET, the MIME types accepted by the requestor. If there are multiple MIME types, the value is a sequence of strings. |
output-headers |
Response headers to include extra headers in the response to the client. Optional. Specify headers as a sequence of alternating header and value pairs: (header1, value1, header2, value2, ...) , or as a map . For details, see Setting Additional Response Headers. |
output-status |
The HTTP response status code and message to return to the client. Optional. If unset, 200 OK is returned, unless an error is raised. For example, to return a 201 Created status, set output-status to (201, "Created") . For details, see Setting the Response Status Code. |
output-type |
The output document MIME type. The service must set output-type for all output documents. |
For a complete example of a resource service extension, see Example: XQuery Resource Service Extension.
This section discusses the default transaction mode under which the implementation of a resource service extension is evaluated, and how you can override the default behavior. This discussion assumes you are familiar with the MarkLogic Server transaction mode concept; for details, see Transaction Mode in Application Developer's Guide.
Choosing the appropriate transaction mode can improve the resource utilization of your implementation. For example, the function implementing your POST method runs in update transaction mode by default. This means it acquires exclusive locks on documents, as described in Update Transactions: Readers/Writers Locks in Application Developer's Guide. If you know that your POST implementation is actually a read-only operation, you can override the default transaction mode so that the function is evaluated in query transaction mode and does not acquire exclusive locks.
The XQuery functions in a resource service extension implementation are evaluated using the following default transaction modes:
get
- query for a single statement transaction, update for a multi-statement transaction; that is, if your request includes a transaction id, the function runs in update mode.put
- updatepost
- query for a single statement transaction, update for a multi-statement transaction; that is, if your request includes a transaction id, the function runs in update mode.delete
- updateTo override the default behavior, include a transaction mode annotation in the declaration of your function:
declare %rapi:transaction-mode("the-mode") function yourNS:method(...)
The value for %rapi:transaction-mode
can be either update or query.
For example, to specify query transaction mode for your put
function, use the following declaration:
declare %rapi:transaction-mode("query") function example:put( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()? {...};
You install a resource service extension module using the REST Client API. Any dependent user-defined modules must be installed separately before installing your extension; for details, see Managing Dependent Libraries and Other Assets.
To install or update an extension, send a PUT request to /config/resources
with a URL of the following form, with the extension code in the request body:
http://host:port/version/config/resources/extensionName
Where extensionName is the name used when administering and accessing the extension.
For an XQuery extension, extensionName must match the name in the extension module namespace declaration. For example:
xquery version "1.0-ml"; module namespace yourNSPrefix = "http://marklogic.com/rest-api/resource/extensionName"; ...
Set the Content-type of the request body to application/xquery
or application/vnd.marklogic-javascript
.
MarkLogic Server parses the extension implementation to determine which HTTP methods it supports. Only functions in the implementation that conform to the expected interface become available for use. For interface details, see The Resource Extension Interface.
If the extension service expects parameters, you can optionally declare the parameters using request parameters when installing the extension. This information is metadata that can be returned by a GET request to /config/resources
. It is not used to check parameters on requests to the extension.
To include parameter metadata, declare each supported method and parameter using request parameters of the following form:
method=methodNamemethodName:paramName=paramTypeAndCardinality
Where methodName is one of the extension functions: get
, put
, post
, or delete
; paramName is the name of the parameter; and paramTypeAndCardinality reflects the type and cardinality the parameter would have in an XQuery function declaration.
For example, xs:string?
means a string type parameter which may appear 0 or 1 time. The following URL illustrates an extension named example with an optional string parameter named reviewer to the put
method and a required string parameter genre to the get
method:
'http://host:port/version/config/resources/example?method=put&put:reviewer=string\?&method=get&get:genre=xs:string'
You can also specify extension metadata for provider, version, title, and description as request parameters during installation. For example:
http://host:port/version/config/resources/example?method=get&get:genre=string&provider=MarkLogic%20Corporation
For a complete example, see one of the following topics:
If your module depends upon other user-defined modules, you must install them manually in the modules database of the REST API instance or in the Modules directory of your MarkLogic Server installation. For details, see Using a Resource Service Extension.
To access extension services, send a request of the appropriate type (GET, PUT, POST, or DELETE) to a URL of the following form:
http://host:port/version/resources/extensionName
Where extensionName is the name under which the extension is installed.
If the service expects parameters, supply the parameter names and values as request parameters, prefixing each parameter name with rs:
. For example, if the example service for GET expects a parameter named genre, the request URL would be similar to the following:
http://localhost:8000/LATEST/resources/example?rs:genre=literature
The names, types, and cardinality of method parameters are resource specific. You can send a GET request to /config/resources
to dynamically discover resources extension interfaces. For details, see Discovering Resource Service Extensions.
This section demonstrates the implementation, installation and invocation of a resource service extension implemented in Server-Side JavaScript.
This example extension implements all the HTTP methods supported by the resource extension interface. It demonstrates the use of input arguments, setting response content type, response status codes, additional response headers, and reporting errors to the calling application. The implementation follows the template described in Creating a JavaScript Resource Service Extension.
The methods implemented by this extension do the following:
Helper functions are used to abstract away operations such as reporting errors to the client and normalizing the type of the input document set to Sequence
. The later is demonstrated as a convenience since the PUT method might receive either a single document node or a Sequence
, depending on whether the request body has one or multiple parts.
The following code implements the contract described above. See the comments in the code for details. For usage, see Using the Example Extension.
// GET // // This function returns a document node corresponding to each // user-defined parameter in order to demonstrate the following // aspects of implementing REST extensions: // - Returning multiple documents // - Overriding the default response code // - Setting additional response headers // function get(context, params) { const results = []; context.outputTypes = []; for (const pname in params) { if (params.hasOwnProperty(pname)) { results.push({name: pname, value: params[pname]}); context.outputTypes.push('application/json'); } } // Return a successful response status other than the default // using an array of the form [statusCode, statusMessage]. // Do NOT use this to return an error response. context.outputStatus = [201, 'Yay']; // Set additional response headers using an object context.outputHeaders = {'X-My-Header1' : 42, 'X-My-Header2': 'h2val' }; // Return a Sequence to return multiple documents return Sequence.from(results); }; // PUT // // The client should pass in one or more documents, and for each // document supplied, a value for the 'basename' request parameter. // The function inserts the input documents into the database only // if the input type is JSON or XML. Input JSON documents have a // property added to them prior to insertion. // // Take note of the following aspects of this function: // - The 'input' param might be a document node or a Sequence // over document nodes. You can normalize the values so your // code can always assume a Sequence. // - The value of a caller-supplied parameter (basename, in this case) // might be a single value or an array. // - context.inputTypes is always an array // - How to return an error report to the client // function put(context, params, input) { // normalize the inputs so we don't care whether we have 1 or many const docs = normalizeInput(input); const basenames = params.basename instanceof Array ? params.basename: [ params.basename ]; // Validate inputs. if (docs.count > basenames.length) { returnErrToClient(400, 'Bad Request', 'Insufficient number of uri basenames. Expected ' + docs.count + ' got ' + basenames.length + '.'); // unreachable - control does not return from fn.error } // Do something with the input documents let i = 0; const uris = []; for (const doc of docs) { uris.push( doSomething( doc, context.inputTypes[i], basenames[i++] )); } // Set the response body MIME type and return the response data. context.outputTypes = ['application/json']; return { written: uris }; }; function post(context, params, input) { xdmp.log('POST invoked'); return null; }; function deleteFunction(context, params) { xdmp.log('POST invoked'); return null; }; // PUT helper func that demonstrates working with input documents. // // It inserts a (nonsense) property into the incoming document if // it is a JSON document and simply inserts the document unchanged // if it is an XML document. Other doc types are skipped. // // Input documents are imutable, so you must call toObject() // to create a mutable copy if you want to make a change. // // The property added to the JSON input is set to the current time // just so that you can easily observe it changing on each invocation. // function doSomething(doc, docType, basename) { let uri = '/extensions/' + basename; if (docType == 'application/json') { // create a mutable version of the doc so we can modify it const mutableDoc = doc.toObject(); uri += '.json'; // add a JSON property to the input content mutableDoc.written = fn.currentTime(); xdmp.documentInsert(uri, mutableDoc); return uri; } else if (docType == 'application/xml') { // pass thru an XML doc unchanged uri += '.xml'; xdmp.documentInsert(uri, doc); return uri; } else { return '(skipped)'; } }; // Helper function that demonstrates how to normalize inputs // that may or may not be multi-valued, such as the 'input' // param to your methods. // // In cases where you might receive either a single value // or a Sequence, depending on the request context, // you can normalize the data type by creating a Sequence // from the single value. function normalizeInput(item) { return (item instanceof Sequence) ? item // many : Sequence.from([item]); // one }; // Helper function that demonstrates how to return an error response // to the client. // You MUST use fn.error in exactly this way to return an error to the // client. Raising exceptions or calling fn.error in another manner // returns a 500 (Internal Server Error) response to the client. function returnErrToClient(statusCode, statusMsg, body) { fn.error(null, 'RESTAPI-SRVEXERR', Sequence.from([statusCode, statusMsg, body])); // unreachable - control does not return from fn.error. }; // Include an export for each method supported by your extension. exports.GET = get; exports.POST = post; exports.PUT = put; exports.DELETE = deleteFunction;
This section installs the example resource extension under the name js-example
.
Follow these steps to install the example extension in the modules database associated with your REST API instance:
example.sjs
.# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X PUT -i \ -H "Content-type: application/vnd.marklogic-javascript" \ --data-binary @./example.sjs \ http://localhost:8000/LATEST/config/resources/js-example
For usage examples, see Using the Example Extension.
Optionally, you can include metadata during installation so that a GET request to /config/resources
returns more detail. For example:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X PUT \ -H "Content-type: application/vnd.marklogic-javascript" \ -d@"./example.sjs" \ "http://localhost:8000/LATEST/config/resources/js-example?version=1.0&provider=marklogic&description=A simple resource example&method=get&method=post&method=put&method=delete&get:arg1=[string]"
The table below breaks down the metadata request parameters so it is easier to see what metadata is included.
This section explores the behavior of the example extension and demonstrates how to exercise some of the methods.
The GET method of the example extension returns a JSON document for each parameter value passed in. Each document contains the name and value of a parameter. The GET method also demonstrates overriding the default response status and adding response headers.
function get(context, params) { ... for (const pname in params) { if (params.hasOwnProperty(pname)) { results.push({name: pname, value: params[pname]}); context.outputTypes.push('application/json'); } } context.outputStatus = [201, 'Yay']; context.outputHeaders = {'X-My-Header1' : 42, 'X-My-Header2': 'h2val' }; return Sequence.from(results); };
The following command exercises the GET method. The response contains 2 parts, one for each unique input parameter name. Notice that the value of the parameter a is an array, reflecting its inclusion twice in the request URL.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X GET \ -H "Accept: multipart/mixed; boundary=BOUNDARY" \ 'http://localhost:8000/LATEST/resources/js-example?rs:a=1&rs:b=2&rs:a=3'HTTP/1.1 201 Yay Content-type: multipart/mixed; boundary=BOUNDARY X-My-Header1: 42 X-My-Header2: h2val Server: MarkLogic Content-Length: 207 Connection: Keep-Alive Keep-Alive: timeout=5 --BOUNDARY Content-Type: application/json Content-Length: 25 {"name":"b", "value":"2"} --BOUNDARY Content-Type: application/json Content-Length: 32 {"name":"a", "value":["1", "3"]} --BOUNDARY--
The PUT method expects one or more input documents, and a basename request parameter value for each one. The basename is used to construct the URI for JSON and XML input; other input document types are ignored. A written
property is added to each JSON input by the doSomething
helper function, as shown below:
if (docType == 'application/json') { const mutableDoc = doc.toObject(); mutableDoc.written = fn.currentTime(); xdmp.documentInsert(uri, mutableDoc); ... }
The following example invokes the PUT method, passing in a single JSON document as input. The response tells you the URI under which the document is inserted.
$ curl --anyauth --user user:password -X PUT -d '{"key":"value"}' \ -H "Content-type: application/json" \ 'http://localhost:8000/LATEST/resources/js-example?rs:basename=one' {"written": ["/extensions/one.json"]}
If you retrieve the inserted document, you can see that the PUT method added a JSON property named written to the input document:
$ curl --anyauth --user user:password -X GET \ -H "Accept: application/json" \ http://localhost:8000/LATEST/documents?uri=/extensions/one.json {"key":"value", "written":"11:21:01-08:00"}
You can use a multi-part body to pass more than one document to the PUT method. JSON input is modified as shown above. XML documents are inserted unmodified. All other inputs are ignored. The following example command inserts two documents into the database if the multi-part body contains one JSON part and one XML part:
$ curl --anyauth --user user:password -X PUT -i \ -H "Content-type: multipart/mixed; boundary=BOUNDARY" \ -H "Accept: application/json" \ --data-binary @./multipart-body 'http://localhost:8000/LATEST/resources/js-example?rs:basename=a&rs:basename=b' HTTP/1.1 200 OK Content-type: application/json; charset=UTF-8 Server: MarkLogic Content-Length: 55 Connection: Keep-Alive Keep-Alive: timeout=5 {"written":["/extensions/a.json", "/extensions/b.xml"]}
If you do not include a basename parameter value corresponding to each input document, the PUT implementation returns an error response. Extensions should always return errors to the client using RESTAPI-SRVEXERR
and the pattern described in Reporting Errors. In the example extension, errors are reported using reportErrToClient
as follows:
fn.error(null, 'RESTAPI-SRVEXERR', Sequence.from([statusCode, statusMsg, body])); // unreachable - control does not return from fn.error.
For example, if we repeat the previous PUT request, but remove the second occurrence of basename, a 400 error is returned:
$ curl --anyauth --user user:password -X PUT -i \ -H "Content-type: multipart/mixed; boundary=BOUNDARY" \ -H "Accept: application/json" \ --data-binary @./multipart-body 'http://localhost:8000/LATEST/resources/js-example?rs:basename=a' HTTP/1.1 400 Bad Request Content-type: application/json; charset=UTF-8 Server: MarkLogic Content-Length: 162 Connection: Keep-Alive Keep-Alive: timeout=5 { "errorResponse": { "statusCode": 400, "status": "Bad Request", "messageCode": "RESTAPI-SRVEXERR", "message": "Insufficient number of uri basenames. Expected 2 got 1." } }
The POST and DELETE methods simply log a message when invoked. If you call them, check the MarkLogic Server error log for output.
This section demonstrates the implementation, installation and invocation of a resource service extension implemented in XQuery.
This example extension implements all the HTTP methods supported by the resource extension interface. It demonstrates the use of input arguments, as well as setting content type and response status codes. The implementation follows the template described in Creating an XQuery Resource Service Extension.
The methods implemented by this extension do the following:
The following code implements the contract described above.
xquery version "1.0-ml"; (: Namespace pattern must be: : "http://marklogic.com/rest-api/resource/{$rname}" : and prefix must match resource name :) module namespace example = "http://marklogic.com/rest-api/resource/example"; declare default function namespace "http://www.w3.org/2005/xpath-functions"; declare option xdmp:mapping "false"; (: Conventions: : Module prefix must match resource name, : and function signatures must conform to examples below. : The $context map carries state between the extension : framework and the extension. : The $params map contains parameters set by the caller, : for access by the extension. :) (: Function responding to GET method - must use local name 'get':) declare function example:get( $context as map:map, $params as map:map ) as document-node()* { (: set 'output-type', used to generate content-type header :) let $output-type := map:put($context,"output-type","application/xml") let $arg1 := map:get($params,"arg1") let $content := <args> {for $arg in $arg1 return <arg1>{$arg1}</arg1> } </args> return document { $content } (: must return document node(s) :) }; (: Function responding to PUT method - must use local name 'put'. :) declare function example:put( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()? { (: get 'input-types' to use in content negotiation :) let $input-types := map:get($context,"input-types") let $negotiate := if ($input-types = "application/xml") then () (: process, insert/update :) else error((),"ACK", "Invalid type, accepts application/xml only") return document { "Done"} (: may return a document node :) }; (: Function responding to POST method - must use local name 'post'. :) declare function example:post( $context as map:map, $params as map:map, $input as document-node()* ) as document-node()* { xdmp:log("post!") }; (: Func responding to DELETE method - must use local name 'delete'. :) declare function example:delete( $context as map:map, $params as map:map ) as document-node()? { xdmp:log("delete!") };
Follow these steps to install the example extension in the modules database associated with your REST API instance:
localhost:8000
.example.xqy
.# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X PUT \ -H "Content-type: application/xquery" -d@"./example.xqy" \ "http://localhost:8000/LATEST/config/resources/example"
Optionally, you can include metadata during installation so that a GET request to /config/resources
returns more detail. For example:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X PUT \ -H "Content-type: application/xquery" -d@"./example.xqy" \ "http://localhost:8000/LATEST/config/resources/example?version=1.0&provider=marklogic&description=A simple resource example&method=get&method=post&method=put&method=delete&get:arg1=xs:string*"
The table below breaks down the request parameters so it is easier to see what metadata is included.
The following example command invokes the GET method of the example extension, passing the string super fantastic as arguments to the GET implementation:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X GET \ -H "Accept: application/xml" \ "http://localhost:8000/LATEST/resources/example?rs:arg1=super fantastic" <?xml version="1.0" encoding="UTF-8"?> <args> <arg1>super fantastic</arg1> </args>
The following example invokes the PUT method:
$ curl --anyauth --user user:password -X PUT -d "<input/>" \ -H "Content-type: application/xml" \ http://localhost:8000/LATEST/resources/example Done
The POST and DELETE methods simply log a message when invoked. If you call them, check the MarkLogic Server error log for output.
Only users with the rest-extension-user
role (or equivalent privileges) can execute modules in the modules database, including installed REST resource service extensions.
The pre-defined rest-reader
, rest-writer
, and rest-admin
roles inherit rest-extension-user
. You do not need additional configuration to grant access to your extension to users with these roles.
However, if instead of using the pre-defined roles, you create a custom role with the equivalent privileges, then you must explicitly inherit the rest-extension-user
role in your custom role. Users with the custom role cannot execute resource service extensions unless you do so.
For details, see Controlling Access to Documents and Other Artifacts.
To discover the name, interface, and other metadata about installed resource extensions, send a GET request to the /config/resources
service of the form:
http://host:port/version/config/resources
MarkLogic Server returns a summary of the installed extensions in XML or JSON. The default summary content type is XML. Use the Accept header or format
request parameter to select the output content type. For details, see Controlling Input and Output Content Type.
The amount of information available about a given extension depends on the amount of metadata provided during installation of the extension. The name and methods are always available. Details such as provider, version, and method parameter information are optional.
By default, this request rebuilds the extension metadata each time it is called to ensure the metadata is up to date. If you find this refresh makes discovery take too long, you can disable the refresh by setting the refresh
request parameter to false. Disabling the refresh can result in this request returning inaccurate information, but it does not affect the freshness or availability of the implementation of any extensions.
The following example requests an XML report. One extension is installed, called example. The extension implements only a get method, which accepts a string parameter named genre. The extension provider is Acme Widgets.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -H "Accept: application/xml" \ -X GET http://localhost:8000/LATEST/config/resources ... <rapi:resources xmlns:rapi="http://marklogic.com/rest-api"> <rapi:resource> <rapi:name>example</rapi:name> <rapi:title/> <rapi:version/> <rapi:provider-name>Acme Widgets</rapi:provider-name> <rapi:description/> <rapi:methods> <rapi:method> <rapi:method-name>get</rapi:method-name> <rapi:parameter> <rapi:parameter-name>genre</rapi:parameter-name> <rapi:parameter-type>string</rapi:parameter-type> </rapi:parameter> </rapi:method> </rapi:methods> <rapi:resource-source>/v1/resources/example</rapi:resource-source> </rapi:resource> </rapi:resources>
To retrieve the XQuery code that implements an extension, send a GET request to the /config/resources/{name}
service of the form:
http://host:port/version/config/resources/extensionName
Where extensionName is the name under which the extension is installed.
Set the request Accept header to application/xquery
.
MarkLogic Server returns the XQuery library module that implements the extension in the response body. For example:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -H "Accept: application/xquery" \ -X GET http://localhost:8000/LATEST/config/resources/example ... xquery version "1.0-ml"; module namespace example="http://marklogic.com/rest-api/resource/example"; declare function example:get( ...
Use the /ext
service to install, update, retrieve, and delete assets required by your REST application, including additional XQuery library modules required by resource extensions and transforms, and partial update replace library modules.
You can only use the /ext
service with REST API instances created with MarkLogic Server 7 or later. If you REST API instance was created with an earlier version, refer to the documentation for that version.
The following topics are covered:
When you install or update a dependent library module or other asset as described in this section, the asset is replicated across your cluster automatically. There can be a delay of up to one minute between updating and availability.
MarkLogic Server does not automatically remove dependent assets when you delete the related extension or transform.
Since dependent assets are installed in the modules database, they are removed when you remove the REST API instance if you include the modules database in the instance teardown. For details, see Removing an Instance.
If you installed assets in a REST API instance using MarkLogic 6, they cannot be managed using the /ext
service unless you re-install them using /ext
. Reinstalling the assets may require additional changes because the asset URIs will change. If you choose not to migrate such assets, continue to maintain them according to the documentation for MarkLogic 6.
Use the /ext
service to install any assets required by resource service extensions or transformations, and to install partial update replace library modules. Using this service installs an asset into the modules database associated with the REST API instance. If the REST API instance is part of a cluster, the asset is automatically propagated throughout the cluster.
To install or update an asset, send a PUT request to /ext/{
directories}/{
asset}
with a URL of the following form:
http://host:port/version/ext/directories/asset?perm:role=capability
Where directories is one or more database directories and asset is the asset document name. Use the perm
request parameter to specify additional permissions on the asset, beyond the permissions granted to rest-admin privileges that are always present. The asset is installed into the modules database with the URI /ext/
directories/
asset.
The following example installs the XQuery library module my-lib.xqy
into the modules database with the URI /ext/my/domain/my-lib.xqy
. Execute privileges are granted to the role my-users
.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X PUT -i -d @./my-lib.xqy \ -H "Content-type: application/xquery" \ "http://localhost:8000/LATEST/ext/my/domain/my-lib.xqy?perm:my-users=execute"
If you're installing an asset whose content is sensitive to line breaks, such as a JavaScript module that contains '//' style comments, use --data-binary
rather than -d
to pass the content to curl
. For details, see Introduction to the curl Tool.
You can insert assets into the modules database as JSON, XML, text, or binary documents. MarkLogic Server determines the document format by examining the format
request parameter, the MIME type in the Content-type
HTTP header, or the document URI extension, in that order. For example, if the format
parameter is present, it determines the document type. If there is no format
parameter and no Content-type
header, the URI extension (.xml, .jpg, etc.) determines the document type, based on the MIME type mappings defined for your installation; see the Mimetypes section of the Admin Interface.
To use a dependent library installed with /ext
in your extension or transform module, use the same URI under which you installed the dependent library. For example, if a dependent library is installed with the URI /ext/my/domain/my-lib.xqy
, then the extension module using this library should include an import of the following form:
import module namespace dep="mylib" at "/ext/my/domain/lib/my-lib.xqy";
You can remove either a single asset or all the assets in a directory in the modules database associated with your REST API instance.
To remove a single asset, send a DELETE request with a URL of the following form:
http://host:port/version/ext/directories/asset
Where directories is one or more database directories and asset is the asset document name. For example, the following command removes an XQuery library module with the URI /ext/my/domain/my-lib.xqy
:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X DELETE -i\ "http://localhost:8000/LATEST/ext/my/domain/my-lib.xqy
To remove all assets in a modules database directory, send a DELETE request with a URL of the following form:
http://host:port/version/ext/directories/
The URL must end with a terminating path separator (/) so that MarkLogic Server recognizes it as a directory.
Where directories is one or more database directories. For example, the following command removes all assets in the directory /ext/my/domain/
:
$ curl --anyauth --user user:password -X DELETE -i\ "http://localhost:8000/LATEST/ext/my/domain/
You can retrieve a list of all assets in the modules database by sending a GET request with a URL of the following form:
http://host:port/version/ext/
You can retrieve a list of assets in a particular directory by sending a GET request with a URL of the following form, where directories is one or more database directory path steps:
http://host:port/version/ext/directories
You can request results as either XML or JSON, using either the HTTP Accept header or the format
request parameter.
The following example retrieves a list of all assets installed under the database directory /ext/my/domain/
, as XML:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X GET -i\ -H "Accept: application/xml" \ http://localhost:8000/LATEST/ext/my/domain/ ... HTTP/1.1 200 OK Server: MarkLogic Content-Type: text/xml; charset=UTF-8 Content-Length: 125 Connection: Keep-Alive Keep-Alive: timeout=5 <rapi:assets xmlns:rapi="http://marklogic.com/rest-api"> <rapi:asset>/ext/patch/replace/my-lib.xqy</rapi:asset> </rapi:assets>
The following example makes the same request, but returns JSON results.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X GET -i\ -H "Accept: application/json" \ http://localhost:8000/LATEST/ext/my/domain/ ... HTTP/1.1 200 OK Server: MarkLogic Content-Type: text/plain; charset=UTF-8 Content-Length: 54 Connection: Keep-Alive Keep-Alive: timeout=5 {"assets":[ {"asset":"/ext/my/domain/my-lib.xqy"} ]}
To retrieve the contents of an asset document, send a GET request with a URL of the following form:
http://host:port/version/ext/directories/asset
Where directories is one or more database directories and asset is the asset document name. For example, the following command retrieves an XQuery library module with the URI /ext/my/domain/my-lib.xqy
:
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X GET -i \ -Accept: "application/xquery" \ "http://localhost:8000/LATEST/ext/my/domain/my-lib.xqy ... HTTP/1.1 200 Document Retrieved Content-type: application/xquery Server: MarkLogic Content-Length: 633 Connection: Keep-Alive Keep-Alive: timeout=5 ...asset contents...
The response data content type is determined by the MIME type in the Accept header. No content conversions are applied. For example, if you retrieve a binary document containing PDF and set the Accept header to application/xml
, the response Content-type will be application/xml
, even though the data in the body is binary.
The /eval
service enables you to send blocks of XQuery and server-side JavaScript code to MarkLogic Server for evaluation. This is equivalent to calling the builtin server function xdmp:eval
(XQuery) or xdmp.eval
(JavaScript). The following topics are covered:
Using the /eval
service requires special privileges not required for other REST Client API services. You must have the following privileges or the equivalent:
To send JavaScript code to MarkLogic Server for evaluation, use POST:/v1/eval
. Send a POST request to the /eval
service with a URL of the following form:
http://host:port/version/eval
The POST body MIME type must be x-www-form-urlencoded
, with the ad-hoc code as the value of the javascript
form parameter and any external variables in the vars
form parameter. For details on passing in variable values, see Specifying External Variable Values.
Your query must be expressed using the MarkLogic server-side JavaScript dialect. For details, see the JavaScript Reference Guide.
For example, suppose you want to evaluate the following JavaScript code, where word1
and word2
are external variable values supplied by the request:
word1 + " " + word2
The following request causes the code to be evaluated by MarkLogic Server. Use the curl --data-urlencode
option to force URL-encoding of the form parameter values in the POST body.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode javascript='word1 + " " + word2' \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval
The response is always multipart/mixed
, with one part for each value returned by the evaluated code. The part headers can contain additional type information about the returned data in the X-Primitive
header. For details, see Interpreting the Results of an Ad-Hoc Query.
For example, the above request returns results similar to the following:
HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=auto; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=198d5dc521a0e8cf Content-Length: 113 Connection: Keep-Alive Keep-Alive: timeout=5 --198d5dc521a0e8cf Content-Type: text/plain X-Primitive: untypedAtomic hello world --198d5dc521a0e8cf--
You can return documents, objects, and arrays as well as atomic values. To return multiple items, you must return either a JavaScript Sequence
or an XQuery sequence. You can construct a Sequence
from an array-like or a generator, and many builtin functions that can return multiple values return a Sequence
. To construct a sequence, apply xdmp.arrayValues
or Sequence.from
to a JavaScript array.
For example, to extend the previous example to return the two input values as well as the concatenated string, accumulate the results in an array and then apply Sequence.from
to the array:
Sequence.from([word1, word2, word1 + " " + word2])
The following command executes the above code. The response contains 3 parts: one for the string value hello, one for the string value world, and one for the concatenation result, hello world.
$ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode javascript='Sequence.from([word1, word2, word1 + " " + word2])' \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=query; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=3d0b32f66d6d0f33 Content-Length: 279 Connection: Keep-Alive Keep-Alive: timeout=5 --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: untypedAtomic hello --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: untypedAtomic world --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: untypedAtomic hello world --3d0b32f66d6d0f33--
By default, the ad-hoc code is evaluated in the context of the database associated with the REST API instance that receives the request. To evaluate the code against another database, specify the database name or id through the database URL parameter. For example, the following request uses the example database:
$ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode javascript='word1 + " " + word2' \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval?database=example
To send XQuery code to MarkLogic Server for evaluation, use POST:/v1/eval
. Send a POST request to the /eval
service with a URL of the following form:
http://host:port/version/eval
The POST body MIME type must be x-www-form-urlencoded
, with the ad-hoc code as the value of the xquery
form parameter and any external variables in the vars
form parameter. For details on passing in variable values, see Specifying External Variable Values.
For example, suppose you want to evaluate the following XQuery code, where word1
and word2
are external variable values supplied by the request:
xquery version "1.0-ml"; declare variable $word1 as xs:string external; declare variable $word2 as xs:string external; fn:concat($word1, " ", $word2)
The following request body supplies the query code and the parameter values. (The values should be urlencoded. They're shown unencoded here for readability.)
xquery= xquery version "1.0-ml"; declare variable $word1 as xs:string external; declare variable $word2 as xs:string external; fn:concat($word1, " ", $word2) &vars={"word1":"hello","word2":"world"}
The following request causes the code to be evaluated by MarkLogic Server, assuming file concat.xqy
contains the XQuery shown above. Use the curl --data-urlencode
option to force URL-encoding of the form parameter values in the POST body.
# Windows users, see Modifying the Example Commands for Windows $ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode xquery@./concat.xqy \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval
The response is always multipart/mixed
, with one part for each value returned by the evaluated code. The part headers can contain additional type information about the returned data in the X-Primitive
header. For details, see Interpreting the Results of an Ad-Hoc Query.
For example, the above request returns results similar to the following:
HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=auto; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=7e235c5751341cff Content-Length: 106 Connection: Keep-Alive Keep-Alive: timeout=5 --7e235c5751341cff Content-Type: text/plain X-Primitive: string hello world --7e235c5751341cff--
You can return documents and other complex values, as well as atomic values. To return multiple items, return a sequence. The multipart response contains a part for each sequence item.
The following query extends the previous example to return the two input values as well as the concatenated string:
xquery version "1.0-ml"; declare variable $word1 as xs:string external; declare variable $word2 as xs:string external; ($word1, $word2, fn:concat($word1, " ", $word2))
The following command evaluates the query, assuming the query is in a file named concat2.xqy
. The response contains 3 parts, one for the value of word1
, one for the value of word2
, and one for the concatenation result, hello world.
$ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode xquery@./concat2.xqy \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=query; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=3d0b32f66d6d0f33 Content-Length: 279 Connection: Keep-Alive Keep-Alive: timeout=5 --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: string hello --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: string world --3d0b32f66d6d0f33 Content-Type: text/plain X-Primitive: string hello world --3d0b32f66d6d0f33--
By default, the ad-hoc query is evaluated in the context of the database associated with the REST API instance that receives the request. To evaluate the query against another database, specify the database name or id through the database URL parameter. For example, the following request evaluates the query against the example database:
$ curl --anyauth --user user:password -X POST -i \ -H "Content-type: application/x-www-form-urlencoded" \ -H "Accept: multipart/mixed" \ --data-urlencode xquery@./concat.xqy \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/eval?database=example
You can pass values to an ad-hoc query (or invoked module) at runtime using external variables. Specify the variable values in the POST body using the vars
form parameter. The vars
parameter has the following form:
vars={name:value, name
:value, ...}
For example, the following vars parameter supplies values for 2 external variables, named word1
, and word2
:
vars={word1:"hello",word2:"world"}
If you're evaluating or invoking XQuery code, you must declare the variables explicitly in the ad-hoc query or module. For example, the following prolog declares two external variables whose values can be supplied through the above vars
parameter.
xquery version "1.0-ml"; declare variable $word1 as xs:string external; declare variable $word2 as xs:string external; ...
If you're evaluating or invoking XQuery code that depends on variables in a namespace, use Clark notation on the variable name. That is, specify the name using notation of the form {
namespaceURI}
name. For example, given the following query saved to the file nsquery.xqy
:
xquery version "1.0-ml"; declare namespace my = "http://example.com"; declare variable $my:who as xs:string external; fn:concat("hello", " ", $my:who)
You can specify the value of $my:who
as shown in the command below. Note that you must quote the namespace qualified variable name.
$ curl --anyauth --user user:password -i -X POST \ -H "Content-type: application/x-www-form-urlencoded" \ --data-urlencode xquery@./nsquery.xqy --data-urlencode vars='{"{http://example.com}who":"world"}' \ http://localhost:8000/LATEST/eval HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=auto; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=ea9250df22f6543f Content-Length: 106 Connection: Keep-Alive Keep-Alive: timeout=5 --ea9250df22f6543f Content-Type: text/plain X-Primitive: string hello world --ea9250df22f6543f--
When you make a POST request to the /eval
or /invoke
service, the result is always a multipart/mixed
response, with a part for each value returned by the query. Depending on the type of value, the part header can contain information that helps your application interpret the value.
For example, an atomic value (anyAtomicType
, a type derived from anyAtomicType
, or an equivalent JavaScript type) has an X-Primitive part header that can specify an explicit type such as integer
, string
, or date
. The reported type may be more general than the actual type. Types derived from anyAtomicType
include anyURI, boolean, dateTime, double, and string. For details, see http://www.w3.org/TR/xpath-functions/#datatypes.
Returning a JavaScript null
value or an XQuery empty sequences results in an empty response.
The table below lists the Content-type and X-Primitive part header values for a variety of types. The response for other output is undefined, including the output from constructors such as attribute()
, comment()
, processing-instruction()
, boolean-node()
, cts:query()
, or a JavaScript object.
When you use POST:/v1/invoke
or POST:/v1/eval
MarkLogic returns a cookie. Normally, your client application code should ignore the cookie. However, if your server-side code sets a session field, then your client application must send the same cookie to the same host for any subsequent eval or invoke requests that access the session field.
You should also include any load balancer cookies if you are using a load balancer.
If you make an eval or invoke request that requires access to an existing session field in the context of a multi-statement transaction, include both the transaction cookie and the session cookie in the request.
A JavaScript MJS module can be invoked through the /v1/invoke
endpoint. This is the preferred method.
POST:/v1/invoke
enables you to evaluate an XQuery or server-side JavaScript module installed on MarkLogic Server. This is equivalent to calling the builtin server function xdmp:invoke
(XQuery) or xdmp.invoke
(JavaScript).
Using the /invoke
service requires special privileges not required for other REST Client API services. You must have the following privileges or their equivalent:
http://marklogic.com/xdmp/privileges/xdmp-invoke
http://marklogic.com/xdmp/privileges/xdmp-invoke-in
http://marklogic.com/xdmp/privileges/xdbc-invoke
http://marklogic.com/xdmp/privileges/xdbc-invoke-in
To evaluate an XQuery or JavaScript module installed on MarkLogic Server, make a POST request to the /invoke service with a URL of the following form:
http://host:port/version/invoke
The POST body MIME type must be x-www-form-urlencoded
, with the module path as the value of the module
form parameter and any external variables in the vars
form parameter. For details on passing in variable values, see Specifying External Variable Values.
For example, assuming you previously used a PUT /LATEST/ext/invoke/example.sjs
request to install a JavaScript module on MarkLogic Server, then the following command invokes the module:
$ curl --anyauth --user user:password -i -X POST \ -H "Content-type: application/x-www-form-urlencoded" \ --data-urlencode module=/ext/invoke/example.sjs \ http://localhost:8000/LATEST/invoke
The module
parameter must be the path to a module installed in the modules database associated with the REST API instance. You can use the /ext
service to install your module, as described in Managing Dependent Libraries and Other Assets. The module path is resolved using the rules described in Rules for Resolving Import, Invoke, and Spawn Paths in the Application Developer's Guide. If you install your module using PUT /v1/ext/{
name}
, the module path always begins with /ext/
.
If the invoked module expects input variable values, specify them using the vars
form parameter, as described in Specifying External Variable Values.
The response is always multipart/mixed
, with one part for each value returned by the invoked module. The part headers can contain additional type information about the returned data in the X-Primitive
header. For details, see Interpreting the Results of an Ad-Hoc Query.
If your server-side code sets a session field that subsequent eval or invoke requests depend upon, your subsequent calls must pass along the session cookie returned by the first such request. For details, see Managing Session State.
The following example first installs a module in the modules database associated with the REST API instance using the /ext
service, then invokes module through the /invoke
service.
module.sjs
.module.sjs
.Sequence.from([word1, word2, word1 + " " + word2])
module.xqy
.xquery version "1.0-ml"; declare variable $word1 as xs:string external; declare variable $word2 as xs:string external; ($word1, $word2, fn:concat($word1, " ", $word2))
curl --anyauth --user user:password -X PUT -i \ --data-binary @./module.sjs \ -i -H "Content-type: application/vnd.marklogic-javascript" http://localhost:8000/LATEST/ext/invoke/example.sjs
curl --anyauth --user user:password -X PUT -i -d @./module.xqy \ -H "Content-type: application/xquery" http://localhost:8000/LATEST/ext/invoke/example.xqy
curl --anyauth --user user:password -i -X POST \ -H "Content-type: application/x-www-form-urlencoded" \ --data-urlencode module=/ext/invoke/example.sjs \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/invoke
curl --anyauth --user user:password -i -X POST \ -H "Content-type: application/x-www-form-urlencoded" \ --data-urlencode module=/ext/invoke/example.xqy \ --data-urlencode vars='{"word1":"hello","word2":"world"}' \ http://localhost:8000/LATEST/invoke
The result of running this example is similar to the following. There are 3 parts in the response body, corresponding to the 3 values returned by the invoked module.
HTTP/1.1 200 OK Server: MarkLogic 8.0-20141122 Set-Cookie: TxnMode=query; path=/ Set-Cookie: TxnID=null; path=/ Content-Type: multipart/mixed; boundary=09ff9ca463ea6272 Content-Length: 279 Connection: Keep-Alive Keep-Alive: timeout=5 --09ff9ca463ea6272 Content-Type: text/plain X-Primitive: string hello --09ff9ca463ea6272 Content-Type: text/plain X-Primitive: string world --09ff9ca463ea6272 Content-Type: text/plain X-Primitive: string hello world --09ff9ca463ea6272--