Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Monday, December 06, 2004

Tapestry URLs: Half way there

Did a lot of work this weekend on the Tapestry URL front. If you are familiar with Tapestry, you know that nobody likes the way the URLs are formatted. That's an issue, because you don't have control over it ... Tapestry is responsible for building the URLs (a very good thing) and does all the dispatching and so forth (again, very good thing). But Tapestry's butt ugly URLs are a problem for many.

  • All URLs are built off a single servlet, typically /app. This defeats J2EE declarative security, which is path based.
  • The URLs are longish. For example,
    /app?service=direct/0/Home/$Border.login&sp=T
    

    There's a rhyme and a reason to all that, but its very much oriented around the code and not the users.

  • The use of the slash ("/") character in the URLs is an impediment to breaking the application into modules (i.e., putting the admin pages into an "admin" folder).
  • The emphasis on query parameters means that most of an application, after the home page, will be "off limits" to any kind of web spider, such as Google.

I spent a large portion of the last few days working on this, and I'm halfway there. Of course, you'd hardly know it ... if you download Tapestry from CVS and built it right now, you'd see that the URLs have gotten longer! That service query parameter has been broken up into several smaller variables: service, page, component and, where necessary, container. Some Tapestry engine services add others (for example, the asset service has a path query parameter).

That means that the example URL from before would look like:

/app?component=$Border.login&page=Home&service=direct&sp=T

The next step (the work not yet done) is more fun ... what if we converted the page query parameter into more path info, and the service into an extension? Then our URL is much friendlier:

/Home.direct?component=$Border.login&sp=T

If we map the .html extension to the home service, then a Tapestry page looks like a normal HTML page:

/Home.html

From a client web browser, that's a reference to a Tapestry page, even though it looks like just the page's HTML template. With this kind of mapping, you might not even use the PageLink component in your templates, just ordinary HTML links:

<a href="misc/About.html">About</a>

Instead of:

<a jwcid="@PageLink" page="misc/About">About</a>

Getting all of this to work for your own application will require:

  • Adding path mappings to your web.xml:

    <servlet-mapping>
      <servlet-name>app</servlet-name>
      <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    
  • Adding configuration data (HiveMind contributions) to your application, to tell Tapestry about those mappings:
    <map-extension extension="html" service-name="page"/>
    

Hopefully, I can get more of this going in the next couple of days (I have a long flight out to San Francisco this week).

Still, even with these improvements, the URLs aren't perfect. There's some dissatisfaction with bookmarking URLs with the external service; any extra data is encoded into the sp parameter, which has a prefixing system to identify the type of data. For example, "S" for string, "d" for double, "O" for serialized object.

Talking with Erik, I had the idea that we could simplify things by creating a service that bookmarked some set of page properties into the URL. So you might have a ShowAccount page, with an accountId property of type long. The accountId property gets converted into a string as part of the URL. Later, when the request is submitted, the accountId query parameter is converted back to a long and pluggined into the page's actionId property. The end result is a "pretty" URL:

/ShowAccount.html?accountId=972

This is much prettier than what you might accomplish in Tapestry 3.0 using the external service:

/app?service=external/ShowAccount&sp=l972

I think Erik wants to go further though ... he wants something like:

/ShowAccount/972

I think we can get there. I think we can allow one extension to map to different services (here, the extra query parameter hints that this is a bookmark service, not a page service, request). It's a matter of flexible interfaces and flexible HiveMind contributions.

5 comments:

Glen Stampoultzis said...

I want what Erik wants. :-)

Patrick said...

Howard,
I think this looks pretty good -- we have something like this in WebWork but it isn't heavily advertised right now. The mechanism is described here:

http://www.opensymphony.com/webwork/api/com/opensymphony/webwork/dispatcher/CoolUriServletDispatcher.html
(CoolUri isn't exactly a "cool" name)

Basically, the idea is that /foo/123 is the equivalent of calling setFoo(123) on the action (or whatever).

I'm surprised that Tapestry uses the "sp" variable to indicate the type of data. Ognl has such a good type conversion framework, so I'm glad to see Erik is getting you involved in that stuff. I've found it has been really useful.

Erik Hatcher said...

I guess Howard is talking about me. I'm a very RESTful kinda guy. URL's like /ShowAccount.html?accountId=972 are close. For dynamic resources like this, I do not think .html should be used as an extension. Nor does ShowAccount/972 capture what I'd like. Query parameters make sense to me, if they are meaningful. In this example, I'd like ShowAccount?accountId=972 - no extension, and a query parameter for what really is being asked.

Thanks to Howard's advice (with what should have been obvious to me), I have been happily using ExternalLink's with meaningful parameters lately. This allows me to have hyperlinks and forms on non-Tapestry pages that hop into Tapestry. Much of my work is with search interfaces, so my URL's currently look like this: /app?service=external/Search&query=someQuery&page=2. This URL scheme, allowing my IExternalPage implementation to act as a mini-servlet to pull off the parameters, has been a nice stop-gap solution.

With the flexibility Howard is introducing many will be very very happy!

Steve said...

Howard,

What's the status on this ReSTification of Tapestry? We're evaluating tapestry right now for an extensive enterprise app and relatively readable, bookmarkable urls are something we want to maintain if at all possible. I'm looking for basic /ShowAccount.html?accountId=972 type urls - query strings are a good thing, and should have relatively logical parameters.

Howard said...

Using just the built-in friendly URL support, you could get a URL of /ShowAccount.html?sp=972 which is pretty close.

By plugging your own ServiceEncoders into the pipeline, and having corresponding web.xml URL mappings, you could go as far as /ShowAccount/972.