diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt index 21ea53d7..1e3cf6c9 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt @@ -1,6 +1,6 @@ xquery version "3.1"; -declare namespace exist = "http://exist.sourceforge.net/NS/exist"; +declare namespace exist="http://exist.sourceforge.net/NS/exist"; declare variable $exist:path external; declare variable $exist:resource external; @@ -9,7 +9,7 @@ declare variable $exist:prefix external; declare variable $exist:root external; - - - + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt index ffd70d35..c446258a 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt @@ -1,34 +1,35 @@ -if (starts-with($path, '/sandbox/execute')) -then - let $query := request:get-parameter("qu", ()) - let $startTime := util:system-time() - return - - - - - - - - - - - - - - - - - - - -else if (starts-with($path, '/sandbox/results/')) +if (starts-with($path, "/eXide/execute")) then + let $query := request:get-parameter("qu", ()) + let $startTime := util:system-time() + return + + + + + + + + + + + + + + + + + + + +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 :) - - - - \ No newline at end of file + + + + diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-19.xml b/src/main/xar-resources/data/urlrewrite/listings/listing-19.xml index b1362e8a..512a32c3 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-19.xml +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-19.xml @@ -1 +1 @@ - + diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt index 6412d585..80958456 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt @@ -1,6 +1,6 @@ xquery version "3.1"; -declare namespace exist = "http://exist.sourceforge.net/NS/exist"; +declare namespace exist="http://exist.sourceforge.net/NS/exist"; declare variable $exist:path external; declare variable $exist:resource external; @@ -9,8 +9,8 @@ declare variable $exist:prefix external; declare variable $exist:root external; - - - - + + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-22.xml b/src/main/xar-resources/data/urlrewrite/listings/listing-22.xml new file mode 100644 index 00000000..0e972391 --- /dev/null +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-22.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-3.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-3.txt index 1062890e..e8789e1c 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-3.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-3.txt @@ -1,4 +1,4 @@ -if ($exist:path eq '/') then - - - \ No newline at end of file +if ($exist:path eq "/") then + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-7.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-7.txt index 3529f262..bc695fe7 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-7.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-7.txt @@ -1,6 +1,8 @@ - + + + - \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/urlrewrite.xml b/src/main/xar-resources/data/urlrewrite/urlrewrite.xml index 585a3107..84ee0249 100644 --- a/src/main/xar-resources/data/urlrewrite/urlrewrite.xml +++ b/src/main/xar-resources/data/urlrewrite/urlrewrite.xml @@ -1,4 +1,7 @@ -
+ + +
URL Rewriting 1Q23 @@ -8,88 +11,111 @@ - How to use controller.xq files to map URLs to resources in eXist-db. + How to customize a web application's URLs using controller.xq scripts. Introduction - Good Web Applications provide meaningful and consistent URLs to the user. URL - Rewriting is one possible mechanism for providing short, human-readable URLs that - provide access to the often complex heirarchy of XQuery modules, HTML files, data, and other - resources that are combined into an XQuery Web Application. Another alternative mechanism to - URL Rewriting that is directly supported by eXist-db is 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. It is also possible to manage URL mapping or rewritting outside of eXist-db - by placing a Reverse Proxy between eXist-db - and the end-user. - A typical URL Rewriting operation might be handled as follows: + >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 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 looks for an XQuery Main Module to - interpret the rest of the URL (see ). By convention - this Main Module should be called controller.xq (or in older applications - controller.xql). The default configuration will work with a Main Module - saved with this name in the base collection of an application. - - - The controller.xq examines the URL using the provided , and may produce an XML document in the + 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, called + controller-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 named controller.xq (or in older + applications controller.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 , and produces an XML-formatted directive in the . - - - The XQueryURLRewrite servlet interprets the Controller XML document as - a series of instructions on what to do next. These instructions may be as simple as - forwarding to a resource on (or off) the server, or it may be as complex as a pipeline - using the Model-View-Controller pattern (see ) and other servlets such as eXist-db's or . - - + + + 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 ) + and other servlets such as eXist-db's or . 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: A Simple Implementation - Consider a document similar to the one you are currently reading; a direct URL pointing - to that XML document might be: /exist/apps/doc/data/urlrewrite.xml. By - accessing this URL, eXist-db would only return the XML document's content, however it may be - preferable that users should get a properly formatted HTML page instead that they can easily - consume in their web-browser. We may wish to make the HTML rendered version of that document - accessible through a simplified URL like: /exist/apps/doc/urlrewrite. - To achieve this we need a mechanism to map or rewrite a given URL to an application - specific endpoint. So in the simplest case, visiting the URL: - /exist/apps/doc/urlrewrite would initiate an XQuery process that locates the - source document, transforms it into HTML, and finally returns the HTML page. - Any XQuery Main Module named controller.xq is invoked for all URL - paths targeting the collection in which it resides. It has access to a number of pre-bound with details about the request, including - $exist:resource, containing the name of the resource (without path - components) the request tries to access; also the $exist:controller - variable which is bound to the path of the database collection that the - controller.xq is located in. - For example, one may want to direct all requests to + 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 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 is responsible for converting the XML document - named in place of {resource} into an HTML page. To achieve this, one - could create the following XQuery Main Module and store it in the database at the path: + 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: - This example controller returns a simple dispatch document which will be - passed back to the URL Rewriting framework. The forward element instructs the - framework to call the URL modules/transform.xq relative to the collection - in which the controller resides. It adds an HTTP request parameter, named - doc, which indicates the resource to be transformed. The receiving - query can access this parameter using the XQuery HTTP Request Module by calling the XQuery - function: request:get-parameter("doc", ()) in order to retrieve the requested - article. + 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 URL Rewriting framework allows you to turn - simple requests into complex processing pipelines, involving any number of steps. + 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: @@ -101,68 +127,95 @@ - Every dispatch element must contain at least one step, followed by an - optional view element grouping any number of follow up steps. In the 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 to the second - step via the HTTP request (and can be accessed via the XQuery function: - request:get-data()). + 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 XQuery Main Module is expected to return a - single XML document, the root element of which must be either dispatch - xmlns="http://exist.sourceforge.net/NS/exist", or ignore - xmlns="http://exist.sourceforge.net/NS/exist". - Note that all of the elements discussed in this article must be in the + 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 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. + + The <tag>dispatch</tag> 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. + + The <tag>ignore</tag> Action - The ignore action simply bypasses the URL Rewriting process. This may be + 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 . - The ignore element may include an optional cache-control element. + The ignore element may include an optional cache-control element. The <tag>redirect</tag> Action - The redirect action redirects the client to another URL, indicating that the - other URL must be used for subsequent requests. Note that this is implemented by eXist-db - returning an HTTP 302 redirect response to the client. This causes the client to issue a new - request, and this can potentially trigger the controller again; care must be taken to avoid - creaing an un-exitable loop. - The URL to the redirect element is given in an attribute named url. + 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: - A redirect will be visible to the user: for instance the user's web-browser will be - updated to show the specified new URL. + 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 <tag>forward</tag> Action - The forward action forwards the current request to another request path or - servlet. Unlike the redirect action, the forward - action is performed server-side within eXist-db (via the RequestDispatcher of - the servlet engine); therefore the client cannot see where the request was forwarded - to. - The element is allowed the following attributes, and must define either - url or servlet attributes: + 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. + 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. @@ -184,12 +237,17 @@ these in the article . + + 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 for an example. + 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 for an example. @@ -197,7 +255,7 @@ method - The HTTP method (POST, GET, PUT ...) to use when passing the request to the next + 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. @@ -212,7 +270,9 @@ The <tag>view</tag> Action - The view action is used to define processing pipelines, and may follow redirect or forward actions. + 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: @@ -227,45 +287,59 @@ 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. + 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 <tag>set-attribute</tag> Option - The set-attribute option sets a request attribute to the given value. - Attributes are internal to the pipeline, and are part of the Java Servlet specification, - they and are not related to the HTTP Request or HTTP Response. + 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. 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 <tag>clear-attribute</tag> Option + + The <tag>clear-attribute</tag> Option The clear-attribute option clears a request attribute. The name of the request attribute is read from the name attribute. - Unlike parameters, request attributes will be visible to subsequent steps in the - processing pipeline. They only need to be explicitly cleared once they are no longer needed - by the user. eXist-db places no requirement on the user having to ever clear the - attributes. - The <tag>set-header</tag> Option + 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 <tag>set-header</tag> 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. - The HTTP response is shared between all steps in the pipeline, so all following steps will be able to see the change. + The HTTP response is shared between all steps in the pipeline, so all following steps + will be able to see the change. + The <tag>cache-control</tag> Option The cache-control element is used to tell the URL Rewriting framework if the - current URL that is being rewritten 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 XQuery Main Module only needs to be - executed once for each distinct input URL. Subsequent requests for the same URL will be served from the cache. + 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. - Note: only the URL rewrite rule is cached, the HTTP response itself is not cached! The cache-control setting is unrelated to any HTTP Cache Headers in the HTTP Response, and is unrelated to any client-side caching within a web-browser. + 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. @@ -273,10 +347,10 @@ Controller Variables - Several variables are pre-bound and made available to the controller.xq - XQuery Main Module for convenience. For example, if the request path is - /exist/tools/sandbox/get-examples.xq the following variables will be - bound to the the values: + 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: Table title @@ -284,163 +358,175 @@ - Variable Name - Variable Value + + Variable Name + + + Variable Value + - $exist:prefix + $exist:root - /tools + xmldb:exist:///db/apps - $exist:controller + $exist:prefix - /sandbox + /apps - $exist:path + $exist:controller - /get-examples.xq + /sandbox - $exist:resource + $exist:path - get-examples.xml + /get-examples.xq - $exist:root + $exist:resource - xmldb:exist:///db + get-examples.xq
- You do not need to explicitly declare the variables or the namespace. However doing so is - considered best practice, and you can add an external declaration for these variables at the - top of your XQuery. For instance: - declare namespace exist = "http://exist.sourceforge.net/NS/exist"; + 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; -declare variable $exist:controller external; -declare variable $exist:prefix external; -declare variable $exist:root 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:path + $exist:root - 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. + 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: + - exist:resource + + $exist:prefix + - Is bound to the part of the URL after the last /; this is usually - pointing to a resource. - For instance: example.xml. + 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 + $exist:controller - Is bound to the part of the URL leading to the current controller.xq. - For example, if the request path is /xquery/test.xq and the + 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 the xquery collection, the - $exist:controller variable will be bound to the value - /xquery. + $exist:controller variable will be bound to the value + /sandbox. - exist:prefix + $exist:path - 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 /tools to a - collection in the database (see below). In this case, the - $exist:prefix variable would be bound to the value - /tools. + 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:root - + $exist:resource - 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: - + Is bound to the part of the URL after the last /; this is usually + pointing to a resource. + For instance: example.xml. - These variables are recommended for simplicity and convenience. 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. + 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 + Accessing Resources Not Stored in the Database If your controller.xq is stored in a database collection, all relative and/or - absolute URLs within the controller will be resolved against the database, not the file + 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. + 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: @@ -450,27 +536,32 @@ declare variable $exist:root external; context of the servlet engine, typically /exist/. In your HTML, you can now write paths such as: - 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. + 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 XQuery Main Module must be named - controller.xq. If you wish anonymous users to be able to be influenced by - the XQuery URL Rewritting framework then you need to ensure that the guest user - has execute permissions granted on your controller.xq files. - By default, URL Rewritting framework will try to guess the path to the controller XQuery - Main Module by looking at the request path, starting with the most specific collection, and - then looking into each parent collection until the controller is found or the root of the - database has been reached. - This can be configured and over-ridden in eXist-db's - 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. + 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: @@ -479,50 +570,47 @@ declare variable $exist:root external; - root elements that define the root for a file system or db collection hierarchy + 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 a - servlet mapping in $EXIST_HOME/etc/webapp/WEB-INF/web.xml. The advantage is - that the XQueryURLRewrite servlet (which implements the URL Rewriting framework). becomes a - single point of entry for the entire web application and we don't need to handle any of the - servlet paths in the main controller. - For example, if we registered a servlet mapping for /rest in - web.xml, we would need to make sure that this path is ignored in our main - controller.xq. However, if the mapping is done via - controller-config.xml, XQueryURLRewrite will already have handled the - path, which then won'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: + 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: - This means that paths starting with /tools will be mapped to the - collection hierarchy below /db/www. Everything else is handled by the catch - all pattern pointing to the root directory of the webapp (by default corresponding to - $EXIST_HOME/etc/webapp). For example, the URL - http://localhost:8080/exist/tools/admin/admin.xq - will be handled by the controller stored in database collection - /db/www/admin/ (if there is one) or will directly resolve to - /db/www/admin/admin.xq. In this case, all relative or absolute URLs - within the controller will be resolved against the database, not the file system. However, - there's a possibility to escape this path interpretation as described below. + 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 + 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). + 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 @@ -538,8 +626,10 @@ declare variable $exist:root external; 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. + 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: @@ -553,35 +643,49 @@ declare variable $exist:root external; 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 would normally 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. + 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: the XQuery sandbox 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 the sandbox used the - util:eval - function to evaluate the query. However, this has side-effects because - util:eval - executes the query within the context of another query. Some features like module imports will not work properly this way. To avoid - util:eval, the controller code below passes the user-supplied query to XQueryServlet first, then post-processes the returned result and stores it into a session for later use by the AJAX frontend: + 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: - 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. + 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. @@ -627,14 +731,13 @@ declare variable $exist:root external; 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 set - xquery.module-load-path - to - xmldb:exist:///db/test. If the query contains an expression: - import module namespace test = "http://exist-db.org/test" at "test.xq"; + 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 set + xquery.module-load-path to xmldb: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 the xquery.module-load-path allows you to configure this.