Boost logo

Boost :

From: Steven Siloti (ssiloti_at_[hidden])
Date: 2007-04-22 22:25:52


Darren Garvey wrote:
> Hi Steven, sorry about the late reply (I've been travelling).
>
> On 07/04/07, Steven Siloti < ssiloti_at_[hidden] > wrote:
>
>> I've been working on a library for writing web applications (via any
>> sort of gateway: cgi, fcgi, scgi, mod_proxy_http, etc.) that might be of
>> interest here. This is all very preliminary as it's something I hack on
>> in my spare time more-or-less as an exercise (I don't even have much of
>> a use for such a library!).
>>
>> It's design is inspired by Python's WSGI in that you build up a context
>> based on a stack of middleware classes which act as mixins to provide
>> abstractions and services on top of a base request/response provided by
>> the gateway.
>>
>
>
> I've looked at WSGI in the past for ideas, so I'm vaguely familiar with it.
> I'm taking a proper look at it now. If you'd be willing to discuss your
> approach more (by private email if you prefer), I'd love to share ideas.
>
Certainly.
> The mixin system I've created allows mixins to make arbitrary
>
>> additions/changes to the context structure. At the core is a stack class
>> which handles combining multiple mixins into a single (potential)
>> context. Here what it looks like right now:
>>
>> http://magila.googlepages.com/context.hpp
>>
>> It's actual method of operation is somewhat involved. Basically each
>> mixin consists of three types: a constructor object, a context
>> definition structure, and context instance structure. The constructor
>> object is the mixin class itself, it's responsible for carrying any
>> initialization parameters to the final instance.
>>
>> Inside the mixin class a context definition structure (ctx_type) and a
>> context instance structure (type) are defined. The idea is to first
>> build up a type which defines the final structure of the context then
>> pass that as a template parameter to the instance type which actually
>> instantiates the various structures contained therein. The two step
>> process is necessary because otherwise you get a loop were a derived
>> class's type depends on it's base who's type depends on the derived
>> class's type.
>>
>> Here's an example of a basic http context and a simple mixin which
>> parses the Content-Length header out as an int and provides an int in
>> the response which is assigned to the response's Content-Length header.
>>
>> http://magila.googlepages.com/http_context.hpp
>> http://magila.googlepages.com/http_mixins.hpp
>>
>> These can be combined using the stack class as such:
>>
>> stack<http::context, http::content_length>
>>
>> The result is a type which is itself a valid mixin. This allows a lot of
>> flexibility in how you build up a context. Once you've built up you
>> context with all the middleware you want you pass it as a template
>> parameter to a gateway class which is responsible for adding it's own
>> base request/response part of the context then calling process_request()
>> to kick off the upper layers in the stack.
>>
>> To hide all this complexity from the end user we use a resource mixin
>> which takes a class as a template parameter and calls an appropriate
>> member function based on the http method in the request and passes it a
>> reference to itself. See:
>>
>> http://magila.googlepages.com/http_resource.hpp
>>
>> And finally a trivial example of how it all looks to the end user:
>>
>> http://magila.googlepages.com/swgl_test.cpp
>>
>
>
> The library sounds quite industrial (which is possibly a good thing, IMO),
> but this example _looks_ industrial. Is this really the simplest case?
> Also, if I'd written a large program using this framework, is there a simple
> path to make the same program work with a different protocol?
>
The test code doesn't really represent a realistic use case. In a full
blown application you'd have more or all resources inheriting from a
common base class which defines a standard set of mixins. You'd also
probably not be directly filling in the http response but rather
interacting with a set of higher level mixins for handling things like
cookies, authentication, and document templates.

With a little work I could certainly enable a shorter "Hello World"
case. Chiefly by adding some default behavior that makes the gateway act
similar to the cgi::request class you present on cgi.sf.net when not
given any template parameter.

The gateway handles all the protocol specifics in a way analogous to the
service concept presented on cgi.sf.net. Changing protocols is just a
matter of changing the gateway type, everything above doesn't care as
long as they get a standard http context structure at the base of the stack.

> You'll note I've left out the gateway class. What I'm using now is just
>
>> a trivial standalone server using asio that I wrote just for debugging
>> that's not in any shape that I'd want to show :).
>>
>
>
> I've had a look at the gateway class (at least the one on the server,
> sorry). I'm surprised you didn't show it too, as I think it makes the rest
> of your code much more understandable. :)
>
> As an example, to create an instance of the final context you would do
>
>> something like this:
>>
>> stack<base_context, upper_context>::type<> ctx(base_context(req, res),
>> upper_context(foo, bar, bas));
>>
>> And thus each mixin's ::type's constructor would be passed an object
>> inheriting from it's containing class and which has been constructed
>> from the instance of that class passed to the stack's constructor.
>>
>
>
> It's an interesting idea and the syntax is somewhat appealing too. I'm not
> sure how necessary it is for a 'from scratch' approach though... How close
> is this to Python's WSGI (if that's a reasonable question)?
>
The relation to WSGI is rather loose. I chiefly took the concept of a
gateway feeding requests through a stack of middleware applications to
the user. WSGI is a very pythonic API and thus does a lot of things
which don't map very gracefully to C++.

I'm actually not convinced myself that allowing arbitrary type
modification of the context by middleware is worth the trouble it
causes. A simpler approach would be to restrict middleware to adding a
single uniquely named member to the root of the context. Essentially
making the context structure analogous to the environment dictionary in
WSGI. You'd loose the ability to add things directly to the request so
ctx.request.content_length would change to ctx.content_length.request.
But it would eliminate the need for the two stage buildup of the context
type along with a slew of potential gotchas involving middleware mucking
around with the context type in inappropriate ways.

Steven Siloti


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk