Permalink
Browse files

Initial commit of roundabout. Needs cleanup. Project name subject to …

…change.
  • Loading branch information...
0 parents commit 2f3cef25d9e20bd625f8a252a0a8946e836e2540 Ross Boucher committed Jan 9, 2009
Showing with 224 additions and 0 deletions.
  1. +71 −0 README
  2. +153 −0 roundabout.js
71 README
@@ -0,0 +1,71 @@
+roundabout
+
+copyright 2008 Ross Boucher
+Released under the terms of the MIT license.
+
+roundabout is a JavaScript HTTP routing framework.
+
+It works by taking a list of configurations, and using those configurations
+to determine the path of each incoming http request.
+
+A configuration, called a "road" in roundabout terminology, consists of a path,
+and at least one function that corresponds to an HTTP verb. It can optionally
+contain a function definition for each HTTP verb, as well as a filter function
+which will be executed against each request.
+
+Example road:
+
+{
+ path: "/hello",
+
+ GET: function(context)
+ {
+ context.body = "HELLO WORLD!";
+ },
+
+ POST: function(context)
+ {
+ context.redirect = "/";
+ }
+
+ filter: function(request)
+ {
+ return request.user_agent.match(\firefox\g);
+ }
+}
+
+You could implement only GET, or implement all the verbs; supported verbs are:
+
+GET, POST, PUT, DELETE
+
+filter takes a Jack request object, and should return true if the request
+should be processed, or false if it should not. filter will only be called
+if the path matches.
+
+The first road that has a matching path, verb, and filter that returns yes
+(or has no filter at all) will process the request.
+
+Paths can also be more complex than simple string by using wildcards:
+
+path: "/*/hello" matches "/foo/hello", "/bar/hello" but not "/hello" or "/foo/bar"
+
+Wildcards can be accessed on the context using the wildcards property.
+
+{
+ path: "/ * /hello",
+
+ GET: function(context)
+ {
+ context.body = context.wildcards[0];
+ }
+}
+
+In this example, going to /foo/hello would print "foo" to the screen.
+
+Another option that can be used with paths is named wildcards:
+
+path: /{name}/friends
+
+This will match "/james/friends", "/bob/friends", etc. and the result
+is available in context.wildcards["name"]
+
@@ -0,0 +1,153 @@
+
+/*
+
+roundabout
+
+copyright 2008 Ross Boucher
+Released under the terms of the MIT license.
+
+roundabout is a JavaScript HTTP routing framework.
+
+*/
+
+roundabout = {}
+
+//configuration options
+roundabout.caseSensitive = false;
+
+//store all routing events
+roundabout.roads = [];
+
+//constants for http methods
+roundabout.HTTP_GET = "GET";
+roundabout.HTTP_POST = "POST";
+roundabout.HTTP_PUT = "PUT";
+roundabout.HTTP_DELETE = "DELETE";
+
+//constants for common http headers and values
+//fixme: fill these in
+
+//route: takes a variable number of arguments, each is a "road"
+roundabout.route = function()
+{
+ for (var i=0, count=arguments.length; i<count; i++)
+ {
+ var road = arguments[i];
+
+ road["$regex"] = road.path.replace(/\*/g, "([a-zA-Z0-9+.;%\-]+)").replace(/{[a-zA-Z0-9]*}/g, "([a-zA-Z0-9+.;%\-]+)").replace(/\//g, "\\/");
+ road["$wildcards"] = road.path.match(/(\*)|\{([a-zA-Z0-9]+)\}/g);
+
+ //remove the leading and trailing { } from the wildcard name. fixme: wish i could do it in the regex...
+ var starCount = 0;
+ if (road["$wildcards"])
+ {
+ for (var i in road["$wildcards"])
+ {
+ if ((typeof road["$wildcards"][i]) !== (typeof "string"))
+ continue;
+
+ if (road["$wildcards"][i].length > 1)
+ road["$wildcards"][i] = road["$wildcards"][i].substring(1, road["$wildcards"][i].length - 1);
+ else
+ road["$wildcards"][i] = starCount++;
+ }
+ }
+
+ roundabout.roads.push(road);
+ }
+}
+
+roundabout.notFound = function(context)
+{
+ context.status = 404;
+ context.body = "Whoops. Couldn't find what you were looking for";
+}
+
+roundabout._notFound = function(context)
+{
+ context.status = 404;
+ roundabout.notFound(context);
+}
+
+roundabout.clear = function()
+{
+ roundabout.roads = [];
+}
+
+roundabout.context = function(defaultHeaders)
+{
+ this.status = 200;
+ this.body = "";
+ this.headers = {};
+ this.wildcards = {};
+
+ for (i in defaultHeaders)
+ this.headers[i] = defaultHeaders[i];
+
+ return this;
+}
+
+roundabout.context.prototype.redirect = function(location)
+{
+ this.status = 307;
+ this.headers["Location"] = location;
+}
+
+roundabout.defaultHeaders = {
+ "Content-type":"text/plain"
+}
+
+roundabout.dispatch = function(request)
+{
+ var context = new roundabout.context(roundabout.defaultHeaders);
+
+ try
+ {
+ var requestMethod = String(request.getMethod()).toUpperCase(),
+ requestPath = roundabout.caseSensitive ? String(request.getPathInfo()) : String(request.getPathInfo()).toLowerCase(),
+ found = false;
+
+ for (var i=0, roads = roundabout.roads, count=roads.length; i<count && !found; i++)
+ {
+ var road = roads[i],
+ roadPath = roundabout.caseSensitive ? road.path : road.path.toLowerCase(),
+ methodBlock = road[requestMethod] || road[requestMethod.toLowerCase()] || null;
+
+ var regex = new RegExp("^"+road["$regex"]+"$", roundabout.caseSensitive ? "i" : "");
+
+ if (methodBlock && regex.test(requestPath) && (!road["filter"] || road.filter(request)))
+ {
+ var resultingWildcards = String(request.getPathInfo()).match(regex);
+
+ for (var wildcardIndex=0, totalWildcards=road["$wildcards"].length; wildcardIndex<totalWildcards; wildcardIndex++)
+ context.wildcards[road["$wildcards"][wildcardIndex]] = resultingWildcards[wildcardIndex+1];
+
+ methodBlock(context);
+ found = true;
+ }
+ }
+
+ if (!found)
+ roundabout._notFound(context);
+ }
+ catch (e)
+ {
+ context.status = 500;
+ }
+
+ return [context.status, context.headers, context.body];
+}
+
+function doGet(request, response)
+{
+ var theResponse = roundabout.dispatch(request);
+
+ response.setStatus(theResponse[0]);
+
+ var headers = theResponse[1];
+
+ for (var i in headers)
+ response.setHeader(i, headers[i]);
+
+ response.getWriter().println(theResponse[2]);
+}

0 comments on commit 2f3cef2

Please sign in to comment.