| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE document PUBLIC "//xdoc/" "http://maven.apache.org/dtd/xdoc_1_0.dtd"> |
| <!-- |
| Copyright 2005 The Apache Software Foundation |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| <document> |
| <properties> |
| <title>Configuring Friendly URLs</title> |
| </properties> |
| <body> |
| |
| <section name="Configuring Friendly URLs"> |
| <p> |
| Earlier versions of Tapestry have had a long-standing tradition of |
| <em>really ugly URLs</em> |
| . Because the framework generates the URLs and is also responsible for parsing and |
| dispatching on them in later requests, it was not seen as an issue. |
| </p> |
| |
| <p> |
| In fact, the ugly URLs |
| <em>do</em> |
| cause some problems: |
| </p> |
| |
| <ul> |
| <li> |
| Since all requests are routed through a single servlet (typically mapped to |
| <code>/app</code> |
| ), J2EE declarative security, which is path-based, is defeated. |
| </li> |
| <li> |
| Ugly URLs tend to be longer than friendly URLs, which can make a difference when |
| creating a WML application. |
| </li> |
| <li> |
| A single directory may contain all the artifacts (HTML templates, |
| specifications, properties files) for all the pages in an entire application. |
| There isn't a sanctioned approach to organizing things into subdirectories. |
| </li> |
| <li> |
| The reliance on query parameters means that common search engines will only see |
| a tiny fraction of the application. |
| </li> |
| </ul> |
| |
| <p> |
| Starting with 4.0, |
| <em>friendly URLs</em> |
| are integrated directly into framework (in 3.0 an ambitious, but more limited, patch |
| was required). |
| </p> |
| |
| <span class="warn"> |
| <strong>Warning:</strong> |
| <p> |
| For security purposes, enabling friendly URLs implies that pages are no longer |
| accessible via their ugly URL counterpart. This is not the case. If a malevolent |
| user can either guess - or via cookies identify - your servlet path, they can |
| construct an ugly URL to a resource that is protected via security and gain access |
| to the protected resource. |
| </p> |
| </span> |
| |
| <p>Friendly URLs are divided into two concerns:</p> |
| |
| <ul> |
| <li> |
| Converting information normally stored as a query parameter into part of the URL |
| path. |
| </li> |
| <li>Parsing the path to restore the information previously encoded.</li> |
| </ul> |
| |
| <p> |
| For example, the ugly URL |
| <code>/app?page=news/Thread&service=page</code> |
| may be converted into the friendly URL |
| <code>/news/Threads.html</code> |
| . In this case, the |
| <code>page=news/Thread</code> |
| query parameter became the |
| <code>news/Thread</code> |
| portion of the URL, and the |
| <code>service=page</code> |
| query parameter became the |
| <code>.html</code> |
| extension to the URL. |
| </p> |
| |
| <subsection name="Understanding Tapestry URLs"> |
| |
| |
| <p> |
| To understand how to get friendly URLs, you must understand a little about what |
| information Tapestry packs into URLs. |
| </p> |
| |
| <p> |
| Every request to a Tapestry application is mapped to an |
| <em>engine service</em> |
| . An engine service is something like a servlet, embedded within Tapestry. The |
| <code>service</code> |
| query parameter is used to select an engine service by name. A number of |
| services are provided with the framework, the most common of which are: |
| </p> |
| |
| |
| <dl> |
| <dt>page</dt> |
| <dd>Activates and renders a specific page.</dd> |
| <dt>direct</dt> |
| <dd> |
| Used with the |
| <a href="../components/link/directlink.html">DirectLink</a> |
| and |
| <a href="../components/form/form.html">Form</a> |
| components. |
| </dd> |
| <dt>home</dt> |
| <dd> |
| Default service used when the service parameter is not specified (such as |
| when first accessing the application); activates and renders the Home page. |
| </dd> |
| </dl> |
| |
| <p> |
| Each service is responsible for creating URLs with the correct query parameters. |
| By default, the URL path is always |
| <code>/app</code> |
| and any additional information comes out of the query parameters. The most |
| common parameters are: |
| </p> |
| |
| <dl> |
| <dt>page</dt> |
| <dd>The name of a page to activate.</dd> |
| <dt>service</dt> |
| <dd>The service responsible for the request.</dd> |
| <dt>component</dt> |
| <dd>The nested component id of a component.</dd> |
| <dt> |
| sp |
| |
| </dt> |
| <dd> |
| Stores listener parameters passed in the URL (used by |
| <a href="../components/link/directlink.html">DirectLink</a> |
| and passed into |
| <a href="listenermethods.html">listener method</a> |
| s, the "sp" is a holdover from 3.0). |
| </dd> |
| </dl> |
| |
| <p> |
| Following this scheme, a typical URL might be |
| <code> |
| /app?component=border.logout&page=news/Thread&service=direct |
| </code> |
| . Yep, that's UGLY. |
| </p> |
| |
| </subsection> |
| |
| <subsection name="Enabling Friendly URLs"> |
| |
| |
| <p> |
| To use ordinary ugly URLs, Tapestry requires only a |
| <a href="configuration.html#configuration.deployment-descriptor"> |
| small amount of configuration in web.xml |
| </a> |
| . Enabling friendly URLs requires adding more configuration to web.xml, and to |
| your |
| <a href="hivemind.html">HiveMind module deployment descriptor</a> |
| . |
| </p> |
| |
| <p> |
| Friendly URLs are controlled by |
| <a |
| href="../apidocs/org/apache/tapestry/engine/ServiceEncoder.html"> |
| ServiceEncoder |
| </a> |
| s. Getting Tapestry to output friendly URLs is a matter of plugging encoders |
| into the correct pipeline ... this is all done using HiveMind. |
| </p> |
| |
| <subsection name="page-service-encoder"> |
| |
| |
| <p> |
| The most common type of encoder is the |
| <code>page-service-encoder</code> |
| , which encodes the |
| <code>page</code> |
| and |
| <code>service</code> |
| parameters. In your hivemodule.xml: |
| </p> |
| |
| <source xml:space="preserve"> |
| <contribution configuration-id="tapestry.url.ServiceEncoders"> |
| <page-service-encoder id="page" extension="html" service="page"/> |
| </contribution></source> |
| |
| <p> |
| This contribution to the |
| <a href="../tapestry-framework/hivedoc/config/tapestry.url.ServiceEncoders.html"> |
| tapestry.url.ServiceEncoders |
| </a> |
| configuration point creates a |
| <a |
| href="../apidocs/org/apache/tapestry/engine/ServiceEncoder.html"> |
| ServiceEncoder |
| </a> |
| that maps the |
| <code>.html</code> |
| extension (on the URL path) to the page service. The |
| <code>id</code> |
| attribute must be unique for all contributed encoders. |
| </p> |
| |
| <p> |
| For Tapestry to recognize the URLs, you must inform the servlet container to |
| send them to the Tapestry application servlet, by adding a mapping to |
| web.xml: |
| </p> |
| <source xml:space="preserve"> |
| <servlet-mapping> |
| <servlet-name>myapp</servlet-name> |
| <url-pattern>*.html</url-pattern> |
| </servlet-mapping></source> |
| |
| |
| <span class="info"> |
| <strong>Note:</strong> |
| <p> |
| This means that even static HTML pages that are part of your web |
| application will be treated as Tapestry pages; any incoming request that |
| ends with .html will be routed into the Tapestry application. Page |
| specifications are optional, so Tapestry will treat the HTML pages are |
| if they were HTML page templates. If you want to allow ordinary static |
| content, then you should use another extension such as ".page" or ".tap" |
| (the choice is arbitrary). |
| </p> |
| </span> |
| |
| </subsection> |
| |
| <subsection name="direct-service-encoder"> |
| |
| |
| <p> |
| A specialized encoder used |
| <em>exclusively</em> |
| with the direct service. Encodes the page name into the servlet path, then a |
| comma, then the nested id for the component. One of two extensions is used, |
| depending on whether the URL is stateful (an HttpSession existed when the |
| link was rendered), or stateless. |
| </p> |
| |
| <p> |
| A typical URL might be: |
| <code>/admin/Menu,border.link.direct</code> |
| . This indicates a page name of |
| <code>admin/Menu</code> |
| and a component id of |
| <code>border.link</code> |
| . By convention, the ".direct" extension is for stateless URLs. |
| </p> |
| |
| <p>The hivemodule.xml contribution:</p> |
| |
| <source xml:space="preserve"> |
| <contribution configuration-id="tapestry.url.ServiceEncoders"> |
| <direct-service-encoder id="direct" stateless-extension="direct" stateful-extension="sdirect"/> |
| </contribution> |
| </source> |
| |
| <p> |
| In addition, the |
| <code>*.direct</code> |
| and |
| <code>*.sdirect</code> |
| mappings must be added to web.xml: |
| </p> |
| |
| <source xml:space="preserve"> |
| <servlet-mapping> |
| <servlet-name>myapp</servlet-name> |
| <url-pattern>*.direct</url-pattern> |
| </servlet-mapping> |
| |
| <servlet-mapping> |
| <servlet-name>myapp</servlet-name> |
| <url-pattern>*.sdirect</url-pattern> |
| </servlet-mapping> </source> |
| |
| </subsection> |
| |
| <subsection name="asset-encoder"> |
| |
| |
| <p> |
| The |
| <code>asset-encoder</code> |
| is for use with the asset service. The asset service exposes assets stored |
| on the classpath (i.e., inside JARs) to the client web browser. The asset |
| service receives a request with a resource path, and writes back a binary |
| stream of that resources content. |
| </p> |
| |
| <p> |
| In addition, each request includes a |
| <em>message digest</em> |
| , a string generated from the bytes of the the resource. This message digest |
| acts as a |
| <em>credential</em> |
| , assuring that only classpath resources explicitly exposed by the |
| application are accessible by the client (this prevents devious users from |
| obtaining Java class files, for example). The message digest can only be |
| computed by the server, using the full content of the actual file. |
| </p> |
| |
| <p> |
| To enable friendly URLs for the asset service, add the following to your |
| hivemodule.xml: |
| </p> |
| |
| <source xml:space="preserve"> |
| <contribution configuration-id="tapestry.url.ServiceEncoders"> |
| <asset-encoder id="asset" path="/assets"/> |
| </contribution></source> |
| |
| <p> |
| This contribution will encode asset URLs using the given path. The provided |
| path, |
| <code>/assets</code> |
| comes first, then the digest string, then the path for the URL. An example |
| URI would be |
| <code> |
| /assets/91ab6d51232df0384663312f405babbe/org/apache/tapestry/contrib/palette/select_right.gif |
| </code> |
| . |
| </p> |
| |
| <p>In addition you must add a mapping to web.xml:</p> |
| |
| <source xml:space="preserve"> |
| <servlet-mapping> |
| <servlet-name>myapp</servlet-name> |
| <url-pattern>/assets/*</url-pattern> |
| </servlet-mapping></source> |
| |
| |
| <p> |
| If you choose a different folder than |
| <code>/assets/</code> |
| then be sure to make corresponding changes in both hivemodule.xml and |
| web.xml. |
| </p> |
| |
| </subsection> |
| |
| <subsection name="extension-encoder"> |
| |
| |
| <p> |
| The |
| <code>extension-encoder</code> |
| is used to encode just the |
| <code>service</code> |
| query parameter. The output URL is the service name with a fixed extension |
| (typically, ".svc"), i.e., |
| <code>/home.svc</code> |
| or |
| <code>/restart.svc</code> |
| . |
| </p> |
| |
| <p>In your hivemodule.xml:</p> |
| |
| <source xml:space="preserve"> |
| <contribution configuration-id="tapestry.url.ServiceEncoders"> |
| <extension-encoder id="extension" extension="svc" after="*"/> |
| </contribution></source> |
| |
| |
| <p> |
| The use of the |
| <code>after</code> |
| attribute ensures that this encoder is always executed after any other |
| encoders. Order is important! |
| </p> |
| |
| <p>For this example, another mapping is required in the web.xml:</p> |
| |
| <source xml:space="preserve"> |
| <servlet-mapping> |
| <servlet-name>myapp</servlet-name> |
| <url-pattern>*.svc</url-pattern> |
| </servlet-mapping></source> |
| </subsection> |
| |
| <subsection name="encoder"> |
| |
| |
| <p> |
| Finally, when one of the pre-defined encoders is insufficient, you can |
| define your own. The <encoder> element allows an arbitrary object that |
| implements the |
| <a |
| href="../apidocs/org/apache/tapestry/engine/ServiceEncoder.html"> |
| ServiceEncoder |
| </a> |
| interface to be plugged into the pipeline. The <encoder> element |
| supports the (required) id attribute, and the optional before and after |
| attributes. |
| </p> |
| |
| <p> |
| From the Virtual Library example, a custom encoder implementation is used as |
| a special way to reference the ViewBook and ViewPerson pages using the |
| external service (see the |
| <a href="../components/link/externallink.html">ExternalLink</a> |
| component for more information about using this engine service). The end |
| result is that the URLs for these two pages look like |
| <code>/vlib/book/2096</code> |
| rather than |
| <code>/vlib/ViewBook.external?sp=2096</code> |
| or |
| <code>/vlib/app?page=ViewBook&service=external&sp=2096</code> |
| . Certainly the first option is by far the prettiest. |
| </p> |
| |
| <p>These encoders are configured in hivemodule.xml as follows:</p> |
| |
| <source xml:space="preserve"> |
| <encoder id="viewbook" before="external" object="instance:ViewPageEncoder,pageName=ViewBook,url=/book"/> |
| <encoder id="viewperson" before="external" object="instance:ViewPageEncoder,pageName=ViewPerson,url=/person"/> |
| <page-service-encoder id="external" extension="external" service="external"/> |
| </source> |
| |
| <p> |
| The order of the encoders in the pipline is very important, so the use of |
| the before attribute ensures that the specialized encoders for these two |
| pages are allowed to operate before the general purpose external service |
| encoder. |
| </p> |
| |
| <p>The two special pages are mapped in web.xml using their custom URLs:</p> |
| |
| <source xml:space="preserve"> <servlet-mapping> |
| <servlet-name>vlib</servlet-name> |
| <url-pattern>/book/*</url-pattern> |
| </servlet-mapping> |
| <servlet-mapping> |
| <servlet-name>vlib</servlet-name> |
| <url-pattern>/person/*</url-pattern> |
| </servlet-mapping> |
| </source> |
| |
| <p> |
| The implementation of the ViewPageEncoder class is all about an encode() |
| method and a matching decode() method. |
| </p> |
| |
| <p> |
| The encode() method must check to see if the link being generated is the |
| right page name and the right service, returning (without doing anything) if |
| not. The link being constructed is represented as an instance of |
| <a |
| href="../apidocs/org/apache/tapestry/engine/ServiceEncoding.html"> |
| ServiceEncoding |
| </a> |
| : |
| </p> |
| |
| <source xml:space="preserve"> |
| public void encode(ServiceEncoding encoding) |
| { |
| if (!isExternalService(encoding)) |
| return; |
| |
| String pageName = encoding.getParameterValue(ServiceConstants.PAGE); |
| |
| if (!pageName.equals(_pageName)) |
| return; |
| |
| StringBuilder builder = new StringBuilder(_url); |
| |
| String[] params = encoding.getParameterValues(ServiceConstants.PARAMETER); |
| |
| // params will not be null; in fact, pretty sure it will consist |
| // of just one element (an integer). |
| |
| for (String param : params) |
| { |
| builder.append("/"); |
| builder.append(param); |
| } |
| |
| encoding.setServletPath(builder.toString()); |
| |
| encoding.setParameterValue(ServiceConstants.SERVICE, null); |
| encoding.setParameterValue(ServiceConstants.PAGE, null); |
| encoding.setParameterValue(ServiceConstants.PARAMETER, null); |
| } |
| |
| private boolean isExternalService(ServiceEncoding encoding) |
| { |
| String service = encoding.getParameterValue(ServiceConstants.SERVICE); |
| |
| return service.equals(Tapestry.EXTERNAL_SERVICE); |
| } </source> |
| |
| <p> |
| We cheat just a bit here because we know that the service parameters will be |
| a single numeric string. You can see exactly how encoder works, by building |
| a new servlet path that encodes information that was stored as query |
| parameters, the setting those query parameters to null |
| </p> |
| |
| <p> |
| The flip side is the decode() method, which works by recognizing the URL |
| generated by the encode() method and restoring the query parameters by |
| parsing the URL: |
| </p> |
| |
| <source xml:space="preserve"> public void decode(ServiceEncoding encoding) |
| { |
| String servletPath = encoding.getServletPath(); |
| |
| if (!servletPath.equals(_url)) |
| return; |
| |
| String pathInfo = encoding.getPathInfo(); |
| |
| String[] params = TapestryUtils.split(pathInfo.substring(1), '/'); |
| |
| encoding.setParameterValue(ServiceConstants.SERVICE, Tapestry.EXTERNAL_SERVICE); |
| encoding.setParameterValue(ServiceConstants.PAGE, _pageName); |
| encoding.setParameterValues(ServiceConstants.PARAMETER, params); |
| } </source> |
| |
| </subsection> |
| |
| <p> |
| When constructing this style of encoder, it is important to remember that the |
| servlet path does not end with a slash, but tthe path info, if non-null, will |
| start with a slash. |
| </p> |
| |
| </subsection> |
| |
| </section> |
| </body> |
| </document> |