cpp-netlib includes and implements two distinct HTTP server implementations that you can use and embed in your own applications. Both HTTP Server implementations:
- Cannot be copied. This means you may have to store instances of the HTTP Server in dynamic memory if you intend to use them as function parameters or pass them around in smart pointers of by reference.
- Assume that requests made are independent of each other. None of the HTTP Server implementations support request pipelining (yet) so a single connection only deals with a single request.
- Are header-only and are compiled-into your application. Future releases in case you want to upgrade the implementation you are using in your application will be distributed as header-only implementations, which means you have to re-compile your application to use a newer version of the implementations.
The HTTP Servers have different semantics, and in some cases require different APIs from the supplied template parameters.
There are two different user-facing template classes that differentiate the Synchronous Servers from the Asynchronous Servers. Both templates take a single template parameter named Handler which describes the type of the Handler function object.
There are two different Handler concepts, one concept for Synchronous Servers and another for Asynchronous Servers.
The SynchronousHandler concept for Synchronous Servers is described by the following table:
Legend:
Construct | Return Type | Description |
---|---|---|
h(req,res) | void | Handle the request; res is passed in as a non-const lvalue, which represents the response to be returned to the client performing the request. |
More information about the internals of the Synchronous Servers can be found in the following section.
The AsynchronousHandler concept for Asynchronous Servers is described by the following table:
Legend:
Construct | Return Type | Description |
---|---|---|
h(req, conn) | void | Handle the request; conn is a shared pointer which exposes functions for writing to and reading from the connection. |
More information about the internals of the Asynchronous Servers can be found in the following section.
The synchronous server implementation is represented by the template server in namespace boost::network::http. The server template takes in a single template parameter named Handler which models the SynchronousHandler concept (described above).
An instance of Handler is taken in by reference to the constructor of the HTTP server. This means the Handler is not copied around and only a single instance of the handler is used for all connections and requests performed against the HTTP server.
Warning
It is important to note that the HTTP server does not implement any locking upon invoking the Handler. In case you have any state in the Handler that will be associated with the synchronous server, you would have to implement your own synchronization internal to the Handler implementation. This matters especially if you run the synchronous server in multiple threads.
The general pattern of usage for the HTTP Server template is shown below:
struct handler;
typedef boost::network::http::server<handler> http_server;
struct handler {
void operator()(
http_server::request const & req,
http_server::response & res
) {
// do something, and then edit the res object here.
}
};
More information about the actual HTTP Server API follows in the next section. It is important to understand that the HTTP Server is actually embedded in your application, which means you can expose almost all your application logic through the Handler type, which you can also initialize appropriately.
The following sections assume that the following file has been included:
#include <boost/network/include/http/server.hpp>
And that the following typedef’s have been put in place:
struct handler_type;
typedef boost::network::http::server<handler_type> http_server;
struct handler_type {
void operator()(
http_server::request const & request,
http_server::response & response
) {
// do something here
}
};
Parameter Name | Type | Description |
---|---|---|
_address | string_type | The hostname or IP address from which the server should be bound to. This parameter is required. |
_port | string_type | The port to which the server should bind and listen to. This parameter is required. |
_handler | Handler & | An lvalue reference to an instance of Handler. This parameter is required. |
_thread_pool | boost::network::utils::thread_pool & | An lvalue reference to an instance of boost::network::utils::thread_pool – this is the thread pool from where the handler is invoked. This parameter is only applicable and required for async_server instances. |
_io_service | boost::asio::io_service & | An optional lvalue to an instance of boost::asio::io_service which allows the server to use an already-constructed boost::asio::io_service instance instead of instantiating one that it manages. |
_reuse_address | bool | A boolean that specifies whether to re-use the address and port on which the server will be bound to. This enables or disables the socket option for listener sockets. The default is false. |
_report_aborted | bool | A boolean that specifies whether the listening socket should report aborted connection attempts to the accept handler (an internal detail of cpp-netlib). This is put in place to allow for future-proofing the code in case an optional error handler function is supported in later releases of cpp-netlib. The default is false. |
_receive_buffer_size | int | The size of the socket’s receive buffer. The default is defined by Boost.Asio and is platform-dependent. |
_send_buffer_size | int | The size of the socket’s send buffer. The default is defined by Boost.Asio and is platform-dependent. |
_receive_low_watermark | int | The size of the socket’s low watermark for its receive buffer. The default is defined by Boost.Asio and is platform-dependent. |
_send_buffer_size | int | The size of the socket’s send low watermark for its send buffer. The default is defined by Boost.Asio and is platform-dependent. |
_non_blocking_io | bool | An optional bool to define whether the socket should use non-blocking I/O in case the platform supports it. The default is true. |
_linger | bool | An optional bool to determine whether the socket should linger in case there’s still data to be sent out at the time of its closing. The default is true. |
_linger_timeout | int | An optional int to define the timeout to wait for socket closes before it is set to linger. The default is 0. |
To use the above supported named parameters, you’ll have code that looks like the following:
using namespace boost::network::http; // parameters are in this namespace
boost::asio::io_service my_io_service;
boost::network::utils::thread_pool pool(2);
handler handler_instance;
async_server<handler> instance(_address="0.0.0.0", _port="80", _handler=handler_instance,
_io_service=my_io_service, _thread_pool=pool,
_reuse_address=true);
instance.run();
The following definitions assume that a properly constructed http_server instance has been constructed in the following manner:
handler_type handler;
http_server server("127.0.0.1", "8000", handler);
boost::thread t1(boost::bind(&http_server::run, &server));
boost::thread t2(boost::bind(&http_server::run, &server));
server.run();
t1.join();
t2.join();
The response object has its own public member functions which can be very helpful in certain simple situations.
enum status_type {
ok = 200,
created = 201,
accepted = 202,
no_content = 204,
multiple_choices = 300,
moved_permanently = 301,
moved_temporarily = 302,
not_modified = 304,
bad_request = 400,
unauthorized = 401,
forbidden = 403,
not_found = 404,
not_supported = 405,
not_acceptable = 406,
internal_server_error = 500,
not_implemented = 501,
bad_gateway = 502,
service_unavailable = 503
};
The response object also has the following publicly accessible member values which can be directly manipulated by the handler.
Member Name | Type | Description |
---|---|---|
status | status_type | The HTTP status of the response. |
headers | vector<header> | Vector of headers. [1] |
content | string_type [2] | The contents of the response. |
[1] | A header is a struct of type response_header<http::tags::http_server>. An instance always has the members name and value both of which are of type string_type. |
[2] | string_type is boost::network::string<http::tags::http_server>::type. |
The asynchronous server implementation is significantly different to the synchronous server implementation in three ways:
- The Handler instance is invoked asynchronously. This means the I/O thread used to handle network-related events are free to handle only the I/O related events. This enables the server to scale better as to the number of concurrent connections it can handle.
- The Handler is able to schedule asynchronous actions on the thread pool associated with the server. This allows handlers to perform multiple asynchronous computations that later on perform writes to the connection.
- The Handler is able to control the (asynchronous) writes to and reads from the HTTP connection. Because the connection is available to the Handler, that means it can write out chunks of data at a time or stream data through the connection continuously.
The asynchronous server is meant to allow for better scalability in terms of the number of concurrent connections and for performing asynchronous actions within the handlers. If your applacation does not need to write out information asynchronously or perform potentially long computations, then the synchronous server gives a generally better performance profile than the asynchronous server.
The asynchronous server implementation is available from a single user-facing template named async_server. This template takes in a single template parameter which is the type of the Handler to be called once a request has been parsed from a connection.
An instance of Handler is taken as a reference to the constructor similar to the synchronous server implementation.
Warning
The asynchronous server implementation, like the synchronous server implementation, does not perform any synchronization on the calls to the Handler invocation. This means if your handler contains or maintains internal state, you are responsible for implementing your own synchronization on accesses to the internal state of the Handler.
The general pattern for using the async_server template is shown below:
struct handler;
typedef boost::network::http::async_server<handler> http_server;
struct handler {
void operator()(
http_server::request const & req,
http_server::connection_ptr connection
) {
// handle the request here, and use the connection to
// either read more data or write data out to the client
}
};
The following sections assume that the following file has been included:
#include <boost/network/include/http/server.hpp>
#include <boost/network/utils/thread_pool.hpp>
And that the following typedef’s have been put in place:
struct handler_type;
typedef boost::network::http::server<handler_type> http_server;
struct handler_type {
void operator()(
http_server::request const & request,
http_server::connection_ptr connection
) {
// do something here
}
};
Note
The boost::network::utils::thread_pool has a single constructor parameter which is the number of threads to run the thread pool.
The following definitions assume that a properly constructed http_server instance has been constructed in the following manner:
handler_type handler;
boost::network::utils::thread_pool thread_pool(2);
http_server server("127.0.0.1", "8000", handler, thread_pool);
boost::thread t1(boost::bind(&http_server::run, &server));
boost::thread t2(boost::bind(&http_server::run, &server));
server.run();
t1.join();
t2.join();
The connection object has its own public member functions which will be the primary means for reading from and writing to the connection.
The connection object exposes a function write that can be given a parameter that adheres to the Boost.Range Single Pass Range Concept. The write function, although it looks synchronous, starts of a series of asynchronous writes to the connection as soon as the range is serialized to appropriately sized buffers.
To use this in your handler, it would look something like this:
connection->write("Hello, world!");
std::string sample = "I have a string!";
connection->write(sample);
The connection object has a function read which can be used to read more information from the connection. This read function takes in a callback that can be assigned to a Boost.Function with the signature void(input_range,error_code,size_t,connection_ptr). The following list shows what the types actually mean:
- input_range – boost::iterator_range<char const *> : The range that denotes the data read from the connection.
- error_code – boost::system::error_code : The error code if there were any errors encountered from the read.
- size_t – std::size_t : The number of bytes transferred.
- connection_ptr – http_server::connection_ptr : A handle to the current connection, so that it is kept alive at the time of the read callback invocation.
This interface is useful when doing reads of uploaded data that can be potentially large and may not fit in memory. The read handler is then responsible for dealing with the chunks of data available from the connection.
enum status_t {
ok = 200
, created = 201
, accepted = 202
, no_content = 204
, multiple_choices = 300
, moved_permanently = 301
, moved_temporarily = 302
, not_modified = 304
, bad_request = 400
, unauthorized = 401
, forbidden = 403
, not_found = 404
, not_supported = 405
, not_acceptable = 406
, internal_server_error = 500
, not_implemented = 501
, bad_gateway = 502
, service_unavailable = 503
};
Note
You may set and re-set the status several times as long as you have not set the headers or sent data through the connection. If you do this after data has already been set, the function will throw an instance of std::logic_error.
The set_headers function takes a Single Pass Range of boost::network::http::response_header<http::tags::http_async_server> instances and linearizes them to a buffer with at most BOOST_NETWORK_HTTP_SERVER_CONNECTION_HEADER_BUFFER_MAX_SIZE and immediately schedules an asynchronous write once that is done.
The function throws an instance of std::logic_error if you try to set the headers for a connection more than once.