#85 Weblet routing

andy Wed 14 Jun 2006

I sort of stumbled on this idea while playing around with Rails-style filters. But this might be a good solution for routing web requests:

class Weblet
{ 
  **
  ** Route this request to another Weblet, returning the instance of
  ** the Weblet to route request to. Return null from this method to
  ** send a 404 Not Found response. The default implementation
  ** returns this.
  **
  virtual Weblet route(WebReq req, WebRes res)
  {
    return this
  }
}

So web requests essentially get routed down similar to a tree. The root Weblet for fandev.org might look like this:

class Root extends Weblet
{ 
  override Weblet route(WebReq req, WebRes res)
  {
    if (req.uri == "/") return Home.make
    if (req.uri.startsWith("/discussion")) return Discussion.make
    return null
  }
}

And the Discussion weblet would further route based on your URL.

andy Tue 27 Jun 2006

I've struggled with coming up with a good design for URL-Weblet mapping. The goal of course is that I want to be able to write all weblets "relatively", so that I can reference URLs in my code as "/foo" when in deployment they may actually be "/a/b/c/foo".

In order to acheive this I need a mechanism to crawl down a set of Weblets from a URL, and also to know my "depth" so I can assemble back a functional URL.

Originally I had thought it would be really easy to do that using a Map and building a Weblet tree:

class Root
{
  Void init()
  {
    add("foo", Foo.make)
  }
}

class Foo
{
  Void init()
  {
    add("bar", Bar.make)
  }
}

/foo/bar -> Root.get("foo").get("bar") -> Bar

Which makes for a very elegant design, however, it does not address dynamic URLs - like the URL for this page. I can't map 258 straight to a Weblet instance.

So I went back to the route() method, and thought it might be on the right track. But I think instead of working off the URI directly, WebReq includes another field called path of type Str[], which is just req.uri.split("/").

req.uri  -> "/foo/bar"
req.path -> ["foo", "bar"]

And as I walk down my tree using route(), I can clip off the starting path:

class Root
{
  Weblet route(WebReq req, WebRes res)
  {
    // Where req.clip -> ["foo", "bar"] -> ["bar"]
    if (req.path.first == "foo") 
      return Foo.make.route(req.clip, res)
    return this
  }
}

class Foo
{
  Weblet route(WebReq req, WebRes res)
  {
    if (req.path.first == "bar") return Bar.make
    return this
  }
}

The issue here is that I am creating a new instance of every Weblet on the way down - maybe that method is static - I don't know. But then you can get your real URL using your current path:

myPath := "/" + req.path.join("/")
contextPath := req.uri[0...(-myPath.size)]
href := contextPath + "/bar"

Where that code is probably a convenience method on WebReq. Or acutally, probably more efficient is that every time you clip(), I append that to a contextPath field on WebReq:

req.contextPath = ""
req.path = ["a", "b", "c"]  

WebReq clip()
{
  contextPath += "/" + path.pop
  return this
}

Login or Signup to reply.