Hi list,
I've been designing a HTTP library for Boost inclusion for the last two
years. First it was funded as a GSoC 2014 project. Then I continued to
polish the implemented bits on my own and the library was peer reviewed
(and rejected) last year.
I'm currently addressing the requests made during last year's review and
one of the requested items was a higher-level interface built on top the
current building blocks that would be easier to use. One of such
abstractions would be an HTTP request router.
I usually do my research and check what others have done (not only in C++),
read a lot of spec and do a design on my own (sometimes I ask for feedback
of a few chosen ones). I rarely ask for public feedback because I usually
can solve the interface issues on my own. However, Boost.Http is a new
thing and I'm exploring an unexplored ground. I've been facing an issue for
the last months and I'd like to have "open" feedback of anyone interested
in this topic. Maybe it'll speed me up.
The issue I'm facing is that (1) I haven't found enough diversity of HTTP
request routers and (2) Boost.Http is a new thing and problems must be
approached differently (what other HTTP library will let you do HTTP over
uTP using Boost.Asio threading/async model?).
Detailing the problem a little more: I categorize current HTTP request
routers into two styles.
The "tree style" is the style where you declare some paths to be handled
and each request will be routed to at most one handler. You'll find this
style usually when the author advertise a ReST support library
(e.g.crow[1]).
The "chained style" is the style where you declare a chain of
pairs (or anything in these lines) and one handler can "give
up" and pass the route to the next in line. Rules in the "chained side" can
overlap and an order exists (and must be explicit). You can find this style
being used frequently in the Node.JS frameworks (e.g. connectjs[2]). You
could reimplement these NodeJS APIs in C++ keeping a lot of the same
look'n'feel. For instance, take a look at node.native[3].
I thought I could combine and allow both styles. Actually, Boost.Http don't
force you to use any style and both could be used. However I wanted to
implement both allowing some kind of interoperability and communication
between them. There are a lot of open questions right now and I decided to
implement both individually and only then come back trying to unify them.
So, for now, my problem is designing the "chained style". Even implementing
only "one style" I had open questions and I decided to draft an
implementation to maybe give some light to my own mind.
One thing that I was trying to address would be the "next" function. If you
take a look at the connectjs library[2], you'll see that the "next"
function is an argument passed to the handler and, if the handler wants to
give up and pass the route to the next handler, it only needs to call
"next". This behaviour could be emulated in C++ using std::function<>.
An alternative design would be to make the handler return a bool. This is
actually the design I've chose in Tufão[4] and I regret this decision to
this date. The bad thing about this approach is the too-much-sync minded.
If you need to communicate with a database to know whether you'll be able
to handle the route then you need to block the whole thread under this
approach.
Knowing this is Boost and Boost is about serious C++ users I know I must be
concerned about performance. Therefore, many may not like the
std::function<> approach. It can imply some hidden allocation that you pay
even if the handler is not going to give up on the route.
Another approach would be to turn the handlers into template functors.
However, this would be too much burden on the user. And if I chose this
route, what would be the type of "next"?
The approach I've chose was to encode the "next" function into the "request
object". In Boost.Http, there is no "request object" like you'd be used to
when using other APIs. Boost.Http exposes a socket and the socket will fill
a message object of your choice (let's call it the input message) and later
you pass it another message object to execute the reply (let's call it the
output message). So the "next" function is really encoded in the input
message.
Other APIs use a passive style and constantly check if there is network
activity and schedule new socket reads. Boost.Asio is more explicit and
Boost.Http follows along. An active style is useful because it'll allow you
to defer new operations to later when your server is under heavy load.
Anyway, I felt that maybe I'll need a "done" function and I've added an
empty one for now. I'll see if I'll really need it later when I integrate
everything together. For now, just know that the "done" function does
nothing.
Anyway, this is my draft and I'd like to know your opinion:
https://github.com/vinipsmaker/asiohttpserver/blob/router/example/router.cpp...
You can see that I'm declaring a static router because most of the time you
don't change the rules at runtime. This router is actually more difficult
to design and it'll be easier to add a "dynamic router" later on.
I also want to mention that I'm aware of another effort by another
developer (Uisleandro) trying to add a router to Boost.Http:
https://github.com/uisleandro/Router_for_Boost.Http
I also want to mention that performance of the implementation isn't really
something I care about for the draft. If it were at another place it could
mean that I'm not interested in implementation feedback. However it doesn't
mean that I know the best way to implement these APIs and I'm still
interested in implementation feedback (even more because I don't remember
if I ever implemented much TMP before).
Some points that I'll tackle LATER will be:
- A router is a handler too, then you can have nested routers.
- The router doesn't take the rule, only the handler. Later, I'll make
algorithms that adapts rules and conditionally call your handler or pass
the route to the next handler. This design is more flexible and I wish I
could also use it in "tree style too".
- How to create Message types more easily? I've read about
component-based architectures before, but I never really understood them.
Is there any library that was proposed to Boost in these lines that I could
take a look at?
[1] https://github.com/ipkn/crow
[2] https://github.com/senchalabs/connect#mount-middleware
[3] https://github.com/d5/node.native
[4]
http://vinipsmaker.github.io/tufao/ref/1.x/class_tufao_1_1_http_server_reque...
--
Vinícius dos Santos Oliveira
https://vinipsmaker.github.io/