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:
- Make sure you have AWS installed on your system
- Git clone the code from this article 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:
$ git clone --recursive http://forge.open-do.org/anonscm/git/aws/aws.git
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:
$ cd Ada_Web_Server_Tutorial_1/ $ make $ cd exe/ $ ./aws_tutorial
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:
with Ada.Text_IO; with AWS.Config; with AWS.Default; with AWS.Server; with Handlers; procedure AWS_Tutorial is Web_Server : AWS.Server.HTTP; begin Ada.Text_IO.Put_Line ("I'm available at localhost port" & Positive'Image (AWS.Default.Server_Port) & ". Press q to kill me."); Ada.Text_IO.Put_Line ("http://localhost:8080/helloworld"); AWS.Server.Start (Web_Server => Web_Server, Dispatcher => Handlers.Get_Dispatcher, Config => AWS.Config.Get_Current); AWS.Server.Wait (AWS.Server.Q_Key_Pressed); AWS.Server.Shutdown (Web_Server); end AWS_Tutorial;
Simplicity at work eh’? Lets stroll through the code together.
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
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
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/handlers.ad[sb] files. Lets take a peek at the specification file first:
with AWS.Services.Dispatchers.URI; package Handlers is function Get_Dispatcher return AWS.Services.Dispatchers.URI.Handler; -- Return the content handlers for our server. end Handlers;
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:
with Hello_World; with Not_Found; package body Handlers is ---------------------- -- Get_Dispatcher -- ---------------------- function Get_Dispatcher return AWS.Services.Dispatchers.URI.Handler is Dispatcher : AWS.Services.Dispatchers.URI.Handler; begin Dispatcher.Register_Default_Callback (Action => Not_Found.Callback); -- The default callback is going to be our 404 not found handler. If -- a requested resource isn't specifically defined here, we're going -- to return a 404 to the client. Dispatcher.Register (URI => "/helloworld", Action => Hello_World.Callback); -- The /helloworld resource. return Dispatcher; end Get_Dispatcher; end Handlers;
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
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:
with AWS.Dispatchers.Callback; package Not_Found is function Callback return AWS.Dispatchers.Callback.Handler; -- Return a callback for the Not_Found (404) response. end Not_Found;
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:
with AWS.Messages; with AWS.MIME; with AWS.Response; with AWS.Status; package body Not_Found is function Generate_Content (Request : in AWS.Status.Data) return AWS.Response.Data; -- Generate the 404 response. ---------------- -- Callback -- ---------------- function Callback return AWS.Dispatchers.Callback.Handler is begin return AWS.Dispatchers.Callback.Create (Generate_Content'Access); end Callback; ------------------------ -- Generate_Content -- ------------------------ function Generate_Content (Request : in AWS.Status.Data) return AWS.Response.Data is Resource : constant String := AWS.Status.URI (Request); begin return AWS.Response.Build (Content_Type => AWS.MIME.Text_HTML, Message_Body => "<h1>Not found</h1>" & "<p>Resource '" & Resource & "' not found</p>", Status_Code => AWS.Messages.S404); end Generate_Content; end Not_Found;
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:
type Callback is access function (Request : AWS.Status.Data) return AWS.Response.Data;
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
with AWS.Dispatchers.Callback; package Hello_World is function Callback return AWS.Dispatchers.Callback.Handler; -- Return a callback for the Hello World! response. end Hello_World;
with AWS.Messages; with AWS.MIME; with AWS.Response; with AWS.Status; package body Hello_World is function Generate_Content (Request : in AWS.Status.Data) return AWS.Response.Data; -- Generate the Hello World! response. ---------------- -- Callback -- ---------------- function Callback return AWS.Dispatchers.Callback.Handler is begin return AWS.Dispatchers.Callback.Create (Generate_Content'Access); end Callback; ------------------------ -- Generate_Content -- ------------------------ function Generate_Content (Request : in AWS.Status.Data) return AWS.Response.Data is Browser : constant String := AWS.Status.User_Agent (Request); begin return AWS.Response.Build (Content_Type => AWS.MIME.Text_HTML, Message_Body => "<h1>Hello World!</h1>" & "<p>Browser used : " & Browser & "</p>", Status_Code => AWS.Messages.S200); end Generate_Content; end Hello_World;
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
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!