Onion C++ Bindings best practices / patterns. pt. 1.

Normally not even I use the C bindings for onion. C is a great language to focus on algorithms and details, but for web development its too DIY. Normally to use Onion I use C++. The bindings are not 100% complete, but enough to make performant (space, size and speed) servers. As I use it more I will try to describe the best practices I can find.

Use Onion::Url

Onion::Url is the basic url regex based dispatcher. It uses onion_url under the hood. It is so nice you can add url handlers to url handlers, creating the onion structure:


Admin admin; // A "module".

Onion::Url other_urls;
other_urls.add("static", "Static text data", HTTP_OK); // Static data can be added just like that. Useful for small snippets.
other_urls.add("^lambda/(.*)$", [](Onion::Request &req, Onion::Response &res){ // Lambdas can be used too. Regex can be used to capture data.
  res<<"Hello world"<<req.query().get("1");
  return OCS_PROCESSED;
});

Onion::Url root(onion_server); // Get the root handler.

root.add("^admin/", admin.urls()); // And add sub urls.
root.add("^other/", other_urls); // Or a simple url.
root.add("^static/", onion_handler_export_local_new( static_path.c_str() )); // Also can add simple C handlers.

There are many many things that can be done with urls, but I will resume them here: If you use regex, start with ^. $ is a nice way to stop it if this is the end of the string. Groups can be created using (). If its not a regex, its fully compared, so its equivalent to ^text$.

c_handler to use the C bindings.

All Onion:: objects have a c_handler() method that returns the equivalent C pointer to use with C onion functions. So everything you can do in C you can do it with C++ too, even if the bindings are not complete. Whats more important, almost all objects can be created from a C pointer as well, so it works both directions.

Once we have this knowledge that means that, for example, all the existing C handlers, as the webdav hander, static directories or otemplate templates, can be used seamlessly, although some care may be needed.

Use of otemplate templates.

If you are not using otemplates, do it now.

To use it under C++, and until a better mechanism is developed, use the following pattern:


onion_connection_status handler(Onion::Request &req, Onion::Response &res){
  Onion::Dict context = globalContext(req);

  onion_dict *ctx=onion_dict_dup(context.c_handler());
  template_html(ctx, req.c_handler());
  return OCS_PROCESSED;
}

Actually I have a render_to_response, not yet in github, quite similar to Django’s:


namespace Onion{
 typedef std::function<void (onion_dict *d, onion_response *r)> template_f;
}

onion_connection_status Onion::render_to_response(Onion::template_f fn, const Onion::Dict& context, Onion::Response &res)
{
 ONION_DEBUG("Context: %s", context.toJSON().c_str());

 onion_dict *d=const_cast<Onion::Dict*>(&context)->c_handler();
 onion_dict_dup(d);
 
 fn(d, res.c_handler());
 
 return OCS_PROCESSED;
}

Use a context

As seen above, a global function that returns the context can be very useful. In django it is composed by the RequestContext function, that calls all the TEMPLATE_CONTEXT_PROCESSORS.

There are no builtin functionalities like that in Onion, so that is manual, but its a good idea. So create your own globalContext(), that receives the request handler, and fill out a dictionary with the context to pass to the otemplate.

Store state in session

This is a general advise for all web developers.

The global state of the server is not the state of one particular user. Do not use it for that. Actually if you avoid this, you may gain easier implementations, without mutexes nor anything like that. The session is already mutexed, so you get one valid session always, and after modifying, you save a proper one as well. For the event that the user do two petitions at the same time, state may be one or another, so dont push it too hard client side. And if you do, then use a proper database, not the session.

Leave a Reply