This answer, which covers only basic URL rewriting, assumes that the eXist application is built using what I'll call "basic" eXist app architecture:
- The app, which we'll call "my-app", is stored in
/db/apps/my-app
.
- No modifications have been made to the Jetty configuration files in
EXIST_HOME/tools/jetty/etc
, the XQuery servlet configuration file in EXIST_HOME/webapp/WEB-INF/web.xml
, or the base configuration for eXist's URL rewriting at webapp/WEB-INF/controller-config.xml
.
- This means the application can be accessed at
http://localhost:8080/exist/apps/my-app
. (If the goal is to serve this application via a URL like http://my-server/
and have this routed to eXist at http://localhost:8080/exist/apps/my-app
, this is best handled by a reverse proxy server, which is beyond the scope of "basic" eXist app architecture.)
- We will handle URL rewriting via eXist's URL rewriting facility - i.e., writing a
controller.xql
file - rather than via RESTXQ.
With these assumptions, we can create completely custom URLs, allowing us to take a URL like:
http://localhost:8080/exist/apps/my-app/doc.xq?filename=my-document.xml
and make this same resource available via:
http://localhost:8080/exist/apps/my-app/doc/my-document
To accomplish this, we need to create a new XQuery main module, named controller.xql
(it must be called this exactly, and we'll call it the app's "controller") in the root collection of your app: /db/apps/my-app/controller.xql
. This is a special module, which eXist looks for first when a request comes for a path in the /apps
URL space (e.g., http://localhost:8080/exist/apps/...
). While typically an app will have only one controller, eXist supports multiple controllers; eXist looks in the target collection and then up the collection tree, from deepest branch up to the root /db/apps
collection.
The purpose of the controller is to take information about the request - the path requested and other information about the app's context - and return a special kind of directive, which tells eXist how to route the request. The key information about the request is exposed to your query in a series of external variables (variables you don't have to define that eXist sets for you and that you can reference), including, most importantly, $exist:path
- the portion of the request URL that comes after the path to the collection containing the controller. So in the URL above, $exist:path
would equal /doc/my-document
.
Now, let's create the directive that takes this path and forwards this request (formulated using with the filename
parameter to your actual endpoint:
xquery version "3.1";
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;
if (starts-with($exist:path, "/doc/")) then
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/doc.xq">
<add-parameter name="filename" value="{$exist:resource}.xml"/>
</forward>
</dispatch>
else
<ignore xmlns="http://exist.sourceforge.net/NS/exist"/>
In the first block of code (the prolog), this controller explicitly declares the variables that eXist provides to the controller. In the main portion of the query, the conditional expression handles two cases:
If the path requested begins with /doc/
, then we want the request to be handled by doc.xq
- an XQuery module in our app at /db/apps/my-app/doc.xq
. Rather than type this full path, we can replace /db/apps/my-app
with $exist:controller
- which is the database path to the collection where the active controller is found. We also know that doc.xq
requires a filename
parameter, so we explicitly construct that from the $exist:resource
variable, which always provides us with the portion of the requested URL after the final slash - e.g., my-document
in our example URL above. From the perspective of the query that the controller forwards the request to (e.g., doc.xq
), it thinks it has received the request directly, and it can access request parameters via the request:get-parameter()
function. For example, doc.xq
could simply contain:
xquery version "3.1";
let $doc := request:get-parameter("filename", ())
return
<p>Who's looking for {$filename}?</p>
And the request for the URL above would return <p>Who's looking for my-document.xml?</p>
.
For any other requests, we will let these through without performing any forwarding or other URL action.
If you are using eXist's HTML templating facility, you will likely be forwarding requests to a template (document.html
) instead of a query (doc.xq
). In this case, the directive gets a little more complicated:
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/document.html"/>
<view>
<forward url="{$exist:controller}/modules/view.xql">
<add-parameter name="filename" value="{$exist:resource}.xml"/>
</forward>
</view>
</dispatch>
Here, we are forwarding the request to document.html
, but then the result is passed through eXist's templating handler module, which is conventionally stored in your app's modules/view.xql
file. (Note that the <add-parameter>
directive has moved to the 2nd <forward>
directive.)
The other external variables (namely, $exist:root
and $exist:prefix
) and other kinds of directives (namely, <redirect>
and other sub-elements) are described in the eXist documentation's page on URL Rewriting, http://exist-db.org/exist/apps/doc/urlrewrite. While the page currently warns that it is out of date, I think it is still a good resource.
The strength of eXist's URL Rewriting facility is that you can use the full expressiveness of XQuery to determine how your application routes and receives URLs. The weakness is that the controller can become a long chain of conditionals that is hard to maintain; just try to keep the controller logic as simple as possible. It can also take some time to learn the external variables; I'd suggest making ample use of logging (via the util:log
or console:log
functions to output the values of the external variables and other information about the request, as available via request:get-url()
and the other request:get-*
functions) to see what's going on with each request, until you get the hand of it. You may also find it instructive to survey controller files in other apps whose source code is available, and ask more questions here!