libnghttp2_asio: High level HTTP/2 C++ library

libnghttp2_asio is C++ library built on top of libnghttp2 and provides high level abstraction API to build HTTP/2 applications. It depends on Boost::ASIO library and OpenSSL. Currently libnghttp2_asio provides server side API.

libnghttp2_asio is not built by default. Use --enable-asio-lib configure flag to build libnghttp2_asio. The required Boost libraries are:

  • Boost::Asio
  • Boost::System
  • Boost::Thread

To use libnghttp2_asio, first include following header file:

#include <nghttp2/asio_http2.h>

Also take a look at that header file asio_http2.h.

Server API

Server API is designed to build HTTP/2 server very easily to utilize C++11 anonymous function and closure. The bare minimum example of HTTP/2 server looks like this:

#include <nghttp2/asio_http2.h>

using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;

int main(int argc, char* argv[])
{
  http2 server;

  server.listen
    ("*", 3000,
     [](std::shared_ptr<request> req, std::shared_ptr<response> res)
     {
       res->write_head(200);
       res->end("hello, world");
     });
}

First we instantiate nghttp2::asio_http2::server::http2 object. Then call nghttp2::asio_http2::server::http2::listen function with address and port to listen to and callback function, namely “request callback”, invoked when request arrives.

The req and res represent HTTP request and response respectively. nghttp2::asio_http2_::server::response::write_head constructs HTTP response header fields. The first argument is HTTP status code, in the above example, which is 200. The second argument, which is omitted in the above example, is additional header fields to send.

nghttp2::asio_http2::server::response::end sends responde body. In the above example, we send string “hello, world”.

Serving static files and enabling SSL/TLS

In this example, we serve a couple of static files and also enable SSL/TLS.

#include <nghttp2/asio_http2.h>

using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;

int main(int argc, char* argv[])
{
  http2 server;

  server.tls("server.key", "server.crt");

  server.listen
    ("*", 3000,
     [](std::shared_ptr<request> req, std::shared_ptr<response> res)
     {
       if(req->path() == "/" || req->path() == "/index.html") {
         res->write_head(200);
         res->end(file_reader("index.html"));
       } else {
         res->write_head(404);
         res->end("<html><head><title>404</title></head>"
                  "<body>404 Not Found</body></html>");
       }
     });
}

Specifying path to private key file and certificate file in nghttp2::asio_http2::server::http2::tls will enable SSL/TLS. Both files must be in PEM format.

In the above example, if request path is either “/” or “/index.html”, we serve index.html file in the current working directory. nghttp2::asio_http2::server::response::end has overload to take function of type nghttp2::asio_http2::read_cb and application pass its implementation to generate response body. For the convenience, libnghttp2_asio library provides nghttp2::asio_http2::file_reader function to generate function to server static file.

Server push

Server push is also supported.

#include <nghttp2/asio_http2.h>

using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;

int main(int argc, char* argv[])
{
  http2 server;

  server.tls("server.key", "server.crt");

  server.listen
    ("*", 3000,
     [](std::shared_ptr<request> req, std::shared_ptr<response> res)
     {
       if(req->path() == "/") {
         req->push("GET", "/my.css");

         res->write_head(200);
         res->end(file_reader("index.html"));

         return;
       }

       if(req->path() == "/my.css") {
         res->write_head(200);
         res->end(file_reader("my.css"));

         return;
       }

       res->write_head(404);
       res->end("<html><head><title>404</title></head>"
                "<body>404 Not Found</body></html>");
     });
}

When client requested “/”, we push “/my.css”. To push resource, call nghttp2::asio_http2::server::request::push function with desired method and path. Later, the callback will be called with the pushed resource “/my.css”.

Enable multi-threading

Enabling multi-threading is very easy. Just call nghttp2::asio_http2::server::http2::num_threads function with the desired number of threads:

http2 server;

// Use 4 native threads
server.num_threads(4);

Run blocking tasks in background thread

The request callback is called in the same thread where HTTP request is handled. And many connections shares the same thread, we cannot directly run blocking tasks in request callback.

To run blocking tasks, use nghttp2::asio_http2::server::request::run_task. The passed callback will be executed in the different thread from the thread where request callback was executed. So application can perform blocking task there. The example follows:

#include <unistd.h>
#include <nghttp2/asio_http2.h>

using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;

int main(int argc, char* argv[])
{
  http2 server;

  server.num_concurrent_tasks(16);

  server.listen
    ("*", 3000,
     [](std::shared_ptr<request> req, std::shared_ptr<response> res)
     {
       req->run_task
         ([res](channel& channel)
          {
            // executed in different thread than the thread where
            // request callback was executed.

            // using res directly here is not safe.  Capturing it by
            // value is safe because it is std::shared_ptr.

            sleep(1);

            channel.post
              ([res]()
               {
                 // executed in the same thread where request callback
                 // was executed.
                 res->write_head(200);
                 res->end("hello, world");
               });
          });
     });
}

First we set the number of background threads which run tasks. By default it is set to 1. In this example, we set it to 16, so at most 16 tasks can be executed concurrently without blocking handling new requests.

We call req->run_task() to execute task in background thread. In the passed callback, we just simply sleeps 1 second. After sleep is over, we schedule another callback to send response to the client. Since the callback passed to req->run_task() is executed in the different thread from the thread where request callback is called, using req or res object directly there may cause undefined behaviour. To avoid this issue, we can use nghttp2::asio_http2::channel::post by supplying a callback which in turn get called in the same thread where request callback was called.