URL Rewriting
(1Q23)
How to customize a web application's URLs using controller.xq
scripts.
Introduction
Good web applications provide meaningful and consistent URLs to the user. eXist-db's URL Rewriting facility is eXist's oldest internal mechanism for providing short, human-readable URLs to XQuery web applications, conveniently masking the often complex hierarchy of XQuery modules, HTML files, data, and other resources. (A newer facility for achieving these goals is eXist-db's implementation of RESTXQ. A third method is to place a reverse proxy between eXist-db and the end-user. Many applications combine two or even all three of these methods.)
For a brief overview of how eXist-db's URL Rewriting facility works, consider what happens when eXist-db receives an HTTP request:
-
eXist-db's Jetty web server receives an HTTP request. In the default configuration this is handled by the
XQueryUrlRewrite
servlet for any URL starting with the path/exist
. -
The
XQueryUrlRewrite
servlet first checks the URL against a series of URL patterns defined in an eXist-db configuration file, calledcontroller-config.xml
and described in the section below on Base Mappings. If eXist-db finds a matching root pattern, which is/apps
by default, eXist-db will look within the associated controller root path, which is the/db/apps
database collection by default, for controller scripts: special XQuery files namedcontroller.xq
(or in older applicationscontroller.xql
). A controller script applies to the collection it is stored in and its descendants. They form a collection hierarchy—a URL space within which URL rewriting can be flexibly applied. -
The
controller.xq
script examines the URL using the provided Controller Variables, and produces an XML-formatted directive in the Controller XML Format. -
The
XQueryURLRewrite
servlet interprets the directive as a series of instructions on what to do next. These instructions may be as simple as forwarding the request to a resource on the server (or elsewhere), or as complex as a pipeline using the Model-View-Controller pattern (see MVC and Pipelines) and other servlets such as eXist-db's XQueryServlet or XSLTServlet. The controller script isn't limited to returning these types of instructions; like any XQuery main module, they can return any data you may wish. But URL Rewriting instructions are the special capability of controller scripts.
Example 1: Simple URL Rewriting
Consider the application you are currently using. You are likely accessing this article
from the URL /exist/apps/doc/data/urlrewrite
. But how is this possible? The
path to the underlying document in the eXist-db database is
/exist/apps/doc/data/urlrewrite.xml
. If you access this URL directly,
eXist-db will return the XML document's content. However, to return a properly formatted
HTML page that they can easily consume in their web-browser, the author of this application
wrote an XQuery to transform the XML into HTML. Let's call it
transform.xq
. This query can dynamically transform documentation
articles into HTML, but it needs to know which article the user is interested in. The author
decides to craft URLs to specific articles using a URL parameter, doc
,
resulting in a URL like
/exist/apps/doc/modules/transform.xq?doc=urlrewrite.xml
. But this URL is
cumbersome. What if the author wants to expose the HTML version of documentation article
through a much nicer, simplified URL like /exist/apps/doc/urlrewrite
?
To achieve this goal we need a mechanism to map or rewrite a given URL to an application
specific endpoint. Specifically, we need to ensure when a user visits the URL
/exist/apps/doc/urlrewrite
, eXist-db must initiate an XQuery process that
locates the source document, transforms it into HTML, and returns the HTML page. This
control is precisely what the eXist-db URL Rewriting facility provides to application
creators.
The primary mechanism behind customizing URLs like this is the controller
script, an XQuery main module named controller.xq
. This
script is invoked for all URL paths targeting the collection in which it resides. It has
access to a number of Controller Variables pre-bound with details about the request,
including $exist:resource
, which conveniently contains the name of the
requested resource (without any leading path components). It also includes
$exist:controller
, which contains the path to the database collection where
the controller.xq
is located.
This information allows a controller script to forward all requests for
/exist/apps/doc/{resource}
to an XQuery,
transform.xq
, which converts the XML document located via the
{resource}
path component into an HTML page. To achieve this, one would
create the following controler script and store it in the database at the path:
/db/apps/doc/controller.xq
:
xquery version "3.1";
declare namespace exist="http://exist.sourceforge.net/NS/exist";
declare variable $exist:path external;
declare variable $exist:resource external;
declare variable $exist:controller external;
declare variable $exist:prefix external;
declare variable $exist:root external;
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/modules/transform.xq">
<add-parameter name="doc" value="{$exist:resource}.xml"/>
</forward>
</dispatch>
This example controller script returns a simple <dispatch>
element which will
be passed back to eXist-db's URL Rewriting framework. The <forward>
element
instructs the framework to call the URL modules/transform.xq
relative to
the collection where the controller resides. It adds an HTTP Request parameter, named
doc
, which constructs the resource that the
transform.xq
module will retrieve and transform. The
transform.xq
module will access the doc
parameter by
calling the request:get-parameter()
XQuery function from eXist-db's HTTP
Request Module, so that it can locate the desired resource.
Example 2: Defining a Pipeline
Real world controllers are often far more complex and may contain arbitrary XQuery code to distinguish between different scenarios. The eXist-db URL Rewriting framework allows you to turn simple requests into complex processing pipelines, involving any number of steps.
For example, let us split the <dispatch>
element from above into a pipeline
involving two steps:
-
Load the resource to be transformed
-
Pass the content of the resource to
modules/transform.xq
xquery version "3.1";
declare namespace exist="http://exist.sourceforge.net/NS/exist";
declare variable $exist:path external;
declare variable $exist:resource external;
declare variable $exist:controller external;
declare variable $exist:prefix external;
declare variable $exist:root external;
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="data/{$exist:resource}.xml"/>
<view>
<forward url="{$exist:controller}/modules/transform.xq"/>
</view>
</dispatch>
Every <dispatch>
element must contain at least one <forward>
element,
followed by an optional <view>
element, grouping any number of follow up steps. In
this example, the first <forward>
element simply instructs eXist-db to load the
requested XML document and then return its content. The output of the first step is passed
internally via the HTTP request to the second step. The transform.xq
module can access this output via the request:get-data()
XQuery function from
eXist-db's HTTP Request Module.
Controller XML Format
As we have seen, the controller.xq
script is expected to return a single XML
element: a <dispatch>
or <ignore>
element. The script can actually return
any data, as you wish, but the <dispatch>
or <ignore>
elements have special
properties in the context of a controller.xq
script.
Note that all of the elements described in this section must be in the
http://exist.sourceforge.net/NS/exist
namespace.
The dispatch Action
The <dispatch>
element must contain one of the action
elements
redirect
or forward
, followed by an optional view
element. It may also contain an optional cache-control
element.
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<redirect url="..."/>
</dispatch>
The ignore Action
The <ignore>
action simply bypasses the URL Rewriting facility. This may be
useful for requests to fixed resources like images or stylesheets. An alternative may be to
handle requests for such resources separately from the XQueryUrlRewrite
servlet; this is discussed in Locating Controller Scripts and Configuring Base Mappings.
<ignore xmlns="http://exist.sourceforge.net/NS/exist"/>
The <ignore>
element may include an optional cache-control
element.
The redirect Action
The <redirect>
action redirects the client to another URL with an HTTP 302 redirect
response, indicating that the requested resource has been temporarily moved to a
new URL (supplied by the Location HTTP Header). Clients typically follow the redirect and
issue a request to the new URL; users will see their web browser update and show the new
URL. Note that this second request may well access the eXist-db controller again; care must
be taken to avoid creaing an infinite loop.
The <redirect>
element must contain a url
attribute:
-
url
-
The URL to redirect the request to.
Many eXist-db applications establish redirects for requests to their root without a
leading slash, to ensure that all subsequent requests to the application begin with a
leading slash. This is often accomplished as the first condition in the
controller.xq
script:
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<redirect url="..."/>
</dispatch>
In a local copy of the eXist-db Documentation application, for example, this will cause
a request for http://localhost:8080/exist/apps/doc
to receive the
following HTTP 302 redirect response:
HTTP/1.1 302 Found Location: http://localhost:8080/exist/apps/doc/ Content-Length: 0
The forward Action
The <forward>
action internally forwards the current request to another request
path or servlet. Unlike the redirect
action, the
forward
action is performed entirely server-side by eXist-db (via the
RequestDispatcher
of the servlet engine). The details of the internal forward
action are not exposed to the client; i.e., the user's web browser will not show any changes
to the URL.
The <forward>
element must contain either a url
or
servlet
attribute:
-
url
-
The new request path, which will be processed by the servlet engine in the normal way, as if it were directly called.
If a relative path is provided, then it is resolved relative to to the current request path. An absolute path will be resolved relative to the path that triggered the controller.
For example, if the current URL context is
/exist
and the supplied attribute readsurl="/admin"
, the resulting path will be/exist/admin
. -
servlet
-
The name of a servlet as given in the
<servlet-name>
element of the corresponding servlet definition from eXist-db's web descriptor$EXIST_HOME/etc/webapp/WEB-INF/web.xml
configuration file.For example, valid names within the eXist-db's standard setup would be
XQueryServlet
orXSLTServlet
. You can see some examples of these in the article MVC and Pipelines.
The <forward>
element may contain the following optional attributes:
-
absolute
-
To be used in combination with
url
. If set to "yes", the url will be interpreted as an absolute path within the current servlet context. See Accessing Resources Not Stored in the Database for an example. -
method
-
The HTTP method (e.g., POST, GET, PUT) to use when passing the request to the next step in the pipeline; not applicable to the first step. The default method for pipeline steps in the view section is always
POST
.
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/modules/transform.xq">
<add-parameter name="doc" value="{$exist:resource}.xml"/>
</forward>
</dispatch>
The <forward>
element can contain the optional child elements: add-parameter
, set-attribute
, clear-attribute
, and set-header
.
The view Action
The <view>
action is used to define processing pipelines, and may follow redirect
or forward
actions.
The <view>
element is used to wrap a sequence of action elements such as forward
. It is often used to call another
servlet to process the results of the initial action. This is discussed in the article:
MVC and Pipelines
The add-parameter Option
The <add-parameter>
option adds (or overwrites) a HTTP Request
Parameter.
The name of the parameter is taken from the name
attribute, and the
value from the value
attribute.
<add-parameter name="xxx" value="yyy"/>
The original HTTP request will be copied before the change is applied. This applies only to the step on which it is placed, that is to say that subsequent steps in the pipeline will not see the parameter.
To access the value of the parameter, use the request:get-parameter()
XQuery function from eXist-db's HTTP Request Module.
The set-attribute Option
The <set-attribute>
option adds (or overwrites) a Java Servlet request
attribute. They are part of the Java Servlet specification, and are not related to the HTTP
Request or HTTP Response. Request attribute are internal to the pipeline. Unlike request
parameters, request attributes will be visible to subsequent steps in the processing
pipeline.
The name of the request attribute is read from the name
attribute,
and the value from the value
attribute.
<set-attribute name="xxx" value="yyy"/>
You can set arbitrary request attributes, for instance to pass information between XQuery modules. Some attribute names may be reserved by various servlets in the pipeline.
To access the value of the request attribute, use the
request:get-attribute()
XQuery function from eXist-db's HTTP Request
Module.
The clear-attribute Option
The <clear-attribute>
option clears a request attribute.
The name of the request attribute is read from the name
attribute.
<clear-attribute name="xxx"/>
Since request attributes will be visible to subsequent steps in the processing pipeline, you may wish to clear them once they are no longer needed. eXist-db places no requirement on the user having to ever clear attributes.
The set-header Option
The <set-header>
option sets an HTTP Response Header field.
The name of the header is read from the name
attribute, and the value
from the value
attribute.
<set-header name="xxx" value="yyy"/>
The HTTP response is shared between all steps in the pipeline, so all following steps will be able to see the change.
The cache-control Option
The <cache-control>
element is used to tell the URL Rewriting framework if the
mapping used to rewrite the input URL to its dispatch rule should be cached. It has a single
attribute: cache="yes|no"
.
Internally the URL Rewriting framework maintains a mapping between input URLs and
dispatch rules. When the cache is enabled, the controller.xq
script only
needs to be executed once for each distinct input URL. Subsequent requests for the same URL
will be served from the cache.
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<redirect url="..."/>
<cache-control cache="yes"/>
</dispatch>
Note: Only the URL rewrite rule is cached. The HTTP response itself is not cached. The
cache-control
setting is completely unrelated to HTTP Cache Headers in
the HTTP Response and any client-side caching within a web browser.
Controller Variables
Five URL Rewriting-related variables are pre-bound and made available to the
controller.xq
script for convenience. For example, if the request path is
/exist/apps/sandbox/get-examples.xq
these variables will be bound to the
following values:
Variable Name |
Variable Value |
---|---|
|
|
|
|
|
|
|
|
|
|
These variables are pre-bound for simplicity and convenience. You do not need to
explicitly declare the variables or the exist
namespace. However, doing so
is considered best practice, by adding this block to the prolog of your
controller.xq
script. For instance:
declare namespace exist="http://exist.sourceforge.net/NS/exist";
declare variable $exist:root external;
declare variable $exist:prefix external;
declare variable $exist:controller external;
declare variable $exist:path external;
declare variable $exist:resource external;
These variables can also be accessed by any query within the controller hierarchy via the
request:get-attribute()
function, e.g.,
request:get-attribute("$exist:prefix")
.
Some further remarks about each variable:
-
$exist:root
-
Is bound to the root of the current controller hierarchy. This may either point to the file system or to a collection in the database. You can use this variable to locate resources relative to the root of the application.
For example, assume that you want to process a request using stylesheet
db2xhtml.xsl
, which could either be stored in the/stylesheets
directory in the root of the webapp or, if the app is running from within the database, the corresponding/stylesheets
collection. You want your app to be able to run from either location. The solution is to incorporate the value of the$exist:root
variable into your logic:<forward servlet="XSLTServlet"> <set-attribute name="xslt.stylesheet" value="{$exist:root}/stylesheets/db2xhtml.xsl"/> </forward>
-
$exist:prefix
-
If the current controller hierarchy is mapped to a certain path prefix, then the
$exist:prefix
variable will be bound to that prefix.For example: the default configuration maps the path
/apps
to a collection in the database (see below). In this case, the$exist:prefix
variable would be bound to the value/apps
. -
$exist:controller
-
Is bound to the part of the URL leading to the current
controller.xq
.For example, if the request path is
/sandbox/test.xq
and the controller is in thexquery
collection, the$exist:controller
variable will be bound to the value/sandbox
. -
$exist:path
-
Is bound to the last part of the request URL, i.e., after the section leading to the collection containing the controller.xq.
For instance, if the resource
example.xml
resides within the same collection as the controller query, the$exist:path
variable would be bound to the value/example.xml
. $exist:resource
-
Is bound to the part of the URL after the last
/
; this is usually pointing to a resource.For instance:
example.xml
.
In addition, within a controller.xq
file, you also have access to the entire
XQuery function library, including the functions in the HTTP request
,
response
, and session
modules.
Accessing Resources Not Stored in the Database
If your controller.xq
is stored in a database collection, all relative and/or
absolute URLs processed by the controller will be resolved against the database, not the file
system. This can be a problem if you need to access common resources, which should be shared
with other applications residing on the file system or in the database.
The forward
directive accepts an
optional attribute absolute="yes|no"
to handle this. If one sets
absolute="yes"
, an absolute path (starting with a /
) in the
url
attribute will resolve relative to the current servlet context, not
the controller context.
For example, to forward all requests starting with a path /libs/
to a
directory within the webapp
folder of eXist-db, you can build upon the
following example snippet:
if (starts-with($exist:path, "/libs/"))
then
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="/{substring-after($exist:path, '/libs/')}" absolute="yes"/>
</dispatch>
This simply removes the /libs/
prefix and sets the attribute
absolute="yes"
, so that the path will be resolved relative to the main
context of the servlet engine, typically /exist/
. In your HTML, you can now
write paths such as:
<script type="text/javascript" src="/libs/scripts/jquery/jquery-1.7.1.min.js"/>
This will locate the jquery file in webapp/scripts/jquery/...
, even if
the rest of your application is stored in the db and not on the file system.
Locating Controller Scripts and Configuring Base Mappings
By convention, the controller script must be named controller.xq
(or in
older applications controller.xql
; if both controller.xq
and
controller.xql
are placed in the same collection, the former takes precedence,
and the latter is ignored).
If you wish to allow anonymous users to access resources using the URL Rewriting framework
then you need to ensure that the controller.xq
script is world-executable,
e.g., sm:chmod(xs:anyURI("/path/to/controller.xq")), "o+x")
.
By default, the URL Rewriting framework will try to guess the path to the controller script by looking at the request path, starting with the deepest, most specific collection, and then examining each parent collection until a controller script is found or the root of the database has been reached.
This can be configured and overridden via the controller-config.xml
configuration file in $EXIST_HOME/etc/webapp/WEB-INF
, which defines the
base mappings used.
In fact, one web application may have more than one controller hierarchy. For example, you
may want to keep the main webapp within the file system, while some tools and scripts should
be served from a database collection. This can be done by configuring two roots within the
controller-config.xml
file.
The configuration file has two components:
-
<forward>
actions which map URL patterns to servlets -
<root>
elements that define the root for a file system or database collection hierarchy
The <forward>
elements specify path mappings for common servlets, similar to the
servlet mapping in $EXIST_HOME/etc/webapp/WEB-INF/web.xml
. The advantage to
specifying them in controller-config.xml
is that the XQueryURLRewrite
servlet (which implements the URL Rewriting framework) becomes a single point of entry for the
entire web application, and no additional handling of the servlet paths is necessary in the
controller script.
For example, if we had registered a servlet mapping for /rest
in
web.xml
, we would have needed to make sure that this path is ignored in
our main controller.xq
scripts. However, since the mapping is done via
controller-config.xml
, XQueryURLRewrite handles the path, which then
doesn't need to be accounted for in our controller.
The <root>
elements define the roots of a directory or database collection
hierarchy, mapped to a certain base path. For example, the default
controller-config.xml
uses two roots:
<!-- HTTP requests to /apps are mapped onto the database path /db/apps -->
<root pattern="/apps" path="xmldb:exist:///db/apps"/>
<!--
++ The default fallback web application is served from the
++ /etc/webapp directory on the filesystem.
-->
<root pattern=".*" path="/"/>
This means that paths starting with /apps
will be mapped to the
collection hierarchy below /db/apps
. All relative or absolute URLs within
the URL space managed by the controller.xq
script will be resolved against
the database, not the file system. Everything else is handled by the catch all pattern
pointing to the root directory of the webapp (which, by default, corresponds to
$EXIST_HOME/etc/webapp
). However, there's a possibility to escape this
path interpretation as described below.
MVC and Pipelines
The XQueryURLRewrite
servlet does more than just forward or redirect
requests: the response can be processed by passing it to a pipeline of views. "Views" are
again just plain Java servlets. The most common use of a view would be to post-processes the
XML returned from the primary URL, either through another XQuery or an XSLT stylesheet
(XSLTServlet
). XQueryURLRewrite
passes the HTTP response stream of
the previous servlet to the HTTP request received by the next servlet. It is fully possible to
extend eXist-db by adding in your custom own servlets.
Views may also directly exchange information through the use of request attributes (more on that below).
You define a view pipeline by adding a <view>
element to the <dispatch>
element returned by the controller. The <view>
element is a wrapper around another
sequence of <forward>
or <rewrite>
actions.
For example, assume we have some XML documents written in DocBook format, and that we wish
to render this as HTML by transforming the XML to HTML through an XSLT stylesheet
webapp/stylesheets/db2html.xsl
. This can be done by returning the
following <dispatch>
element from a controller.xq
:
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<view>
<forward servlet="XSLTServlet">
<set-attribute name="xslt.stylesheet" value="stylesheets/db2html.xsl"/>
</forward>
</view>
<cache-control cache="no"/>
</dispatch>
In this example there are no forwarding actions except for the view, so the request will
be handled by the servlet engine in the default way. The response is then passed to the
XSLTServlet
. A new HTTP POST request is created whose body is set to the
response data of the previous step. The XSLTServlet
gets the path to the
stylesheet from the request attribute xslt.stylesheet
and applies it to the
provided data.
If any step in the pipeline generates an error or returns an HTTP status code >= 400, the pipeline processing stops and the response is send back to the client immediately. The same happens if the first step returns with an HTTP status 304 (NOT MODIFIED), which indicates that the client can use the version it has cached.
We can also pass a request through more than one view. The following document applies two stylesheets in sequence:
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<!-- NOTE: query results are passed to XSLT servlet via a request attribute -->
<set-attribute name="xquery.attribute" value="model"/>
<view>
<forward servlet="XSLTServlet">
<set-attribute name="xslt.input" value="model"/>
<set-attribute name="xslt.stylesheet" value="xquery/stylesheets/acronyms.xsl"/>
</forward>
<forward servlet="XSLTServlet">
<clear-attribute name="xslt.input"/>
<set-attribute name="xslt.stylesheet" value="stylesheets/db2html.xsl"/>
</forward>
</view>
</dispatch>
The example also demonstrates how information can be passed between actions.
XQueryServlet
(which is called implicitly because the URL ends with
.xq
) can save the results of the called XQuery to a request attribute instead
of writing them to the HTTP output stream. It does so if it finds a request attribute named
xquery.attribute
, which should in turn contain the name of the request
attribute that the output should be saved to.
In the example above, xquery.attribute
is set to model
.
This causes XQueryServlet
to fill the request attribute model
with the results of the XQuery it executes. The query result will not be written to the HTTP
response as you might expect, instead at this point the HTTP response body remains empty as
the data is inside the request attribute.
Likewise, XSLTServlet
can take its input from a request attribute instead of
parsing the HTTP request body. The name of the request attribute should be given in attribute
xslt.model
. XSLTServlet discards the current request content (which is
empty anyway) and uses the data in the attribute's value as input for the transformation
process.
XSLTServlet
will always write to the HTTP response. The second
invocation of XSLTServlet
therefore needs to read its input from the HTTP
request body which contains the response of the first servlet. Since request attributes are
preserved throughout the entire pipeline, we need to clear the xslt.input
with an explicit call to clear-attribute
.
The benefit of exchanging data through request attributes is that we save one
serialization step: XQueryServlet
directly passes the node tree of its output as
a valid XQuery value, so XSLTServlet
does not need to parse it again.
The advantages become more obvious if you have two or more XQueries which need to exchange
information: XQuery 1 can use the XQuery extension function
request:set-attribute()
to save an arbitrary XQuery sequence to an attribute.
XQuery 2 then subsequently calls request:get-attribute()
to retrieve this value.
As it can directly access the data passed in from XQuery 1, no time is lost serializing and
deserializing the data.
Let's have a look at a more complex example: eXist-db's eXide web application needs to execute a user-supplied XQuery fragment. The results should be retrieved in an asynchronous way, so the user doesn't need to wait and the web interface remains usable.
Older versions of eXide's ancestor sandbox application used the
util:eval()
function to evaluate the query. However, this had
side-effects because util:eval()
executes the query within the context of the
parent query. Some features like module imports could not work properly. To avoid
util:eval()
, the controller code below passes the user-supplied query to
XQueryServlet
first, then post-processes each returned result and stores
it into a session for later use by the AJAX frontend:
if (starts-with($path, "/eXide/execute"))
then
let $query := request:get-parameter("qu", ())
let $startTime := util:system-time()
return
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<!-- Query is executed by XQueryServlet -->
<forward servlet="XQueryServlet">
<!-- Query is passed via the attribute "xquery.source" -->
<set-attribute name="xquery.source" value="{$query}"/>
<!-- Results should be written into attribute "results" -->
<set-attribute name="xquery.attribute" value="results"/>
<!-- Errors should be passed through instead of terminating the request -->
<set-attribute name="xquery.report-errors" value="yes"/>
</forward>
<view>
<!-- Post process the result: store it into the HTTP session and return the number of hits only. -->
<forward url="session.xq">
<clear-attribute name="xquery.source"/>
<clear-attribute name="xquery.attribute"/>
<set-attribute name="elapsed" value="{string(seconds-from-duration(util:system-time() - $startTime))}"/>
</forward>
</view>
</dispatch>
else
if (starts-with($path, "/sandbox/results/"))
then
(: Retrieve an item from the query results stored in the HTTP session. The
format of the URL will be /sandbox/results/X, where X is the number of the
item in the result set :)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="../session.xq">
<add-parameter name="num" value="{$name}"/>
</forward>
</dispatch>
The client passes the user-supplied query string in a request parameter, so the controller
has to forward this to XQueryServlet
somehow. XQueryServlet
has an
option to read the XQuery source from a request attribute, xquery.source
.
The query result will be saved to the attribute results
. The second XQuery,
session.xq
, takes the result and stores it into an HTTP session,
returning only the number of hits and the elapsed time.
When called through retrieve, session.xq
looks at parameter
num
and returns the item at the corresponding position from the query
results stored in the HTTP session.
Special Attributes Accepted by eXist-db Servlets
eXist-db's XQueryServlet
as well as the XSLTServlet
expect a few
predefined request attributes. The names of these attributes are listed below, and are
reserved, that is to say that they should not be used for other purposes.
XQueryServlet
-
xquery.attribute
-
Contains the name of a request attribute. Instead of writing query results to the response output stream,
XQueryServlet
will store them into the named attribute. The value of the attribute will be an XQuery Sequence. If no query results were returned, the attribute will contain an empty sequence. -
xquery.source
-
If set, the value of this attribute must contain the XQuery code to execute. Normally,
XQueryServlet
reads the XQuery from the file given in the request path. Use of thexquery.source
attribute allows you to overwrite this behaviour, e.g. if you want to evaluate an XQuery which was generated within the controller. -
xquery.module-load-path
-
The path which will be used for locating modules. This is only relevant in combination with
xquery.source
and tells the XQuery engine where to look for modules imported by the query. For example, if you stored required modules into the database collection/db/test
, you can setxquery.module-load-path
toxmldb:exist:///db/test
. If the query contains an expression:import module namespace test="http://exist-db.org/test" at "test.xq";
The XQuery engine will try to find the module
test.xq
in the filesystem by default, which may not be what you were expecting! Setting thexquery.module-load-path
allows you to configure this. -
xquery.report-errors
-
If set to
yes
, an error in the XQuery will not result in an HTTP error. Instead, the string message of the error is enclosed in an element<error>
and then written to the response stream. The HTTP Response's Status Code will remain unchanged.
XSLTServlet
-
xslt.stylesheet
-
The path to the XSLT stylesheet. Relative paths will be resolved against the current request URL, absolute paths against the context of the web application (
/exist
). To reference a stylesheet which is stored in the database, use an XML:DB URL likexmldb:exist:///db/styles/myxsl.xslt
. -
xslt.input
-
Contains the name of a request attribute from which the input to the transformation process should be taken. The input has to be a valid eXist-db XQuery Sequence.
This attribute is usually combined with the
xquery.attribute
attribute provided byXQueryServlet
and allows passing data between the two without additional serialization or deserialization overhead. -
xslt.user
-
The name of the eXist -db user account to use when reading and executing the stylesheet.
-
xslt.password
-
The password for the user given in
xslt.user
XSLTServlet
will attempt to map all other request attributes starting with the
prefix xslt.
into stylesheet parameters. So, for
example, if you set a request attribute xslt.myattr
it will be available
within the stylesheet as parameter $xslt.myattr
. For security reasons,
this is the only way to pass request parameters into the stylesheet; you can use your
controller.xq
to transform a HTTP Request parameter into a request
attribute and pass that on to the view.