In the beginning
The early days of creating web applications with .NET had us beating our brains against ASP.NET. Developing web applications using ASP.NET is difficult and messy because it tries to shield you from the details of HTTP. In practice, it's awkward to use and it flagrantly abuses HTTP and web browsers. Kayak takes the opposite tack - it's a small, simple, nimble layer between you and the underlying TCP stream which gives you fine-grained control over how your application communicates with its clients.
A word about architecture
Kayak consists of two DLL libraries: a request framework, and the HTTP server on which it depends. Unlike IIS, the Kayak server does not load your code into its process space. You write your own entry point, instantiate a
KayakServer object, set it up however you want, and start it when you're ready. Usually, you'll set it up to handle requests using the Kayak Framework.
The framework simply maps HTTP requests to .NET method invocations. Arguments to a method invocation can be generated from the HTTP request, and the return value of the invocation can be used to generate the HTTP response. By default, the Kayak Framework converts .NET objects to and from JSON. It automatically deserializes JSON in the body of an incoming HTTP request and passes the deserialized .NET objects as arguments to invocations of your methods. When your function returns, Kayak then serializes the return value of the invocation as JSON to the body of the HTTP response. Kayak makes it very easy to create those simple HTTP APIs which are all-the-rage these days.
By the following principles of REST (excuse the buzzword; I don't like it either), you eliminate the need for many of the features that bloat tools like ASP.NET, MonoRail, and ASP.NET MVC, as well as Django and other popular frameworks for dynamic languages.
By way of example
We're going to get familiar with Kayak by making a simple CMS API. An in-browser AJAX app or a desktop app in could communicate with this API to implement a management interface for the CMS. Our CMS will implement the concept of a uniquely-identifiable document, each of which has several revisions. A document revision has a title, a body, a slug (URL-safe title), and a list of tags. The full source of the CMS is included with this article as a ZIP file.
Make a server
Before we get further into the details of the CMS, let's take a look at how to set up a Kayak server.
As you can see, this is a regular C# entry point. There is no magical loading of your code into a freaky container that does God-only-knows-what before your code gets control, and when and how when your program exits is very clear. You are in control and can do exactly what you want, when you want to do it. Not at all like the ASP.NET model!
Use the framework
UseFramework() method in the previous listing is an extension method to the server declared by the framework. It searches your assembly for methods marked with a special attribute called
[Path()], and configures the server to respond using those methods. The
[Path()] attribute takes a single argument: a string specifying the path which, when requested, will cause the method to be invoked. Let's write a method which will respond to requests for /, the root path, by simply saying hello.
Nothing too surprising there. Notice that our class extends from
KayakService. This isn't strictly necessary, but it's pretty handy because it gives us access to the current HTTP context objects. In this example, we're using the
KayakResponse object to write some text to the in-memory response buffer. After the method is invoked, Kayak will automatically write the contents of the buffer to the network.
Spit out some JSON
How about something a bit more tricky? Let's create a method which will return a JSON array of all document revisions in our database which have a certain tag.
There are a few interesting things going on here. First, take a look at the
[Path()] attribute: we have a nice, REST-ful path to invoke this method. The tag used in the search is taken right from the path and passed into the method invocation. Second, the
includeOldRevisions parameter is taken from either the query string or a cookie value. If it's not found in either of those places, it defaults to
false). Lastly, the array of
DocumentRevision objects that this method returns is automatically serialized to JSON. If you make a request for
/documents/tagged/cool, you'll get a JSON array of all the revisions tagged with "cool", which might look something like this:
Eat some JSON
Now let's put JSON data into our app. We want to write an API method for creating a new document. A document is really just a set of revisions, so all we need to make a new document a first revision. Our API method will accept a first revision, save it, inform the client that the operation was a success (using the 201 Created response code), and return the ID of the newly created document as a JSON string. So, let's write a method that accepts a single argument of type
We've got two new attributes,
[Verb()]: it takes a single string argument specifying the HTTP request method which must be used in combination with the path in order for the method to be invoked.
(Kayak uses the not-quite-correct terminology "verb" to refer to an HTTP request method. This is for the sake of distinction from .NET's concept of a method. When no
[Verb()] attribute is present on a method,
[Verb("GET")] is implied.)
Verbs are an important concept in HTTP and understanding the way they're used is important when creating a proper HTTP API, regardless of whether or not you're using Kayak. In general, verbs act on a "resource" on the web server named by the path. There are four standard HTTP verbs: GET, POST, PUT, and DELETE. An HTTP client uses GET to retrieve a resource; an HTTP server sends the requested resource in the body of a response to a GET request. The verbs POST and PUT are used to create or modify a resource: when making a PUT or POST request, an HTTP client sends some data to the server in the body of the HTTP request. The meaning of a POST request is: "Save this data, give it a unique URL, and let me know what that URL is". The meaning of a PUT request is: "Save this data at this URL." Thus POST is most appropriate for creating records, and PUT is most appropriate for updating them. Finally, there is DELETE, which causes the server to remove the resource. An HTTP server should not generate an error if DELETE is invoked on resource more than once.
In the listing above, we've marked the
DocumentRevision method parameter with the
[RequestBody] attribute to indicate that Kayak should read the request body data sent by the client to construct arguments. The type of the parameter enforces a structure on data accepted from the client.
To invoke this method, a client could send a request such as the following:
Kayak's response will look something like:
The JSON string in the response body indicates the ID of the newly-created document, from which a client, if it knows the API, can construct the URL of the newly created resource.
Not quite what you want? Do something entirely different!
What you've just seen is the "sensible default" configuration of the Kayak Framework. At its core, the Kayak Framework is just about invoking methods in response to HTTP requests. If you would rather read and write XML, you could plug it right in. If you'd rather not mess with attributes and instead map methods based on the names of the enclosing class and the method, you can do that too. The Kayak Framework is designed to be extremely flexible.
Philosophy and other interesting bits
Kayak does not try to be a "full stack" web development platform. As an implementation of HTTP, it does not have any opinions about database layers or template languages. The project aims to foster a deeper understanding of HTTP and an appreciation of it as a simple, versatile, and powerful application protocol.
Kayak's architecture is designed with performance in mind. All disk and network IO is performed in an asynchronous manner, and Kayak can simultaneously service multiple connections per worker thread. When used properly, it enables you to write extremely efficient applications.
Kayak is a simple server with an easy-to-use request framework. It automatically maps HTTP verb/path combinations to your methods, deserializes arguments to invocations of those methods from the query string or JSON request body, and serializes the return values as JSON. It's behavior is highly configurable, yet simple to use and understand thanks to its limited scope.
I am a software developer at Spotlight Mobile in rainy Portland, Oregon, where I design and build user interfaces for the web and iPhone OS, and write application backends in C# using Kayak.
View complete profile here.
Please login to rate or to leave a comment.