Gem #29: Introduction to the Ada Web Server (AWS)

by Pascal Obry —EDF R&D

Let's get started…

This Gem presents a basic introduction the Ada Web Server (AWS). The core components of AWS comprise a Web server which supports the HTTP protocol. This Web server can be embedded into any application. Common usages are:

- To develop a full Web application.
- Adding a GUI to a mostly batch-oriented application like a long-running simulation that we want to get control of from time to time.
- To develop a distributed application exchanging messages using the HTTP or SOAP protocols.

The AWS HTTP module handles the decoding of the HTTP request and encoding of the user's response. It uses a callback mechanism to interact with the user's application. Let's take the famous Hello World example and see how it could be translated into the AWS framework.

First the user's hello_world callback, which is a function with a single parameter of type Status.Data and returning a Response.Data object:

   with AWS.MIME;
   with AWS.Response;
   with AWS.Status;

   package body CB is
      use AWS;
      function Hello_World (Request : in Status.Data) return Response.Data is
      begin
         return Response.Build (MIME.Text_HTML, "<p>Hello World!</p>");
      end Hello_World;
   end CB;

The AWS.Response unit contains many constructors. Response.Build is one. Another is Response.File, for returning a file. Various high-level constructors are provided, such as for streaming data.

Now here's the main subprogram with the server:

   with AWS.Server;
   with CB;
   procedure Hello_World is
      use AWS;
      HTTP : Server.HTTP;
   begin
      Server.Start (HTTP, "Hello_World", Callback => CB.Hello_World'Access);
      delay 60.0;
      Server.Shutdown (HTTP);
   end Hello_World;

That's it. The start routine registers the CB.Hello_World procedure as the user's callback and starts the server using the default port, which is 8080. After running the hello_world executable, by entering the URL http://localhost:8080/ into your web browser you'll receive the message "Hello World", and this will occur for any URI. So entering http://localhost:8080/whatever will also return "Hello World". After 60 seconds the server will shut down and the program will exit.

All HTTP parameters are available from the Request parameter of the Hello_World callback. So let's add a local constant to get the actual URI received:

   URI : constant String := Status.URI (Request);

Then we change the Response.Build call to:

   return Response.Build
      (MIME.Text_HTML, "<p>Hello World! URI=" & URI & "</p>");

After restarting the server, entering http://localhost:8080/home into the web browser will display "Hello World! URI=/home".

An if/elsif construction can be used to test for each URI that must be handled by the server:

   if URI = "..." then
      ...
   elsif URI = "..." then
      ...
   else
      return Response.Build
         (MIME.Text_HTML,
          "<p>Not found : URI=" & URI & "</p>",
          Status_Code => Messages.S404);
   end if;

Using callbacks is simple, but when the application becomes larger it's easier to use dispatchers. So let's change the Hello World program to use dispatchers:

   with AWS.Response;
   with AWS.Status;
   with AWS.Dispatchers;
   package CB is
      use AWS;

      type Hello_World is new Dispatchers.Handler with null record;

      overriding function Dispatch
        (Handler : in Hello_World;
         Request : in Status.Data) return Response.Data;
   private
      overriding function Clone (Element : in Hello_World) return Hello_World;
   end CB;

The body of Dispatch is identical to the Hello_World callback above. The Dispatchers.Handler type has a clonable interface, and Clone in this case is trivial:

   overriding function Clone (Element : in Hello_World) return Hello_World is
   begin
      return Element;
   end Clone;

In this case the main subprogram is slightly larger:

   with AWS.Config;
   with AWS.Server;
   with AWS.Services.Dispatchers.URI;
   with CB;

   procedure Hello_World is
      use AWS;
      HTTP : Server.HTTP;
      Conf : Config.Object;
      HW   : CB.Hello_World;
      Root : Services.Dispatchers.URI.Handler;
   begin
      Services.Dispatchers.URI.Register (Root, "/hello", HW);
      Server.Start (HTTP, Root, Conf);
      delay 60.0;
      Server.Shutdown (HTTP);
  end Hello_World;

The main difference is that we have registered the HW dispatcher to be used only for URI "/hello" (http://localhost:8080/hello). For any other URL, a 404 error message will be sent by the dispatcher module. It's also possible to use a regular expression or a prefix if needed. The configuration object can be set to change any server settings, such as the port, the number of simultaneous connections, timeouts, etc.

Many kinds of dispatchers exist, not only for supporting URIs (as in this example), but also for handling virtual hosts, request methods (POST/GET) and services for linking dispatchers, as well as dispatching based on timers (see child units of AWS.Services.Dispatchers). For an example of a simple API for converting a callback to a dispatcher see AWS.Dispatchers.Callback.

Various other services are offered directly by AWS, including HTTP/SOAP, WSDL, Ajax, SMTP, and a templates engine. But covering all of those is beyond the scope of this introduction.

Related Source Code

Ada Gems example files are distributed by AdaCore and may be used or modified for any purpose without restrictions.

aws_basic.zip