XQuery/URL 重写基础
您希望将简单、简短、直观且设计精良的传入 URL 映射到数据库中的适当结构。您希望实现“酷 URL”的理想目标,并使您的 XQuery 应用程序在您的数据库内以及其他数据库中可移植。
eXist 中的典型 URL 格式类似于以下内容
http://www.example.com:8080/exist/rest/db/app/search.xq?q=apple
您希望用户通过一个更酷、更少依赖于平台的 URL(例如以下内容)访问此页面
http://www.example.com/search?q=apple
为了将您的 URL 转换为后者的酷形式,您需要了解 eXist 中 URL 的基本原理。
从根本上说,eXist 的 URL 由 3 部分组成
- 主机名和端口:在上面的示例中,主机名为 www.example.com,端口为 8080
- Web 应用程序上下文:在上面的示例中,上下文为 /exist
- 路径:在上面的示例中,路径为 /rest/db/app/search.xq?q=apple
自定义 eXist URL 可能意味着针对 3 部分中的 1 部分或多部分。
以下某些方法使用 eXist 的 URL 重写功能,从概念上讲,该功能可以让您的应用程序遵循 MVC(模型-视图-控制器)设计。eXist 1.5 预先配置了体现这些原理的工作设置
- 位于 /db/myapp/ 下方的集合,通过 REST servlet 通过 /exist/rest/db/myapp/ 公开,同时可以通过 URL 重写的方式在 /exist/apps/myapp/ 位置访问。
- 在 /db/myapp/ 中放置一个 controller.xql 将确定此集合中的数据(即模型)如何通过 URL 重写创建的空间呈现 - 可以说:它控制模型的视图。
请继续阅读以下内容,了解如何在 eXist 1.4.1 版本中配置 URL 重写以获得相同的设置。
eXist 的默认 Web 服务器(Jetty)的端口为 8080,它在 $EXIST_HOME/tools/jetty/etc/jetty.xml 文件的第 51 行设置。您可以修改此文件,也可以在启动时通过设置 -Djetty.port=80 标志来设置端口。
请注意,更改端口的方式因启动 eXist 的方式而异。如果您从 bin/startup 使用 UNIX 或 DOS shell 启动 eXist,则必须更改 startup.sh 或 startup.bat 文件。如果您使用 UNIT tools/wrapper/exist.sh 工具或 Windows 服务自动启动 eXist,则需要更改 jetty.xml 文件。
重新启动 eXist。现在,进行了此更改后,您的 URL 将变为
http://www.example.com/exist/rest/db/app/search.xq?q=apple
而不是
http://www.example.com:8080/exist/rest/db/app/search.xq?q=apple
在 Unix(包括 Mac OS X)和 Linux 上,您需要以 root 身份运行 eXist 才能绑定到端口 80。否则服务器将无法启动。
要将服务器的 Web 应用程序上下文从 /exist 修剪到 /,请转到同一个 $EXIST_HOME/tools/jetty/etc/jetty.xml 文件的第 134 行,并将以下内容更改为
来自
<Arg>/exist</Arg>
至
<Arg>/</Arg>
重新启动 eXist。现在,进行了此更改后,您的 URL 将变为
http://www.example.com/rest/db/app/search.xq?q=apple
而不是
http://www.example.com/exist/rest/db/app/search.xq?q=apple
在自定义 URL 的其余部分时,eXist 的 URL 重写功能变得既强大又具有挑战性。(有关 eXist 中 URL 此方面的完整文档,请参阅 eXist 关于 URL 重写的文档。)
eXist URL 重写的核心是一个文件,它控制其网站部分的 URL;此文件称为 controller.xql,您将其放置在 Web 应用程序目录的根目录。它控制其目录及其子目录中的所有 URL(尽管子目录可以包含自己的 controller.xql 文件 - 稍后会详细介绍)。如果您的 Web 应用程序存储在文件系统上,则您可能将“controller.xql”放置在 /webapp 目录中。如果您的 Web 应用程序存储在 eXist 数据库中,则您可能会将其放在 /db 集合中。在我们正在运行的示例应用程序中,您将把 controller.xql 存储在哪里?
当前形式:http://www.example.com/rest/db/app/search.xq?q=apple
目标 URL:http://www.example.com/search?q=apple
controller.xql 的自然位置是 /db/app 目录,因为 search.xq 文件(以及推测的其他 .xq 文件)存储在此目录或其下方。
鉴于应用程序的根 controller.xql 的此位置,我们需要告诉 eXist 在“/db/app”目录中查找根 controller.xql。我们通过编辑 /webapp/WEB-INF 文件夹中的 controller-config.xml 文件来实现。注释掉第 27-28 行,并添加以下内容
<root pattern="/*" path="xmldb:exist:///db/app"/>
然后重新启动 eXist。此新的根模式将把所有 URL 请求(/*)转发到 /db/app 目录。现在,进行了此更改后,您的 URL 将变为
http://www.example.com/search.xq?q=apple
而不是
http://www.example.com/rest/db/app/search.xq?q=apple
自定义 URL 的最后一步是创建一个 controller.xql 文件,该文件将接收对 /search?q=apple 的请求,并将此请求与 q 参数一起传递给 search.xq 文件。
一个可以实现此目标的基本 controller.xql 文件如下所示
xquery version "1.0";
(:~
Default controller XQuery.
Forwards '/search' to search.xq in the same directory
and passes all other requests through.
:)
(: Root path: forward to search.xq in the same collection
(or directory) as the controller.xql :)
if (starts-with($exist:path, '/search')) then
let $query := request:get-parameter("q", ())
return
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="search.xq"/>
<set-attribute name="q" value="{$query}"/>
</dispatch>
(: Let everything else pass through :)
else
<ignore xmlns="http://exist.sourceforge.net/NS/exist">
<cache-control cache="yes"/>
</ignore>
请注意,$exist:path 变量是 eXist 可供 controller.xql 文件使用的变量。$exist:path 的值始终等于请求 URL 中控制器根目录之后的部分。对“/search”的请求将导致 $exist:path 为“/search”。
将此查询保存为 controller.xql,并将其放在 /db/app 目录中。恭喜!我们的 URL 现在已成为我们设想的非常酷的形式
http://www.example.com/search?q=apple
而不是
http://www.example.com/search.xq?q=apple
此 $exist:path 变量是 controller.xql 文件可用的 5 个此类变量之一。(有关每个变量的更多信息,请参阅完整的 URL 重写文档。)这些变量可以让您非常精细地控制请求的 URL 以及 eXist 对应用程序资源的内部路径。
由于您可能希望根据 URL 参数(例如 q=apple)重新路由 URL 请求,您可能希望使用 request:get-parameter() 函数检索 URL 参数,然后使用 <add-parameter> 元素显式地将此参数传递给目标查询,如示例 controller.xql 文件中所示。
因此,在自定义 URL 的“路径”部分时,我们实际上关注了 3 个项目
- 其根控制器目录的根模式和路径(回想 controller-config.xml 文件中的 <root> 元素)
- 控制器目录后的路径的其余部分
- 作为 URL 部分包含的URL 参数
此简单示例只触及了 URL 重写功能的表面。使用 URL 重写不仅可以让您的应用程序拥有“酷 URL”,而且还可以使您的应用程序更具可移植性,无论是在您的服务器上,还是在将您的应用程序移植到其他服务器上。
如果您希望主应用程序位于 /db/app 中,但仍希望访问存储在文件系统上的应用程序(例如管理员应用程序('/webapp/admin')),请在 controller-config.xml 中添加一个 <root> 元素,声明您希望与文件系统的 /webapp 目录关联的根模式。将您当前的根元素替换为以下内容
<root pattern="/fs" path="/"/>
<root pattern="/*" path="xmldb:exist:///db/app"/>
所有以/fs开头的 URL 请求将被转发到文件系统的webapp目录。所有其他 URL 将继续转发到/db/app目录。
虽然你只需要一个controller.xql(甚至没有)就可以正常运作,但 eXist 允许 controller.xql 文件被放置在根控制器层次结构的任何级别,如controller-config.xml 中的 <root> 元素定义的那样。这使得 controller.xql 文件能够高度针对特定目录的关注点。eXist 会搜索与 URL 请求的深度级别匹配的最深层次 controller.xql 文件,向上遍历至根控制器.xql。
确保你的条件表达式以正确的顺序排列,以便规则按照该顺序进行评估,并且没有规则被无意中首先评估。换句话说,如果另一个规则匹配以 '/sea' 开头的 URL,那么 URL 重写器将始终将 '/search' URL 传递给该规则,而不是你的 '/search' 规则。
controller.xql 中的代码除了通常的变量之外,还会传递一些其他变量。下面的 controller.xql 不进行任何转发,而是打印其值,以及请求文档的路径(如果存在)...
xquery version "1.0";
declare namespace exist="http://exist.sourceforge.net/NS/exist";
import module namespace text="http://exist-db.org/xquery/text";
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;
let $document := concat($exist:root, (: $exist:prefix, :) $exist:controller, $exist:path)
return
<dummy>
<exist-root>{$exist:root}</exist-root>
<exist-prefix>{$exist:prefix}</exist-prefix>
<exist-controller>{$exist:controller}</exist-controller>
<exist-path>{$exist:path}</exist-path>
<exist-resource>{$exist:resource}</exist-resource>
<document>{$document}</document>
</dummy>
Joe Wicentowski 在 2009 年 10 月 19 日的周一,为 eXist-open 邮件列表贡献了这篇文章的核心内容。随后,Dan McCreary 和 Joe Wicentowski 将其编辑成现在的形式。