Wiki source code of Short XWiki URLs

Version 34.2 by Vincent Massol on 2013/09/19

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 This tutorial shows you how to tune your XWiki platform by replacing the default URL scheme with a shorter scheme.
6
7 {{info}}
8 A short URL is an URL without the ##xwiki/bin/view## parts.
9 {{/info}}
10
11 = I. Application name =
12
13 The ##/xwiki/## part of the URL is the application name. It identifies the application that should process the request, and it allows a container to host more than one application. To change it you must refer to your container's documentation and find how to map the context path of a web application. For example on Tomcat it's enough to simply deploy the XWiki webapp in the ##webapps## directory, in a sub directory named after the application name you wish to use (e.g. ##webapps/myappname##).
14
15 A special case is when deploying XWiki as the ROOT application, which actually allows the application name part to be empty, so an URL can take the form ##server.com/bin/view/Space/Document##. Achieving this depends on the container, as there's no standard regarding the ROOT application. For example:
16 * in Tomcat, with the default configuration, all it takes is to deploy the XWiki web application in ##webapps##, in a sub directory named ##ROOT## (i.e. ##webapps/ROOT##).
17 * In Jetty, with the default configuration, all it takes is to deploy the XWiki web application in ##webapps##, in a sub directory named ##root##. Note that if you're using the Standalone distribution (which packages Jetty and HSQLDB) then you'll also need to:
18 ** Remove the existing ##webapps/root## directory which contains a redirect Servlet that automatically redirects root URLs to the ##xwiki## context. You won't need that anymore.
19 ** Rename the existing ##webapps/xwiki## directory into ##webapps/root##.
20 ** Remove the ##jetty/contexts/xwiki.xml## file and thus keep only the ##jetty/contexts/root.xml## file. Otherwise you'll get a warning in the console.
21
22 Refer to your container's documentation for more details.
23
24 = II. Servlet mapping name =
25
26 The second part is the hardest part to remove. It identifies the servlet that should process the page, which, for ##/bin/##, is the Struts servlet. Generically speaking, to get rid of ##/bin/##, you need to configure your system so that URLs matching ##/*## are mapped to the Struts Servlet (by default only ##/bin/*## URLs are mapped to the Struts Servlet).
27
28 However you need to be careful that the following prefixes do NOT go through the Struts Servlet (see your ##web.xml## to check their mappings):
29 * ##/resources/*## and ##/skins/*##: Statically served resources. These need to be served directly as static resources.
30 * ##/rest/*##: REST resources
31 * ##/xmlrpc/*##: XML-RPC resources
32 * ##*.gwtrpc##: GWT-RPC calls
33 * ##/webdav/*##: WebDav calls
34 * ##/XWikiService##: XWiki GWT Servlet
35 * ##/redirect##: The XWiki Redirect Servlet used to redirect to the home page when no page is specified in the URL
36
37 There are various alternate ways to achieve this as detailed below.
38
39 Now XWiki also generates URL and you can tell it to generate URLs without the ##bin/## part by adding this piece of code in ##xwiki.cfg##: {{code language="none"}}xwiki.defaultservletpath={{/code}} (this is not required with the UrlRewriteFilter solution since it rewrites outbound URLs too).
40
41 == UrlRewriteFilter ==
42
43 This is a [[framework offering a Servlet Filter>>http://www.tuckey.org/urlrewrite/]] allowing to rewrite URLs. Install it and drop the following configuration in ##WEB-INF/urlrewrite.xml##:
44
45 {{code language="xml"}}
46 <?xml version="1.0" encoding="utf-8"?>
47 <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 4.0//EN"
48 "http://www.tuckey.org/res/dtds/urlrewrite4.0.dtd">
49 <urlrewrite>
50
51 <rule>
52 <note>
53 Ensure that URLs ending with .gwtrpc are not modified.
54 </note>
55 <from>^/(.*)\.gwtrpc$</from>
56 <to type="forward" last="true">-</to>
57 </rule>
58
59 <rule>
60 <note>
61 En sure that URLs that must not be served by the Struts Servlet are not modified.
62 </note>
63 <from>^/(bin|resources|skins|rest|webdav|xmlrpc)/(.*)$</from>
64 <to type="forward" last="true">-</to>
65 </rule>
66
67 <rule>
68 <note>
69 For all other URLs we prepend the "/bin/" prefix so that they get routed to the XWiki Action Servlet.
70 </note>
71 <from>^/(.*)$</from>
72 <to type="forward">/bin/$1</to>
73 </rule>
74
75 <outbound-rule>
76 <note>
77 Rewrite outbound URLs to remove the "/bin" part.
78 </note>
79 <from>/bin/(.*)/(.*)$</from>
80 <to>/$1/$2</to>
81 </outbound-rule>
82
83 </urlrewrite>
84 {{/code}}
85
86 {{warning}}
87 The outbound URL rewriting [[works only with XWiki 5.2+>>http://jira.xwiki.org/browse/XWIKI-9430]].
88 {{/warning}}
89
90 == Configuring web.xml ==
91
92 You could be tempted to configure the XWiki's ##web.xml## file as follows below but **note that the WYSIWYG editor won't work with this solution**. The problem is that the ##/*## mapping will override the ##/*.gwtrpc## mapping. This is caused by the Servlet spec which says:
93
94 > The URL path mapping rules below are used in order. The first successful match is used with no further matches attempted:
95 > 1. The container will try to find an exact match of the path of the request to the path of the servlet. A successful match selects the servlet.
96 > 2. The container will recursively try to match the longest path-prefix. This is done by stepping down the path tree a directory at a time, using the '/' character as a path separator. The longest match determines the servlet selected.
97 > 3. If the last segment in the URL path contains an extension (e.g. .jsp), the servlet container will try to match a servlet that handles requests for the extension. An extension is defined as the part of the last segment after the last '.' character.
98 > 4. If neither of the previous three rules result in a servlet match, the container will attempt to serve content appropriate for the resource requested. If a "default" servlet is defined for the application, it will be used.
99
100 In any case if you don't use the WYSIWYG editor this could be a good solution so here it is just in case:
101 * Copy the mapping for the Struts servlet to also be activated for ##/*##:(((
102 {{code language="xml"}}
103 <servlet-mapping>
104 <servlet-name>action</servlet-name>
105 <url-pattern>/*</url-pattern>
106 </servlet-mapping>
107 {{/code}}
108
109 Be sure to leave the other mappings in place, so that ##/bin/## works, too.
110 )))
111 * Configure your container to statically serve ##/resources/*## and ##/skins/*## resources. By default we don't have any mapping for those in ##web.xml## which means they are statically served (by the Default Servlet). Configuring the Default Servlet is container-dependent unfortunately.
112 ** In Jetty, the container shipped with the XWiki installer, you will have to write:
113 *** For Jetty <= 6.x:(((
114 {{code language="xml"}}
115 <servlet>
116 <servlet-name>defaultSkins</servlet-name>
117 <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
118 <load-on-startup>1</load-on-startup>
119 </servlet>
120 <servlet-mapping>
121 <servlet-name>defaultSkins</servlet-name>
122 <url-pattern>/skins/*</url-pattern>
123 </servlet-mapping>
124 <servlet-mapping>
125 <servlet-name>defaultSkins</servlet-name>
126 <url-pattern>/resources/*</url-pattern>
127 </servlet-mapping>
128 {{/code}}
129 )))
130 *** For Jetty >= 7.x:(((
131 {{code language="xml"}}
132 <servlet>
133 <servlet-name>defaultSkins</servlet-name>
134 <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
135 <load-on-startup>1</load-on-startup>
136 </servlet>
137 <servlet-mapping>
138 <servlet-name>defaultSkins</servlet-name>
139 <url-pattern>/skins/*</url-pattern>
140 </servlet-mapping>
141 <servlet-mapping>
142 <servlet-name>defaultSkins</servlet-name>
143 <url-pattern>/resources/*</url-pattern>
144 </servlet-mapping>
145 {{/code}}
146 )))
147 ** In Tomcat, the default Servlet does not accept a parameter for changing the resource base, so you would need to write another default servlet.
148
149 == web.xml + Apache ==
150
151 Do as above but instead of mapping the Default Servlet, bypass the Servlet container at the web front-end level. For example, if you are using Apache httpd as a front-end, and assuming a webapp deployed as a ROOT webapp and an AJP connection between httpd and the servlet container, the following configuration allows you to serve skin files and static resources directly from httpd:
152
153 {{code}}
154 Alias /skins /usr/local/xwiki/skins
155 Alias /resources /usr/local/xwiki/resources
156 ProxyPass /skins/ !
157 ProxyPass /resources/ !
158 {{/code}}
159
160 Note that this solution will not work for the WYSIWYG editor since it needs that the ##.gwtrpc## URLs are directed to the XWiki Servlet. The following Apache configuration using URL rewriting achieves this:
161
162 {{code}}
163 Alias /skins /usr/local/xwiki/skins
164 Alias /resources /usr/local/xwiki/resources
165
166 RewriteEngine on
167
168 RewriteRule ^/+skins - [L]
169 RewriteCond %{REQUEST_URI} !\.gwtrpc$
170 RewriteRule ^/+resources($|/.*) - [L]
171
172 RewriteRule .* http://localhost:8080%{REQUEST_URI} [P,L]
173 ProxyPassReverse / http://localhost:8080/
174 {{/code}}
175
176 {{warning}}Note that while this has been tested to work with XWiki 3.1, it seems it [[doesn't work with 4.5.4 and later>>http://jira.xwiki.org/browse/XWIKI-9455]].{{/warning}}
177
178 == Lighttpd + Jetty ==
179
180 I used lighttpd, but I assume it can be done with other webservers too. This is the configuration I used in the lighttpd config (note that my xwiki folder has been moved to ##/usr/share/jetty/webapps/root## (no 'xwiki' at all)):
181
182 {{code language="none"}}
183 $HTTP["host"] =~ "^www\.domain\.com$" {
184 # ensure all requests for .gwtrpc files go through to java server
185 # we can put this rule first as a higher priority, which java couldn't do
186 $HTTP["url"] =~ "\.gwtrpc$" {
187 proxy.server = ( "" => (( "host" => "127.0.0.1", "port" => 8080 )))
188 }
189 # otherwise, we can handle the static resources
190 else $HTTP["url"] =~ "^/resources/" {
191 alias.url += ( "/resources" => "/usr/share/jetty/webapps/root/resources" )
192 }
193 # otherwise, we can handle the static resources
194 else $HTTP["url"] =~ "^/skins/" {
195 alias.url += ( "/skins" => "/usr/share/jetty/webapps/root/skins" )
196 }
197 # and here is the primary server
198 else $HTTP["host"] =~ "^www\.domain\.com$" {
199 proxy.server = ( "" => (( "host" => "127.0.0.1", "port" => 8080 )))
200 }
201 }
202 # redirect anything.domain.com to www.domain.com
203 else $HTTP["host"] =~ "\.domain\.com$" {
204 url.redirect = ( "^/(.*)" => "http://www.domain.com/$1" )
205 server.name = "www.domain.com"
206 }
207 # redirect domain.com to www.domain.com
208 else $HTTP["host"] =~ "domain\.com$" {
209 url.redirect = ( "^/(.*)" => "http://www.domain.com/$1" )
210 server.name = "www.domain.com"
211 }
212 {{/code}}
213
214 So lighttpd will serve any static content unless it has ##.gwtrpc## on the end of the URL.
215
216 If you use Nginx as a web-server, just add three more locations and set ##root## to them. By ##try_files## Nginx checks static content presence and if doesn't exist, redirect it to the Tomcat (we expect dynamic content in this case, including all ##*.gwtrpc## requests).
217
218 {{code}}
219 location /skins/ {
220 root /var/lib/tomcat7/webapps/ROOT;
221 }
222
223 location /resources/ {
224 try_files $uri $uri/ @fallback;
225 root /var/lib/tomcat7/webapps/ROOT;
226 }
227
228 location @fallback {
229 proxy_pass http://localhost:8080;
230 }
231 {{/code}}
232
233 In the example above XWiki installed as ROOT application in Tomcat. Change path to your XWiki application accordingly.
234
235 Then in ##web.xml##, I changed the ##gwtrpc## mapping to:
236
237 {{code language="xml"}}
238 <servlet-mapping>
239 <servlet-name>gwtrpc</servlet-name>
240 <url-pattern>/resources/*</url-pattern>
241 <url-pattern>/skins/*</url-pattern>
242 </servlet-mapping>
243 {{/code}}
244
245 Since we are using a url-pattern of ##/path/##, it will be specific enough to be a higher priority than the ##/## pattern we'll use next. And since the only thing that will come through via resources or skins will be ##gwtrpc##, then we can be sure it's ok. Note that only ##resources## is required, but I did both anyway.
246
247 Now, as described above, add a rule to catch everything else and redirect it to your XWiki servlet:
248
249 {{code language="xml"}}
250 <servlet-mapping>
251 <servlet-name>action</servlet-name>
252 <url-pattern>/*</url-pattern>
253 </servlet-mapping>
254 {{/code}}
255
256 = III. Struts action name =
257
258 The third part, ##/view/##, identifies the struts action that should process a request. So this tells what we want to do with the document, ##/view/## it, ##/edit/## it or ##/delete/## it, for example. The XWiki platform allows this part to be missing, considering that the default action is to just display the document, so an URL like ##server.com/bin/Space/Document## will work out of the box.
259
260 Even more, the URL factory, the component that generates URLs, can be configured to skip this part when the action is ##/view/##. To do this write this code in ##xwiki.cfg##: {{code language="none"}}xwiki.showviewaction=0{{/code}}.
261
262 = IV. Error Page =
263
264 At the ##WEB-INF/web.xml##, the ##location## of the 404 error code needs to be changed accordingly. For example:
265
266 {{code language="xml"}}
267 <error-page>
268 <error-code>404</error-code>
269 <!--<location>/xwiki/bin/view/Main/DocumentDoesNotExist</location>-->
270 <location>/bin/Main/DocumentDoesNotExist</location>
271 </error-page>
272 {{/code}}
273
274 = V. Conclusion =
275
276 After performing all these changes, you should be able to access documents with URLs like:
277
278 * server.com/Space/Document
279 * server.com/Space/ (pointing to Space.WebHome)
280 * server.com/Document (pointing to Main.Document)
281 * server.com/ will show Main.WebHome, without any redirect.
282
283 As a bonus, these changes are backwards compatible, meaning that any currently working URL will also work with these changes performed, so you won't have any broken bookmarks.

Get Connected