A Day in a Pile of Work

My personal Web development blog

Roadmap for the 0.3 series in Valum

The 0.2 series has focused on bringing the basics that will guide the upcoming features.

Here’s the roadmap for the next release 0.3 of Valum:

  • aggressive optimization based on GLib.Sequence
  • content negociation
  • HTTP authentification (basic and digest)
  • static resource delivery
  • VSGI loader
  • multipart streams
  • typed rule parameters using GType
  • filters
  • Python and Gjs bindings

The following features have been integrated in the trunk:

  • flags for HTTP methods
  • default HEAD to GET (still need to strip the body)
  • routing context rather than a stack that provide states and services
  • shared libraries for VSGI implementations compatible with GLib.TypeModule
  • Router.asterisk to handle asterisk URI *
  • inheritence for Route to split concerns
  • less responsibilities in Route subclasses to encapsulate scopes, types and other features in Router
  • register_type API for the incoming typed parameters

Status handlers has been reworked and cleaned up to cover more cases using a switch block.

  • status handlers are executed in the Router context rather than a double try-catch for consistent behaviours
  • the error message is only used for headers that MUST be part of the response, except for redirection codes

Aggressive Optimizations

Okay, let me calm down. The idea is to bring routing in the O(log n) world by using a GLib.Sequence which consist of a binary tree data structure.

Basically, we have a sequence of Route objects and we try to find in the least number of attempts the next one that accepts a given request.

In short, here’s what should be done:

  • sorting by exclusive criteria (method, version, …)
  • sorting by usage
  • pre-lookup using a trie-based index

I still need to figure out more, it’s all in issue #144.

Middlewares

With the common ground set, this series will bring useful middlewares to process Request and Response efficiently.

To avoid confusion about what a middleware is, I decided to apply the term only to HandlerCallback instances.

Content Negociation

Content negociation is implemented as a set of middlewares which check the request, set the appropriate headers and forward the processing.

If the produced resource is not acceptable, next is called unless NegociateFlags.FINAL is specified. Then, a 406 Not Acceptable is raised.

app.get ("", accept ("text/html", (req, res) => {
    res.body.write_all ("<!DOCTYPE html><html>Hello world!</html>");
}));

app.get ("", accept ("text/plain", (req, res) => {
    res.body.write_all ("Hello world!");
}, NegociateFlags.FINAL));

The latests improvements are awaiting tests.

Static Resources Delivery

Static resources can be served from a path or a resource bundle. It support multiple options:

  • ETag which identify the resource uniquely to prevent transmission
  • Last-Modified (only for path)
  • X-Sendfile if the HTTP server supports it
  • mark the delivered resource as public for caches
  • deliver asynchronously

Delivery from GLib.Resource defaults on the global resources.

The only requirement is to provide a path key in the routing context, which can be easily done with a rule or a regular expression:

using Valum.Static;

app.get ("<path:path>", serve_from_resources (ServeFlags.ENABLE_ETAG));

It’s living here in #143.

Flags for HTTP methods

This is a really nice feature.

HTTP methods are now hanlded as flags in the Router to perform very efficient match.

Standard methods are available in Method enumeration along with the following symbols:

  • ALL to capture all standard method
  • OTHER to capture non-standard method
  • ANY to capture any method

If OTHER is specified, it must be implemented in the matching callback.

all and methods have been removed from Router for obvious reasons and method has been renamed to rule to remain consistent.

app.rule (Method.GET | Method.POST, "", () => {

});

app.rule (Method.ALL, "", () => {

});

app.rule (Method.ANY, "", () => {

});

Method.GET actually stands for Method.ONLY_GET | Method.HEAD so that it can also capture HEAD requests. It’s pretty handy, but I still need to figure out how to strip the produced body.

VSGI Loader

More details here: #130.

Loading of application described as a dynamic module (see GModule for more details) will be brought by a small utility named vsgi. It will be able to spawn instances of the application using a VSGI implementation.

The application has to be written in a specific manner and provide at least one entry point:

public HandlerCallback app;

[CCode (cname = "g_module_check_init")]
public check_init () {
    var _app = new Router ();

    ...
    app = _app.handle;
}

[CCode (cname = "g_module_unload")]
public void unload () {
    app = null;
}
vsgi --directory=build --server=scgi app:app

All VSGI implementations are loadable and compatible with GLib.TypeModule.

The application is automatically reloaded on SIGHUP and it should be possible to implement live reloading with GLib.FileMonitor to facilitate the development as well as integration of Ivy to beautify the stack trace.

Multipart I/O Streams

The multipart stream is essential for any web application that would let clients submit files.

The implementation will be compatible with Soup.MultipartInputStream.

app.post ("", (req, res) => {
   var multipart_body = new MultipartInputStream (req.headers, req.body);

   InputStream? part;
   MessageHeaders part_headers;
   while (part = multipart_body.next_part (out part_headers) != null) {
      if (part_headers.get_content_disposition ())
   }
});

Typed Rule Parameters

The use of GType to interpret and convert rule parameters is essential for an optimal integration with GLib.

The idea is to declare types on the Router and attempt a conversion before pushing the parameter on the context.

app.register_type ("int", /\w+/, typeof (int));

app.get ("<int:i>", (req, res, next, ctx) => {
    var i = ctx["i"].get_int ();
});

Type conversion can be registered with GLib.Value.register_transform_func:

Value.register_transform_func (typeof (string),
                               typeof (int),
                               (src, ref dest) => {
    dest = (Value) int.parse ((string) src);
});

One useful approach would be to reverse that process to generate URLs given a rule.

Posted on .