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.

2 comments:

Patrick Lightbody 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.

Unknown 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.