September 13, 2013

Using the Ada Web Server (AWS), part 1

The Ada Web Server (AWS) is a big library with a whole lot of functionality, far more than I can manage to write about, so what I am going to do in this first article is focus on the basic stuff: Getting a server up and registering a couple of content handlers.

Before we move on lets get two things out of the way:

  1. Make sure you have AWS installed on your system
  2. Git clone the article code from here

If you're having trouble getting AWS up and running, you could take a look at an article I wrote a while ago about Ada Programming on Slackware. Even if you're not using Slackware there might be some tricks in there that also apply to your favorite distro. If you're using Windows I really can't help you, as I know very little about getting Ada and AWS up and running on that line of operating systems.

What is most important though, is that you grab the latest AWS from the Git repository:

At the time of writing the latest commit in the repository was ffc79ac63f06e44100bc69de59eb4ed9b76c41ae (Date: Tue Feb 5 18:48:37 2013 +0100), so assume that you at least need an AWS that is as new as that.

From now on I'm going to assume that you've got a sufficiently new AWS working and the tutorial code cloned from my Github repository, so lets get rocking with some AWS magic.

We start by building and trying the tutorial program you cloned earlier, so we know what we're getting into:

Grab your browser and do as instructed; go to localhost:8080 and see what you get. Nice eh'? A sexy 404 message hosted by your very own Ada powered webserver. Whooooosh! If you don't like the 404, try visiting localhost:8080/helloworld for something else. When you're done being amazed click q to kill the server.

So how did this magic come to life? We zoom to the main program file aws_tutorial.adb for some enlightenment:

Simplicity at work eh'? Lets stroll through the code together.

First we with a bunch of packages: Ada.Text_IO is for text input/output, and that is exactly what we're using it for at line 12 where we output the port and kill instructions. After that we add three AWS packages, of which the first two are dealing with the configuration of the AWS HTTP server, specifically the default settings and loading of any external configuration files. The call to AWS.Config.Get_Current at line 19 searches for the files aws.ini and progname.ini (in our case aws_tutorial.ini) in the same location as the executable, and if any of these are found the values set here are used to configure the Web_Server object that is started at line 17 by the AWS.Server.Start call. If you take a peek at the exe/aws.ini file you'll see that the only value we're setting is Reuse_Address True. What this does is allow us to start and stop the server without having to wait for the system to release the socket. The rest of the configuration is inherited from the AWS.Default package.

We'll ignore the call to Handlers.Get_Dispatcher for now and move further down to line 21 where we're abruptly stopped by the AWS.Server.Wait (AWS.Server.Q_Key_Pressed) call. What is this weird contraption? Well a server needs some sort of "loop" to prevent it from terminating, and this is exactly one such loop, except it isn't really a loop. What Wait does is hang until some specific event occurs, in our case it is waiting for someone to click the q key. As soon as Wait registers some action on the q key it returns and the program moves on to the final statement; the AWS.Server.Shutdown (Web_Server) call. I'm guessing that it is evident what this does. It cleanly shuts down the server and when it is done, the program exits.

And that is really all that is required to add HTTP(s) support to your Ada program.

But wait, how do we generate the content? Ahh, yes now we'll take a closer look at the Handlers.Get_Dispatcher call at line 18. What is this dispatcher thing we're getting? It's simple really. In order for the server to know what to do with all the requests coming in, we need to define some handlers. This is done in the homegrown Handlers.Get_Dispatcher function that lives a groovy life in the Handlers package found in the src/[sb] files. Lets take a peek at the specification file first:

Not much happening here. We define the Get_Dispatcher function which return a AWS.Services.Dispatchers.URI.Handler. The name of the returned type should give away that we're dispatching HTTP request based on the URI of the requested resource. There are a bunch of other dispatchers available in AWS, but for now we'll stick to the basic URI stuff.

The body is a bit more interesting:

The two packages we with contains the code that generates our content. We need them here because this is the place where we register the resource handlers.

We declare the Dispatcher object as an URI.Handler at line 13 and then we proceed with registering two handlers: A default handler at line 15 and a specific /helloworld handler at line 20. Finally we return the Dispatcher object.

The default handler is the Not_Found.Callback function, which means that whenever a resource is requested that doesn't specifically match a registered URI, the Not_Found.Callback function is called. On the other hand if the /helloworld resource is requested, the Hello_World.Callback function is called.

This is how dispatching in AWS works. But it is not the only way. It is also possible to manually dispatch based on the string URL of a request, but that is very clumsy and ugly. You really should use the dispatcher model if you're working with AWS as it offers both convenience and some nice tools to build and maintain your resource->handler structure.

Moving on lets take a peek at the Not_Found package where we handle the 404's:

Here we see the Callback function that we registered in the Handlers.Get_Dispatcher function. And that is really all we make available to the world in the specification of this package. Lets check out the body:

Ahh, this is far more interesting. Starting with the Callback function we see that all it does is return an AWS.Dispatchers.Callback.Handler. This is done using the Create function which takes an AWS.Response.Callback as its single parameter. The signature of the AWS.Response.Callback is this:

And that is exactly the signature of the Generate_Content function. It's marvelous how these things just come together like hand in glove. In Generate_Content we build the response sent to clients requesting unknown resources. This is all done in the AWS.Response.Build call. We define the Content_Type as text/html, we generate the actual content and finally we set the Status_Code to 404.

Pay special attention to line 32 where we grab the requested URI from the Request object. There's a lot of interesting data in this object. Take a peek at the AWS.Status package for more on how to entice this object to give up its secrets.

For the sake of completeness lets also take a peek at the Hello_World package:

As you can see this is very similar to Not_Found, which is only natural since both packages do nearly the same thing: Deliver some very simple HTML. Note that we grab the user agent from the Request object instead of the URI we used in the Not_Found handler.

And that is it. I hope that by now it is clear that adding HTTP support to your Ada program is very easy. Of course there's a lot more that can be done with AWS, and I will deal with that in coming articles. For example building the content using strings is rather lame, so in the next article I will turn my attention to using the AWS templating system for the content (Templates_Parser).

Oh and on a final note let me just mention the GPR file for this tutorial program, especially the very first line that says with "aws";. Yes, that is all you need to add to your project file for gnatmake to know that you want it to pull in AWS when compiling your project. It's wonderfully simple. Enjoy!