Configuring Friendly URLs
Earlier versions of Tapestry have had a long-standing tradition of really ugly URLs. 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.
In fact, the ugly URLs do cause some problems:
- Since all requests are routed through a single servlet (typically mapped to /app), J2EE declarative security, which is path-based, is defeated.
- Ugly URLs tend to be longer than friendly URLs, which can make a difference when creating a WML application.
- 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.
- The reliance on query parameters means that common search engines will only see a tiny fraction of the application.
Starting with 4.0, friendly URLs are integrated directly into framework (in 3.0 an ambitious, but more limited, patch was required).
Friendly URLs are divided into two concerns:
- Converting information normally stored as a query parameter into part of the URL path.
- Parsing the path to restore the information previously encoded.
For example, the ugly URL /app?page=news/Thread&service=page may be converted into the friendly URL /news/Threads.html. In this case, the page=news/Thread query parameter became the news/Thread portion of the URL, and the service=page query parameter became the .html extension to the URL.
Understanding Tapestry URLs
To understand how to get friendly URLs, you must understand a little about what information Tapestry packs into URLs.
Every request to a Tapestry application is mapped to an engine service. An engine service is something like a servlet, embedded within Tapestry. The service 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:
- page
- Activates and renders a specific page.
- direct
- Used with the DirectLink and Form components.
- home
- Default service used when the service parameter is not specified (such as when first accessing the application); activates and renders the Home page.
Each service is responsible for creating URLs with the correct query parameters. By default, the URL path is always /app and any additional information comes out of the query parameters. The most common parameters are:
- page
- The name of a page to activate.
- service
- The service responsible for the request.
- component
- The nested component id of a component.
- sp
- Stores listener parameters passed in the URL (used by DirectLink and passed into listener methods, the "sp" is a holdover from 3.0).
This a typical URL might be /app?component=border.logout&page=news/Thread&service=direct. Yep, that's UGLY.
Enabling Friendly URLs
To use ordinary ugly URLs, Tapestry requires only a small amount of configuration in web.xml. Enabling friendly URLs requires adding more configuration to web.xml, and to your HiveMind module deployment descriptor.
Friendly URLs are controlled by ServiceEncoders. Getting Tapestry to output friendly URLs is a matter of plugging encoders into the correct pipeline ... this is all done using HiveMind.
page-service-encoder
The most common type of encoder is the page-service-encoder, which encodes the page and service parameters. In your hivemodule.xml:
<contribution configuration-id="tapestry.url.ServiceEncoders"> <page-service-encoder id="page" extension="html" service="page"/> </contribution>
This contribution to the tapestry.url.ServiceEncoders configuration point creates a ServiceEncoder that maps the .html extension (on the URL path) to the page service. The id attribute must be unique for all contributed encoders.
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:
<servlet-mapping> <servlet-name>myapp</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping>
direct-service-encoder
A specialized encoder used exclusively 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.
A typical URL might be: /admin/Menu,border.link.direct. This indicates a page name of admin/Menu and a component id of border.link. By convention, the ".direct" extension is for stateless URLs.
The hivemodule.xml contribution:
<contribution configuration-id="tapestry.url.ServiceEncoders"> <direct-service-encoder id="direct" stateless-extension="direct" stateful-extension="sdirect"/> </contribution>
In addition, the *.direct and *.sdirect mappings must be added to web.xml:
<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>
asset-encoder
The asset-encoder 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.
In addition, each request includes a message digest, a string generated from the bytes of the the resource. This message digest acts as a credential, 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.
To enable friendly URLs for the asset service, add the following to your hivemodule.xml:
<contribution configuration-id="tapestry.url.ServiceEncoders"> <asset-encoder id="asset" path="/assets/"/> </contribution>
This contribution will encode asset URLs using the given path. The provided path, /assets/ comes first, then the digest string, then the path for the URL. An example URI would be /assets/91ab6d51232df0384663312f405babbe/org/apache/tapestry/contrib/palette/select_right.gif.
In addition you must add a mapping to web.xml:
<servlet-mapping> <servlet-name>myapp</servlet-name> <url-pattern>/assets/*</url-pattern> </servlet-mapping>
If you choose a different folder than /assets/ then be sure to make corresponding changes in both hivemodule.xml and web.xml.
extension-encoder
The extension-encoder is used to encode just the service query parameter. The output URL is the service name with a fixed extension (typically, ".svc"), i.e., /home.svc or /restart.svc.
In your hivemodule.xml:
<contribution configuration-id="tapestry.url.ServiceEncoders"> <extension-encoder id="extension" extension="svc" after="*"/> </contribution>
The use of the after attribute ensures that this encoder is always executed after any other encoders. Order is important!
For this example, another mapping is required in the web.xml:
<servlet-mapping> <servlet-name>myapp</servlet-name> <url-pattern>*.svc</url-pattern> </servlet-mapping>