Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

This library can be used to create applications that need to expose services through HTTP (e.g. embeddable ReST services).

Warning
This is not an official Boost C++ library. It wasn’t reviewed and can’t be downloaded from www.boost.org. This library will be reviewed eventually.

Conceptually Boost.Http is a Boost.Asio socket for HTTP, which means can leverage Boost.Asio features such as SSL and coroutines, and it integrates seemlessly with other Boost.Asio sockets.

Among its features:

  • Support for modern HTTP features.

    • Pipelining and persistent connection, where the same connection can be used to serve HTTP multiple requests.

    • Chunking, where the HTTP body can be split into smaller pieces that are sent one by one. This is useful for building event listeners.

    • Upgrading, where the HTTP protocol can be changed into another protocol such as WebSockets.

    • Security, where SSL is used to encrypt the traffic.

    • 100-continue status.

  • Lean API that unifies HTTP messages and HTTP chunking.

    • You can handle the request as soon as the metadata is available (request line plus header section).

    • There is only one API to acquire the request body. This API allows progressive downloading.

    • The response body can be be generated wholesale for normal HTTP responses, or be split into smaller pieces using HTTP chunking.

  • Extensible asynchronous design inherited from ASIO.

    • Interaction via futures, coroutines, or callbacks.

    • Various HTTP server architectures are possible, whether multi-threaded servers (one thread per request, either with or without a thread-pool), one coroutine per request, or completely single-threaded.

    • Seamless integration with other non-HTTP ASIO services.

Boost.Http also provides the best HTTP parsing abstractions:

  • Simple.

  • Portable (C++03 and very few other dependencies).

  • Just like Ryan Dahl’s HTTP parser, this parser does not make any syscalls or allocations. It also does not buffer data.

  • You can mutate the passed buffer [1].

  • It doesn’t steal control flow from your application [2]. Great for HTTP pipelining.

  • Matching and decoding tokens as separate steps [3].

Boost.Http also provides a couple of special-purpose server classes:

  • Lightweight standalone server.

    • The underlying socket type is customizable, so you can also use a queue_socket [4][5].

  • Flexible static file server with support for conditional requests, partial download and ETag recognition.

    • You can use the default file resolver or provide one yourself.

    • You can adapt the default resolver.

    • You can add a last hook to customize the response message before the file is served.

    • Stateless abstractions, relying on OS capabilities to provide cache.

Boost.Http has been designed for multiple backends, such HTTP/2, and FastGCI, which can be added in the future. You can choose between compile-time and runtime polymorphism. Adapters are provided.

1. Using

1.1. Requirements

This library is written in C++11 and requires a C++11 supporting compiler.

This library tries to reuse standard library components when possible, but this is not always the case. std::error_code, for instance, cannot be transparently replaced by boost::system::error_code within ASIO-using code.

The full list of dependencies is:

  • CMake 3.1.0 or newer [6]. You can skip this requirement if you don’t intend to run the tests.

  • Boost 1.66 or newer.

    • algorithm, container, string_view, ASIO, date_time and system [7].

    • unit_test_framework [8].

    • context and coroutine [9].

    • filesystem [10].

  • asciidoctor, asciidoctor-diagram, PlantUML and syntrax [11]. You’ll also need pandoc if you want to generate the ePUB output.

2. Tutorial

2.1. Message framework

In this tutorial, you’ll have a small and fast introduction to HTTP (if you’re already familiar with HTTP, you can skip the associated section and it’s more likely you’ll deeply understand Boost.Http faster). You’ll also learn incrementally how to build a proper handling of HTTP requests using Boost.Http.

A second step would be how to organize your application and make an useful application, but this second step is left for the user to tackle alone.

Warning
This tutorial assumes you’re already familiar with ASIO!
Note
This tutorial will make use of coroutines, to increase readability like nothing else could do. You might be interested in the use of alternative asynchronous models to achieve lower memory usage or something else. When C++ adds proper support for coroutines, this will not only be the most readable way, but also the most performant.

2.1.1. HTTP

HTTP is a protocol that shines in extensibility. Its 1.1 version has been used unchanged since 1997 and has been able to power very creative applications to this date. An HTTP/2.0 standard has been released, but most of the differencies are related to efficient connection management and the only feature that can affect higher-level layers of an application making use of HTTP is the HTTP push, which is used to speculatively send data to a client that the server anticipates the client will need.

HTTP also provides means to upgrade a protocol for a running connection and the WebSocket protocol is gaining importance where HTTP is used the most, in the web.

HTTP is a protocol that is oriented to exchange of request and reply messages. Each request is independent, hence the stateless nature of the protocol. Each request has an associated verb and path, used to tell what do to who. Every HTTP message has a body (a payload of binary data) and associated metadata (i.e. a multimap<string, string>). The HTTP response also has a status code associated which is used to indicate success of the requested action.

The metadata carried in every HTTP message is named as HTTP headers. The HTTP headers carry information on how to handle the data payload, with info such as the mime type, language and more. HTTP request messages can contain a token which is used to associate multiple different requests with the same client, so the user doesn’t need to type username and password for every page request.

GET is the most common HTTP verb and it has the simple semantic to retrieve the resource.

If you just want to expose a bunch of files from the local filesystem, you may be interested in async_response_transmit_dir, which will take several responsibilities from you (e.g. partial download and a few basic mechanisms to avoid corrupt files).

The usual setup for an HTTP application is to parse the request URL and choose an appropriate handler based on the path component. Optional middleware handlers can do some ACL based on authentication, resource usage and so on. An auxiliar database is almost always used.

2.1.2. Accepting new connections

Remember, the journey of a thousand miles begins with the first step.

First, we’re going to write the boilerplate code Asio require us to write if we want use stackful coroutines to handle a non predetermined number of concurrent connections.

#include <iostream>

#include <boost/asio/spawn.hpp>
#include <boost/http/buffered_socket.hpp>

using namespace std;
using namespace boost;

class connection: public std::enable_shared_from_this<connection>
{
public:
    void operator()(asio::yield_context yield)
    {
        auto self = shared_from_this();
        try {
            cout << "--\n[" << self->counter << "] Socket ready" << endl;
            // >>> OUR CODE GOES HERE <<<
        } catch (system::system_error &e) {
            if (e.code() != system::error_code{asio::error::eof}) {
                cerr << '[' << self->counter << "] Aborting on exception: "
                     << e.what() << endl;
                std::exit(1);
            }

            cout << '[' << self->counter << "] Error: "
                 << e.what() << endl;
        } catch (std::exception &e) {
            cerr << '[' << self->counter << "] Aborting on exception: "
                 << e.what() << endl;
            std::exit(1);
        }
    }

    asio::ip::tcp::socket &tcp_layer()
    {
        return socket.next_layer();
    }

    static std::shared_ptr<connection> make_connection(asio::io_context &ios,
                                                       int counter)
    {
        return std::shared_ptr<connection>{new connection{ios, counter}};
    }

private:
    connection(asio::io_context &ios, int counter)
        : socket(ios)
        , counter(counter)
    {}

    http::buffered_socket socket;
    int counter;
};

int main()
{
    asio::io_context ios;
    asio::ip::tcp::acceptor acceptor(ios,
                                     asio::ip::tcp
                                     ::endpoint(asio::ip::tcp::v6(), 8080));

    auto do_accept = [&acceptor,&ios](asio::yield_context yield) {
        int counter = 0;
        for ( ; true ; ++counter ) {
            try {
                auto connection
                    = connection::make_connection(ios, counter);
                cout << "About to accept a new connection" << endl;
                acceptor.async_accept(connection->tcp_layer(), yield);

                auto handle_connection
                    = [connection](asio::yield_context yield) mutable {
                    (*connection)(yield);
                };
                spawn(acceptor.get_executor(), handle_connection);
            } catch (std::exception &e) {
                cerr << "Aborting on exception: " << e.what() << endl;
                std::exit(1);
            }
        }
    };

    cout << "About to schedule new connections acceptance" << endl;
    spawn(ios, do_accept);

    cout << "About to run the I/O scheduler/executor" << endl;
    ios.run();

    return 0;
}

Summarizing, we make use of shared_ptr to alloc object and ensure it’ll stay alive as long as there is some reference to it and we spawn an acceptor algorithm who will create a shared_ptr for every connection.

asio::ip::tcp::socket &connection::tcp_layer()
{
    return socket.next_layer();
}

The basic_buffered_socket<T>::next_layer() member-function will return the internal T object. The buffered_socket typedef uses asio::ip::tcp::socket as T.

spawn(acceptor.get_executor(), handle_connection);

We spawn a new handler for every connection, so we’ll be able to handle them all concurrently.

auto self = shared_from_this();

We must create a reference to the shared_ptr before calling any asynchronous function to ensure the object will be live as long as the coroutine.

2.1.3. Receiving requests

You can find the whole code at the end of the secion. For now, we build upon the code from the previous code (just replace the “our code goes” here comment with the code we’ll build in this section.

while (self->socket.is_open()) {

So, the first thing you should do is loop while the socket is open, so the whole pipelining of requests will be handled. If the connection closes ungracefully, an error code will be generated (converted to exceptions by the coroutine completion token) during one of the operations. If the connection closes gracefully, the loop will eventually stop by is_open() returning false.

self->socket.async_read_request(self->request, yield);

So, the first part to actually handle is to ask for the request message. You’ll get the full method and path by the time the completion handler is called (in the case of coroutines, this means the next line of code). You must not touch any of these variables while the operation is in progress (hard to get it wrong if you’re using coroutines). self→request→headers() will also be filled and whatever part of the body that has already been received.

while (self->socket.read_state() != http::read_state::empty) {
    // ...
    switch (self->socket.read_state()) {
    case http::read_state::message_ready:
        // ...
        self->socket.async_read_some(self->request, yield);
        break;
    case http::read_state::body_ready:
        // ...
        self->socket.async_read_trailers(self->request, yield);
        break;

You can use self→socket.read_state() to know which part of the request is still missing and ask for the rest.

But there is a little gotcha. You should send a 100-continue to ask for the rest of the body if required. This feature appeared first in HTTP/1.1 as a mean to better use network traffic, by allowing you to reject requests sooner.

if (http::request_continue_required(self->request)) {
    // ...
    self->socket.async_write_response_continue(yield);
}

This is how we check if we should send 100-continue. It must be done after async_read_request and before async_read_some.

By this time, we have already fully received the request and we can do something with it. Pretty easy, see?

http::response reply;

The response message we’re about to send.

std::string body{"Hello World from \""};
body += self->request.target();
body += "\"\n";
std::copy(body.begin(), body.end(),
          std::back_inserter(reply.body()));

We feed some bytes to the body.

reply.status_code() = 200;
reply.reason_phrase() = "OK";
self->socket.async_write_response(reply, yield);

And then we send the response. Our application is complete.

There is a gotcha here. If you use pure asio::ip::tcp::socket, you’re subject to Asio composed operations restrictions and you should not schedule any operation while the previous one is in progress. You can wrap asio::ip::tcp::socket into some queuing socket [12] to work around this bug and Boost.Http will give you the required customization points to allow you to use it. We don’t worry about this problem here, because with coroutines we’re done.

if (we_are_halting())
    reply.headers().emplace("connection", "close");

If we’re going to halt the server and want to gracefully close current connections, this code can be used to close the HTTP pipeline after the current response end. You should pay attention to safe and idempotent methods if you want to learn more about HTTP pipelining and HTTP in general.

self->request.body().clear(); // free unused resources

Given we’re consuming the body, it’s a good idea to free unused resources. We don’t use C++11’s shrink_to_fit because it could trigger another reallocation in the next piece of body received. The idea is to reuse a small allocated buffer instead. You could also create your own message type to discard feeded bytes.

acceptor.async_accept(connection->tcp_layer(), yield);

HTTP doesn’t require you to handle protocol negotiation separately from the remaining protocol or any super special handshaking flow. Therefore, we use a “naked” acceptor to fuel an usable HTTP socket. Other HTTP backends may have different usage.

And, like I promised, here is the complete code (with a lot of print statements and a few lines to demonstrate more usage):

#include <iostream>

#include <boost/asio/spawn.hpp>
#include <boost/http/buffered_socket.hpp>
#include <boost/http/algorithm/query.hpp>
#include <boost/http/request.hpp>
#include <boost/http/response.hpp>

using namespace std;
using namespace boost;

bool we_are_halting()
{
    return false;
}

class connection: public std::enable_shared_from_this<connection>
{
public:
    void operator()(asio::yield_context yield)
    {
        auto self = shared_from_this();
        try {
            cout << "[" << self->counter << "] Socket ready" << endl;
            while (self->socket.is_open()) {
                cout << "--\n[" << self->counter
                     << "] About to receive a new message" << endl;
                self->socket.async_read_request(self->request, yield);
                self->request.body().clear(); // free unused resources

                if (http::request_continue_required(self->request)) {
                    cout << '[' << self->counter
                         << "] Continue required. About to send"
                        " \"100-continue\""
                         << std::endl;
                    self->socket.async_write_response_continue(yield);
                }

                while (self->socket.read_state() != http::read_state::empty) {
                    cout << '[' << self->counter
                         << "] Message not fully received" << endl;
                    switch (self->socket.read_state()) {
                    case http::read_state::message_ready:
                        cout << '[' << self->counter
                             << "] About to receive some body" << endl;
                        self->socket.async_read_some(self->request, yield);
                        self->request.body().clear(); // free unused resources
                        break;
                    case http::read_state::body_ready:
                        cout << '[' << self->counter
                             << "] About to receive trailers" << endl;
                        self->socket.async_read_trailers(self->request, yield);
                        self->request.body().clear(); // free unused resources
                        break;
                    default:;
                    }
                }

                cout << '[' << self->counter << "] Message received. State = "
                     << int(self->socket.read_state()) << endl;
                cout << '[' << self->counter << "] Method: "
                     << self->request.method() << endl;
                cout << '[' << self->counter << "] Path: "
                     << self->request.target() << endl;
                {
                    auto host = self->request.headers().find("host");
                    if (host != self->request.headers().end())
                        cout << '[' << self->counter << "] Host header: "
                             << host->second << endl;
                }

                std::cout << '[' << self->counter << "] Write state = "
                          << int(self->socket.write_state()) << std::endl;

                cout << '[' << self->counter << "] About to send a reply"
                     << endl;

                http::response reply;

                if (we_are_halting())
                    reply.headers().emplace("connection", "close");

                std::string body{"Hello World from \""};
                body += self->request.target();
                body += "\"\n";
                std::copy(body.begin(), body.end(),
                          std::back_inserter(reply.body()));

                reply.status_code() = 200;
                reply.reason_phrase() = "OK";
                self->socket.async_write_response(reply, yield);
            }
        } catch (system::system_error &e) {
            if (e.code() != system::error_code{asio::error::eof}) {
                cerr << '[' << self->counter << "] Aborting on exception: "
                     << e.what() << endl;
                std::exit(1);
            }

            cout << '[' << self->counter << "] Error: "
                 << e.what() << endl;
        } catch (std::exception &e) {
            cerr << '[' << self->counter << "] Aborting on exception: "
                 << e.what() << endl;
            std::exit(1);
        }
    }

    asio::ip::tcp::socket &tcp_layer()
    {
        return socket.next_layer();
    }

    static std::shared_ptr<connection> make_connection(asio::io_context &ios,
                                                       int counter)
    {
        return std::shared_ptr<connection>{new connection{ios, counter}};
    }

private:
    connection(asio::io_context &ios, int counter)
        : socket(ios)
        , counter(counter)
    {}

    http::buffered_socket socket;
    int counter;

    http::request request;
};

int main()
{
    asio::io_context ios;
    asio::ip::tcp::acceptor acceptor(ios,
                                     asio::ip::tcp
                                     ::endpoint(asio::ip::tcp::v6(), 8080));

    auto do_accept = [&acceptor,&ios](asio::yield_context yield) {
        int counter = 0;
        for ( ; true ; ++counter ) {
            try {
                auto connection
                    = connection::make_connection(ios, counter);
                cout << "About to accept a new connection" << endl;
                acceptor.async_accept(connection->tcp_layer(), yield);

                auto handle_connection
                    = [connection](asio::yield_context yield) mutable {
                    (*connection)(yield);
                };
                spawn(acceptor.get_executor(), handle_connection);
            } catch (std::exception &e) {
                cerr << "Aborting on exception: " << e.what() << endl;
                std::exit(1);
            }
        }
    };

    cout << "About to schedule new connections acceptance" << endl;
    spawn(ios, do_accept);

    cout << "About to run the I/O scheduler/executor" << endl;
    ios.run();

    return 0;
}

2.1.4. Using chunked messages

In the previous example, we used atomic messages to respond the request. But this is limiting when we’re trying to achieve certain kind of tasks, like serving a live video stream. A second option for us is to use chunked messages to respond the request.

Warning
Chunked messages are not always available and you must check if you can use chunked messages for every request with the write_response_native_stream() socket member-function.

If chunked messages are available, you can use the following sequence of actions to respond the request:

  1. async_write_response_metadata once.

  2. async_write zero or more times.

  3. async_write_trailers or async_write_end_of_message, once.

2.1.5. Runtime-based polymorphic abstractions

If you want to create a function that will handle requests originated from different HTTP backends, you have three choices:

  • Rewrite the handler for every HTTP backend.

  • Write generic handlers.

  • Type-erase the HTTP backends.

Boost.Http provide some abstractions to type erase the HTTP backends (playing a similar role to std::function). The starting point to learn about it is the server_socket_adaptor page.

void handler(http::poly_server_socket &socket)
{
    // ...
}

http::server_socket_adaptor<http::buffered_socket>
    socket(acceptor.get_executor().context());
handler(socket);

2.2. Polymorphic handlers (std::function alternative)

Boost.Asio uses handlers with signatures similar to the following to notify about the completion of tasks:

void(boost::system::error_code)

If you intend to wrap the template heavy usage of Boost.Asio behind a stable interface that can be accessed through dynamically loaded plug-ins, you may be tempted to use std::function:

class my_socket
{
public:
    virtual ~my_socket() = default;

    virtual void async_write_some(
        boost::asio::const_buffer buf,
        std::function<void(boost::system::error_code, std::size_t)> handler
    ) = 0;

    virtual void async_read_some(
        std::vector<char> &buf,
        std::function<void(boost::system::error_code, std::size_t)> handler
    ) = 0;
};

void work()
{
    // ...

    auto handle_socket = boost::dll::import<void(std::shared_ptr<my_socket>)>(
        path_to_shared_library, "handle_socket"
    );

    // ...

    for (;;) {
        // ...

        handle_socket(sock);
    }
}

However, this approach is wrong. std::function does type erasure of functors, but it won’t do type erasure of associated allocators and associated executors which are a core part of Boost.Asio. This extra information will be discarded.

If there is an associated executor with a completion handler, this associated executor should be used and propagated by every intermediate handler in the chain.

If I try the following, the propagation will stop at the my_socket boundary as std::function discards the associated executor:

void work()
{
    // ...

    auto handler = boost::asio::bind_executor(
        boost::asio::io_context::strand{ctx},
        [](boost::system::error_code, std::size_t) {}
    );

    boost::asio::async_write(*sock, handler);
}

One way to verify this assertion is with a simple test case. Create two execution contexts, ctx1 and ctx2. Post a handler to ctx1 and then call ctx1.run(). You should expect the handler to be called. Now modify handler to be wrapped and associated with an executor from ctx2. If only this change is done, you should expect the program to exit without ever calling the handler. The following code illustrates this situation:

#include <iostream>

#include <boost/asio/bind_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>

int main()
{
    boost::asio::io_context ctx1;
    boost::asio::io_context ctx2;

    auto handler = boost::asio::bind_executor(
        ctx2,
        []() {
            // won't happen
            std::cout << "handler called" << std::endl;
        }
    );

    boost::asio::post(ctx1, handler);

    ctx1.run();

    return 0;
}

If you also change the code to call ctx2.run() after ctx1.run(), you should expect the handler to be called. But this is just an exercise for the reader. We’re sticking with the previous code of no handler called as the desired behaviour. To be more specific, we’re interested in making sure that we can observe that execution scheduling is done through a specific executor (i.e. associated executors are respected). And, for this executor in particular, the observed output is of no handler called.

If you wrap our handler within a std::function object, the associated executors will be discarded and you should expect the handler to be called again:

#include <functional>
#include <iostream>

#include <boost/asio/bind_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>

int main()
{
    boost::asio::io_context ctx1;
    boost::asio::io_context ctx2;

    auto handler = boost::asio::bind_executor(
        ctx2,
        []() {
            std::cout << "handler called" << std::endl;
        }
    );

    boost::asio::post(ctx1, std::function<void()>{handler});

    ctx1.run();

    return 0;
}

That’s why we provide boost::http::asio::experimental::poly_handler as an alternative to std::function:

#include <iostream>

#include <boost/http/asio/experimental/poly_handler.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>

int main()
{
    boost::asio::io_context ctx1;
    boost::asio::io_context ctx2;

    auto handler = boost::asio::bind_executor(
        ctx2,
        []() {
            // won't happen
            std::cout << "handler called" << std::endl;
        }
    );

    boost::asio::post(
        ctx1,
        boost::http::asio::experimental::poly_handler<void()>{handler}
    );

    ctx1.run();

    return 0;
}

If you call boost::asio::get_associated_executor on boost::http::asio::experimental::poly_handler, you’ll get a type erased (i.e. boost::asio::executor) executor:

auto phandler
    = boost::http::asio::experimental::poly_handler<void()>{handler};
auto ex = boost::asio::get_associated_executor(phandler);

Using poly_handler, we can update the previous my_socket definition and make the DLL boundary work properly:

class my_socket
{
public:
    virtual ~my_socket() = default;

    virtual void async_write_some(
        boost::asio::const_buffer buf,
        boost::http::asio::experimental::poly_handler<
            void(boost::system::error_code, std::size_t)
        > handler
    ) = 0;

    virtual void async_read_some(
        std::vector<char> &buf,
        boost::http::asio::experimental::poly_handler<
            void(boost::system::error_code, std::size_t)
        > handler
    ) = 0;
};

poly_handler will also preserve the associated allocators as can be verified with the following example:

#include <iostream>

#include <boost/http/asio/experimental/poly_handler.hpp>

struct MyHandler
{
    using allocator_type = std::experimental::pmr::polymorphic_allocator<void>;

    MyHandler(std::experimental::pmr::memory_resource *r)
        : r(r)
    {}

    void operator()() {}

    allocator_type get_allocator() const
    {
        return {r};
    }

    std::experimental::pmr::memory_resource *r;
};

int main()
{
    auto r1 = std::experimental::pmr::new_delete_resource();
    auto r2 = std::experimental::pmr::null_memory_resource();
    boost::http::asio::experimental::poly_handler<void()>
        handler{MyHandler{r1}};

    std::experimental::pmr::polymorphic_allocator<void> fallback_a = r2;
    auto a = boost::asio::get_associated_allocator(handler, fallback_a);

    std::cout << "r1 = " << (void*)(r1) << std::endl;
    std::cout << "r2 = " << (void*)(r2) << std::endl;
    std::cout << "a = " << (void*)(a.resource()) << std::endl;

    return 0;
}

In the example, we associated the MyHandler object with r1 just to wrap it under poly_handler. Then we asked what was the associated allocator a and passed r2 as the fallback in case there was no such a. The expected output would be for r1 to be the same as a. And that’s just what you’ll see if you run the example. If you want the example to fail (as in discarding the associated allocator and showing a different output), just replace poly_handler with std::function.

Important
If you try to use an associated allocator whose type doesn’t match std::experimental::pmr::polymorphic_allocator<T>, poly_handler will fail to wrap it and it won’t compile. This behaviour is on purpose as we should not silently discard associated properties. The reason why I don’t support other types of allocators for associated allocators (fallback allocators passed to boost::asio::get_associated_allocator can still be of different types) is because they make little sense in a generic context such as ASIO’s and implementation complexity.

2.3. Parsing (beginner)

In this tutorial, you’ll learn how to use this library to parse HTTP streams easily.

Note
We assume the reader has basic understanding of C++ and Boost.Asio.

We start with the code that should resemble the structure of the program you’re about to code. And this structure is as follows:

#include <boost/http/reader/request.hpp>
#include <string>
#include <map>

namespace http = boost::http;
namespace asio = boost::asio;

struct my_socket_consumer
{
private:
    http::reader::request request_reader;
    std::string buffer;

    std::string last_header;

public:
    std::string method;
    std::string request_target;
    int version;

    std::multimap<std::string, std::string> headers;

    void on_socket_callback(asio::buffer data)
    {
        using namespace http::token;
        using token::code;

        buffer.push_back(data);
        request_reader.set_buffer(buffer);

        while (request_reader.code() != code::end_of_message) {
            switch (request_reader.code()) {
            case code::skip:
                // do nothing
                break;
            case code::method:
                method = request_reader.value<token::method>();
                break;
            case code::request_target:
                request_target = request_reader.value<token::request_target>();
                break;
            case code::version:
                version = request_reader.value<token::version>();
                break;
            case code::field_name:
            case code::trailer_name:
                last_header = request_reader.value<token::field_name>();
            }
            request_reader.next();
        }
        request_reader.next();

        ready();
    }

protected:
    virtual void ready() = 0;
};

You’re building a piece of code that consumes HTTP from somewhere — the in — and spits it out in the form of C++ structured data to somewhere else — the out.

The in of your program is connected to the above piece of code through the on_socket_callback member function. The out of your program is connected to the previous piece of code through the ready overrideable member-function.

By now I shouldn’t be worried about your understanding of how you’ll connect the network I/O with the in of the program. The connection point is obvious by now. However, I’ll briefly explain the out connection point and then we can proceed to delve into the inout-inbetween (Danas) part of the program.

Once the ready member function is called, the data for your request will be available in the method, request_target and the other public variables. From now on, I’ll focus my attention to the sole implementation of my_socket_consumer::on_socket_callback.

The awaited prize
void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    //http::reader::request request_reader;
    //std::string buffer;
    //std::string last_header;

    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    while (request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        case code::skip:
            // do nothing
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
        }
        request_reader.next();
    }
    request_reader.next();

    ready();
}

Try to keep in mind the three variables that will really orchestrate the flow: request_reader, buffer and last_header.

The whole work is about managing the buffer and managing the tokens.

The token access is very easy. As the parser is incremental, there is only one token at a time. I don’t need to explain Boost.Http control-flow because the control flow will be coded by you (a library, not a framework). You only have to use code() to check the current token and value<T>() to extract its value. Use next() to advance a token.

Warning

There is only one caveat. The parser doesn’t buffer data and will decode the token into a value (the value<T>() member function) directly from the buffer data.

This means you cannot extract the current value once you drop current buffer data. As a nice side effect, you spare CPU time for the tokens you do not need to decode (match’n’decoding as separate steps).

The parser doesn’t buffer data, which means when we use the set_buffer member function, request_reader only maintains a view to the passed buffer, which we’ll refer to as the virtual buffer from now on.

In the virtual buffer, there is head/current and remaining/tail. request_reader doesn’t store a pointer/address/index to the real buffer. Once a token is consumed, his bytes (head) are discarded from the virtual buffer. When you mutate the real buffer, the virtual buffer is invalidated and you must inform the parser using set_buffer. However, the bytes discarded from the virtual buffer shouldn’t appear again. You must keep track of the number of discarded bytes to prepare the buffer to the next call to set_buffer. The previous code doesn’t handle that.

The new tool that you should be presented now is token_size(). token_size() will return the size in bytes of current/head.

Warning
There is no guarantee token_size() returns the same size as returned by string_length(request_reader.value<T>()). You need to use token_size() to compute the number of discarded bytes.
void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0; //< NEW

    while (request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        case code::skip:
            // do nothing
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
        }

        nparsed += request_reader.token_size(); //< NEW
        request_reader.next();
    }
    nparsed += request_reader.token_size(); //< NEW
    request_reader.next();
    buffer.erase(0, nparsed); //< NEW

    ready();
}

nparsed was easy. However, the while(request_reader.code() != code::end_of_message) doesn’t seem right. It’s very error-prone to assume the full HTTP message will be ready in a single call to on_socket_callback. Error handling must be introduced in the code.

void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0;

    while (request_reader.code() != code::error_insufficient_data //< NEW
           && request_reader.code() != code::end_of_message) { //< NEW
        switch (request_reader.code()) {
        case code::skip:
            // do nothing
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
        }

        nparsed += request_reader.token_size();
        request_reader.next();
    }
    nparsed += request_reader.token_size();
    request_reader.next();
    buffer.erase(0, nparsed);

    if (request_reader.code() == code::error_insufficient_data) //< NEW
        return; //< NEW

    ready();
}
Note
Don’t worry about token_size(code::error_insufficient_data) being added to nparsed. This (error) "token" is defined to be 0-size (it fits perfectly with the other rules).

Just because it’s easy and we’re already at it, let’s handle the other errors as well:

void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0;

    while (request_reader.code() != code::error_insufficient_data
           && request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        case code::error_set_method: //< NEW
        case code::error_use_another_connection: //< NEW
            // Can only happen in response parsing code.
            assert(false); //< NEW
        case code::error_invalid_data: //< NEW
        case code::error_no_host: //< NEW
        case code::error_invalid_content_length: //< NEW
        case code::error_content_length_overflow: //< NEW
        case code::error_invalid_transfer_encoding: //< NEW
        case code::error_chunk_size_overflow: //< NEW
            throw "invalid HTTP data"; //< NEW
        case code::skip:
            // do nothing
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
        }

        nparsed += request_reader.token_size();
        request_reader.next();
    }
    nparsed += request_reader.token_size();
    request_reader.next();
    buffer.erase(0, nparsed);

    if (request_reader.code() == code::error_insufficient_data)
        return;

    ready();
}

And buffer management is complete. However, the code only demonstrated how to extract simple tokens. Field name and field value are simple tokens, but they are usually tied together into a complex structure.

void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0;

    while (request_reader.code() != code::error_insufficient_data
           && request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        // ...
        case code::skip:
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
            break;
        case code::field_value: //< NEW
        case code::trailer_value: //< NEW
            // NEW
            headers.emplace(last_header,
                            request_reader.value<token::field_value>());
        }

        nparsed += request_reader.token_size();
        request_reader.next();
    }
    nparsed += request_reader.token_size();
    request_reader.next();
    buffer.erase(0, nparsed);

    if (request_reader.code() == code::error_insufficient_data)
        return;

    ready();
}

last_header did the trick. Easy, but maybe we want to separate headers and trailers (the HTTP headers that are sent after the message body). This task can be accomplished by the use of structural tokens.

void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    // NEW:
    // We have to declare `bool my_socket_consumer::use_trailers = false` and
    // `std::multimap<std::string, std::string> my_socket_consumer::trailers`.

    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0;

    while (request_reader.code() != code::error_insufficient_data
           && request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        // ...
        case code::skip:
            break;
        case code::method:
            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
            break;
        case code::field_value:
        case code::trailer_value:
            // NEW
            (use_trailers ? trailers : headers)
                .emplace(last_header,
                         request_reader.value<token::field_value>());
            break;
        case code::end_of_headers: //< NEW
            use_trailers = true; //< NEW
        }

        nparsed += request_reader.token_size();
        request_reader.next();
    }
    nparsed += request_reader.token_size();
    request_reader.next();
    buffer.erase(0, nparsed);

    if (request_reader.code() == code::error_insufficient_data)
        return;

    ready();
}
Note

Maybe you had a gut feeling and thought that the previous code was too strange. If trailer_name is a separate token, why don’t we use request_reader.value<token::trailer_name>() (same to trailer_value) and go away with structural tokens?

Yes, I unnecessarily complicated the code here to introduce you the concept of structural tokens. They are very important and usually you’ll end up using them. Maybe this tutorial needs some revamping after the library evolved a few times.

Also notice that here you can use either request_reader.value<token::field_name>() or request_reader.value<token::trailer_name>() to extract this token value. It is as if trailer_name is “implicitly convertible” to field_name, so to speak. This feature makes the life of users who don’t need to differentiate headers and trailers much easier (with no drawback to the users who do need to differentiate them).

Some of the structural tokens' properties are:

  • No value<T>() associated. value<T>() extraction is a property exclusive of the data tokens.

  • It might be 0-sized.

  • They are always emitted (e.g. code::end_of_body will be emitted before code::end_of_message even if no code::body_chunk is present).

We were using the code::end_of_message structural token since the initial version of the code, so they aren’t completely alien. However, we were ignoring one very important HTTP parsing feature for this time. It’s the last missing bit before your understanding to use this library is complete. Our current code lacks the ability to handle HTTP pipelining.

HTTP pipelining is the feature that allows HTTP clients to send HTTP requests “in batch”. In other words, they may send several requests at once over the same connection before the server creates a response to them. If the previous code faces this situation, it’ll stop parsing on the first request and possibly wait forever until the on_socket_callback is called again with more data (yeap, networking code can be hard with so many little details).

void my_socket_consumer::on_socket_callback(asio::buffer data)
{
    using namespace http::token;
    using token::code;

    buffer.push_back(data);
    request_reader.set_buffer(buffer);

    std::size_t nparsed = 0;

    while (request_reader.code() != code::error_insufficient_data
           && request_reader.code() != code::end_of_message) {
        switch (request_reader.code()) {
        // ...
        case code::skip:
            break;
        case code::method:
            use_trailers = false; //< NEW
            headers.clear(); //< NEW
            trailers.clear(); //< NEW

            method = request_reader.value<token::method>();
            break;
        case code::request_target:
            request_target = request_reader.value<token::request_target>();
            break;
        case code::version:
            version = request_reader.value<token::version>();
            break;
        case code::field_name:
        case code::trailer_name:
            last_header = request_reader.value<token::field_name>();
            break;
        case code::field_value:
        case code::trailer_value:
            (use_trailers ? trailers : headers)
                .emplace(last_header,
                         request_reader.value<token::field_value>());
            break;
        case code::end_of_headers:
            use_trailers = true;
        }

        nparsed += request_reader.token_size();
        request_reader.next();
    }
    nparsed += request_reader.token_size();
    request_reader.next();
    buffer.erase(0, nparsed);

    if (request_reader.code() == code::error_insufficient_data)
        return;

    ready();

    if (buffer.size() > 0) //< NEW
        on_socket_callback(); //< NEW
}

There are HTTP libraries that could adopt a “synchronous” approach where the user must immediately give a HTTP response once the ready() callback is called so the parsing code can parse the whole buffer until the end and we could just put the ready() call into the code::end_of_message case.

There are HTTP libraries that follow ASIO active style and we expect the user to call something like async_read_request before it can read the next request. In this case, the solution for HTTP pipelining would be different.

There are libraries that don’t follow ASIO style, but don’t force the user to send HTTP responses immediately on the ready() callback. In such cases, synchronization/coordination of the response generation by the user and parse resuming by the library is necessary.

This point can be rather diverse and the code for this tutorial only shows a rather quick’n’dirty solution. Any different solution to keep the parsing train at full-speed is left as an exercise to the reader.

The interesting point about the code here is to clear the state of the to-be-parsed message before each request-response pair. In the previous code, this was done binding the “method token arrived” event — the first token in a HTTP request — with such state cleanup.

By now, you’re ready to use this library in your projects. You may want to check Boost.Http own usage of the parser or the Tufão library as real-world and complete examples of this parser.

2.4. Parsing (advanced)

In this tutorial, you’ll learn how to use this library to parse HTTP streams easily.

The architecture of the library is broken into two classes of parsers, the content parsers and the structural parsers.

The content parsers handle non-structural elements, terminal tokens, all easy to match and decode. They are stateless. They are mini-parsers for elements easy to parse and by themselves don’t add much value to justify a library (don’t confuse low value with valueless). They live in the boost::http::syntax namespace. They are useful when you want to parse individual HTTP elements like the range header value. We won’t see them in this tutorial.

The structural parsers handle structured data formats (e.g. HTTP). To achieve flexibility and performance requirements, they follow the incremental/pull parser model (a bit like the more traditional Iterator design pattern as described in the Gang-of-Four book, instead of C++ iterators). These parsers live in the boost::http::reader namespace. These are the parsers we will look into now.

In the future, we may add support for HTTP/2.0 stream format, but for now, we are left with two structural parsers:

  • boost::http::reader::request for HTTP/1.0 and HTTP/1.1 request messages.

  • boost::http::reader::response for HTTP/1.0 and HTTP/1.1 response messages.

Each structural parser is prepared to receive a continuous stream of messages (i.e. what NodeJS correctly refer to as keep-alive persistent streams). Because the structure of messages is flexible enough to be non-representable in simple non-allocating C++ structures, we don’t decode the whole stream as a single parsing result as this would force allocation. What we do instead is to feed the user with one token at a time and internally we keep a lightweight non-growing state required to decode further tokens.

We use the same token definition for HTTP requests and HTTP responses. The tokens can be either of status (e.g. error or skip), structural (e.g. boost::http::token::code::end_of_headers) or data (e.g. boost::http::token::code::field_name) categories. Only tokens of the data category have an associated value.

Each token is associated with a slice (possibly 0-sized if error token or a token from the structural category) of the byte stream. The process goes as follow:

  1. Set the buffer used by the reader.

  2. Consume tokens.

    1. Check code() return.

    2. Possibly call value<T>() to extract token value.

    3. Call next().

  3. Remove parsed data from the buffer.

    1. You’ll need to keep state of parsed vs unparsed data by calling token_size().

  4. If the address of the unparsed data changes, the reader is invalidated, so to speak. You can restore its valid state by setting the buffer to null or to the new address of the unparsed data.

Enough with abstract info. Take the following part of an HTTP stream:

GET / HTTP/1.1\r\n
Host: www.example.com\r\n
\r\n

This stream can be broken in the following series of tokens (order preserved):

  • method.

  • request_target.

  • skip.

  • version.

  • field_name.

  • field_value.

  • skip.

  • end_of_headers.

  • end_of_body.

  • end_of_message.

The parser is required to give you a token_size() so you can remove parsed data from the stream. However, the parser is not required to give the same series of tokens for the same stream. The strucutral and data tokens will always be emitted the same. However, the parser may choose to merge some status token (e.g. skip) with a data token (e.g. request_target). Therefore, the following series of tokens would also be possible for the same example given previously:

  • method.

  • skip.

  • request_target.

  • version.

  • field_name.

  • skip.

  • field_value.

  • end_of_headers.

  • end_of_body.

  • end_of_message.

This (non-)guarantee is to give freedom to vary the implementation. It’d be absurd to expect different implementations of this interface generating the same result byte by byte. You may expect different algorithms also in future versions.

Another useful feature of this non-guarantee is to make possible to discard skip tokens in the buffer, but merge them if the stream is received in the buffer at once.

Just imagine documenting the guarantees of the token stream if we were to make it predictable. It’d be insane.

However, there is one guarantee that the reader object must provide. It must not discard bytes of data tokens while the token is incomplete. To illustrate this point, let’s go to an example. Given the current token is request_target, you have the following code.

assert(parser.code() == boost::http::token::code::request_target);
auto value = parser.value<boost::http::token::request_target>();

While we traverse the stream, the parser will only match tokens. We don’t expect the parser to also decode the tokens. The parser will only decode the tokens if necessary to further match the following tokens. And even when the parser decod’em, the intermediary results may be discarded. In other words, match and decode are separate steps and you can spare CPU time when you don’t need to decode certain elements.

The point is that the token value must be extracted directly from the byte stream and the parser is not allowed to buffer data about the stream (or the decoded values, for that matter). The implication of this rule gives a guarantee about the token order and its relationship to the bytem stream.

You can imagine the stream as having two views. The tokens and the byte streams. The token view spans windows over the byte view.

tokens: | method | skip  | request_target | skip          | version
bytes:  |  GET   | <SPC> |     /          | <SPC> HTTP/1. | 1 <CRLF>

The slice of data associated with a data token can grow larger than the equivalent bytes:

tokens: |  method    | request_target | skip  | version
bytes:  |  GET <SPC> |     /          | <SPC> | HTTP/1.1 <CRLF>

But it cannot shrink smaller than its associated bytes:

tokens: | method | skip    | request_target | skip           | version
bytes:  |  GE    | T <SPC> |     /          | <SPC> HTTP/1.1 | <CRLF>

So you have a token interface easy to inspect and you have a lot of freedom to manage the underlying buffer. Let’s see the boost::http::reader::request parser as used in Tufão:

void HttpServerRequest::onReadyRead()
{
    if (priv->timeout)
        priv->timer.start(priv->timeout);

    priv->buffer += priv->socket.readAll();
    priv->parser.set_buffer(asio::buffer(priv->buffer.data(),
                                         priv->buffer.size()));

    Priv::Signals whatEmit(0);
    bool is_upgrade = false;

    while(priv->parser.code() != http::token::code::error_insufficient_data) {
        switch(priv->parser.symbol()) {
        case http::token::symbol::error:
            priv->socket.close();
            return;
        case http::token::symbol::skip:
            break;
        case http::token::symbol::method:
            {
                clearRequest();
                priv->responseOptions = 0;
                auto value = priv->parser.value<http::token::method>();
                QByteArray method(value.data(), value.size());
                priv->method = std::move(method);
            }
            break;
        case http::token::symbol::request_target:
            {
                auto value = priv->parser.value<http::token::request_target>();
                QByteArray url(value.data(), value.size());
                priv->url = std::move(url);
            }
            break;
        case http::token::symbol::version:
            {
                auto value = priv->parser.value<http::token::version>();
                if (value == 0) {
                    priv->httpVersion = HttpVersion::HTTP_1_0;
                    priv->responseOptions |= HttpServerResponse::HTTP_1_0;
                } else {
                    priv->httpVersion = HttpVersion::HTTP_1_1;
                    priv->responseOptions |= HttpServerResponse::HTTP_1_1;
                }
            }
            break;
        case http::token::symbol::status_code:
            qFatal("unreachable");
            break;
        case http::token::symbol::reason_phrase:
            qFatal("unreachable");
            break;
        case http::token::symbol::field_name:
        case http::token::symbol::trailer_name:
            {
                auto value = priv->parser.value<http::token::field_name>();
                priv->lastHeader = QByteArray(value.data(), value.size());
            }
            break;
        case http::token::symbol::field_value:
            {
                auto value = priv->parser.value<http::token::field_value>();
                QByteArray header(value.data(), value.size());
                priv->headers.insert(priv->lastHeader, std::move(header));
                priv->lastHeader.clear();
            }
            break;
        case http::token::symbol::trailer_value:
            {
                auto value = priv->parser.value<http::token::trailer_value>();
                QByteArray header(value.data(), value.size());
                priv->trailers.insert(priv->lastHeader, std::move(header));
                priv->lastHeader.clear();
            }
            break;
        case http::token::symbol::end_of_headers:
            {
                auto it = priv->headers.find("connection");
                bool close_found = false;
                bool keep_alive_found = false;
                for (;it != priv->headers.end();++it) {
                    auto value = boost::string_view(it->data(), it->size());
                    http::header_value_any_of(value, [&](boost::string_view v) {
                        if (iequals(v, "close"))
                            close_found = true;

                        if (iequals(v, "keep-alive"))
                            keep_alive_found = true;

                        if (iequals(v, "upgrade"))
                            is_upgrade = true;

                        return false;
                    });
                    if (close_found)
                        break;
                }
                if (!close_found
                    && (priv->httpVersion == HttpVersion::HTTP_1_1
                        || keep_alive_found)) {
                    priv->responseOptions |= HttpServerResponse::KEEP_ALIVE;
                }
                whatEmit = Priv::READY;
            }
            break;
        case http::token::symbol::body_chunk:
            {
                auto value = priv->parser.value<http::token::body_chunk>();
                priv->body.append(asio::buffer_cast<const char*>(value),
                                  asio::buffer_size(value));
                whatEmit |= Priv::DATA;
            }
            break;
        case http::token::symbol::end_of_body:
            break;
        case http::token::symbol::end_of_message:
            priv->buffer.remove(0, priv->parser.parsed_count());
            priv->parser.set_buffer(asio::buffer(priv->buffer.data(),
                                                 priv->parser.token_size()));
            whatEmit |= Priv::END;
            disconnect(&priv->socket, SIGNAL(readyRead()),
                       this, SLOT(onReadyRead()));
            break;
        }

        priv->parser.next();
    }
    priv->buffer.remove(0, priv->parser.parsed_count());

    if (is_upgrade) {
        disconnect(&priv->socket, SIGNAL(readyRead()),
                   this, SLOT(onReadyRead()));
        disconnect(&priv->socket, SIGNAL(disconnected()),
                   this, SIGNAL(close()));
        disconnect(&priv->timer, SIGNAL(timeout()), this, SLOT(onTimeout()));

        priv->body.swap(priv->buffer);
        emit upgrade();
        return;
    }

    if (whatEmit.testFlag(Priv::READY)) {
        whatEmit &= ~Priv::Signals(Priv::READY);
        this->disconnect(SIGNAL(data()));
        this->disconnect(SIGNAL(end()));
        emit ready();
    }

    if (whatEmit.testFlag(Priv::DATA)) {
        whatEmit &= ~Priv::Signals(Priv::DATA);
        emit data();
    }

    if (whatEmit.testFlag(Priv::END)) {
        whatEmit &= ~Priv::Signals(Priv::END);
        emit end();
        return;
    }
}

Boost.Http higher level message framework’s socket has a buffer of fixed size and cannot have the luxury of appending data every time. Both high level projects have many fundamental differences.

Boost.Http Tufão

Boost.Asio active style.

Qt event loop passive style.

Boost usage allowed.

It uses this header-only parser lib at Tufão build time and Tufão user will never need Boost again.

Message-based framework which allows different backends to be plugged later keeping the same handlers.

Tied to HTTP/1.1 embedded server.

Callbacks and completion tokens. It may read more than asked for, but it’ll use read_state to keep user informed.

Combined with Qt network reactive programming style, it has a strange logic related to event signalling.

Proper HTTP upgrade semantics.

Strange HTTP upgrade semantics thanks to the immaturity of following NodeJS design decisions.

It normalizes all header keys to lower case.

Case insensitive string classes for the C++ counterpart of the HTTP field names structure.

These are the main differences that I wanted to note. You can be sure this parser will fit you and it’ll be easy to use. And more importantly, easy to use right. NodeJS parser demands too much HTTP knowledge on the user behalf. And thanks to the NodeJS parser hard to use API, Tufão only was able to support proper HTTP pipelining once it migrated to Boost.Http parser (although Boost.Http managed to do lots of ninja techs to support it under NodeJS parser).

To sum up the data received handler structure, you need:

  1. Get the buffer right with parser.set_buffer(buf).

  2. Loop to consume — parser.next() — tokens while http::token::code::error_insufficient_data.

    1. Examine token with parser.code().

    2. Maybe handle error.

    3. Extract data with parser.value<T>() if a data token.

  3. Remove parsed data.

There are a lot of different HTTP server/client models you can build on top of this framework and the notification style you’re to use is entirely up to you. Most likely, you’ll want to hook some actions when the always-to-be-triggered delimiters category tokens (e.g. boost::http::token::code::end_of_headers) are reached.

2.5. Parsing HTTP upgrade

Given you already know the basics, parsing HTTP upgrade is trivial. Because the HTTP parser doesn’t take ownership of the buffer and you pretty much know up until which point the stream was parsed as HTTP.

All you gotta do is consume all the HTTP data (i.e. watch for code::end_of_message) and parse the rest of the buffer as the new protocol. Here is the Tufão code to update an HTTP client to WebSocket:

inline bool WebSocketHttpClient::execute(QByteArray &chunk)
{
    if (errored)
        return false;

    parser.set_buffer(asio::buffer(chunk.data(), chunk.size()));

    while(parser.code() != http::token::code::error_insufficient_data) {
        switch(parser.symbol()) {
        case http::token::symbol::error:
            errored = true;
            return false;
        case http::token::symbol::skip:
            break;
        case http::token::symbol::method:
            qFatal("unreachable");
            break;
        case http::token::symbol::request_target:
            qFatal("unreachable");
            break;
        case http::token::symbol::version:
            if (parser.value<http::token::version>() == 0) {
                errored = true;
                return false;
            }

            break;
        case http::token::symbol::status_code:
            status_code = parser.value<http::token::status_code>();
            if (status_code != 101) {
                errored = true;
                return false;
            }

            parser.set_method("GET");
            break;
        case http::token::symbol::reason_phrase:
            break;
        case http::token::symbol::field_name:
            {
                auto value = parser.value<http::token::field_name>();
                lastHeader = QByteArray(value.data(), value.size());
            }
            break;
        case http::token::symbol::field_value:
            {
                auto value = parser.value<http::token::field_value>();
                QByteArray header(value.data(), value.size());
                headers.insert(lastHeader, std::move(header));
                lastHeader.clear();
            }
            break;
        case http::token::symbol::end_of_headers:
            break;
        case http::token::symbol::body_chunk:
            break;
        case http::token::symbol::end_of_body:
            break;
        case http::token::symbol::trailer_name:
            break;
        case http::token::symbol::trailer_value:
            break;
        case http::token::symbol::end_of_message:
            ready = true;
            chunk.remove(0, parser.parsed_count());
            parser.set_buffer(asio::buffer(chunk.data(),
                                           parser.token_size()));
            break;
        }

        parser.next();
    }
    chunk.remove(0, parser.parsed_count());

    if (ready && headers.contains("Upgrade"))
        return true;

    return false;
}

2.6. Implementing Boost.Beast parser interface

In this tutorial, we’ll show you how to implement Boost.Beast parser interface using Boost.Http parser.

Note
No prior experience with Boost.Beast is required.

Boost.Beast parser borrows much of its design from Ryan Dahl’s HTTP parser — the NodeJS parser. This is a design that I do know and used more than once in different projects. However, this is not the only design I know of. I see much of this design as an evolution of the language limitations that you find in the C language.

I’ve previously written a few complaints about the Ryan Dahl’s HTTP parser. Boost.Beast evolves from this design and takes a few different decisions. We’ll see if, and which, limitations the Boost.Beast parser still carries thanks to this inheritance.

2.6.1. Learning how to use the parser

The parser is represented by a basic_parser<isRequest, Derived> class. The parser is callback-based just like Ryan Dahl’s HTTP parser and it uses CRTP to avoid the overhead of virtual function calls.

Note
DESIGN IMPLICATIONS

And here we can notice the first difference between Boost.Beast and Boost.Http parsers.

If you design an algorithm to work on the parser object, this algorithm must be a template (and it carries the same drawbacks of a header-only library):

template<class Derived>
void do_stuff(boost::beast::http::basic_parser<true, Derived> o);

But if you use Boost.Http parser, this requirement vanishes:

void do_stuff(boost::http::reader::request o);

To feed the parser with data, you call basic_parser::put:

template<
    class ConstBufferSequence>
std::size_t
put(
    ConstBufferSequence const& buffers,
    error_code& ec);

The parser will match and decode the tokens in the stream.

Note
DESIGN IMPLICATIONS

Match and decoding are always applied together. What does this mean? Not much, given decoding HTTP/1.1 tokens is cheap and most of the time it reduces to return a string_view of the associated buffer region.

However, the implications of the fundamental model chosen (pull or push) give rise to larger divergences at this point already.

In the Boost.Beast parser, the token is passed to the function callback registered. In the Boost.Http parser, the token is hold by the parser object itself.

It might not seem much difference at the first glance, but consider the problem of composability. If I want to write an algorithm to take a boost::http::reader::request object to read a field_name token and possibly skip it together with the associated field_value (maybe that field isn’t of your interest), you only need to wrap the parser object if you’re using the Boost.Http solution [13]. As for the Boost.Beast parser, you’re left with a long and hackishy inheritance chain that I don’t even want to imagine right now if you were to compose the algorithms.

Just as a concrete illustration of what I meant by the Boost.Http solution:

template<class T /* = http::reader::request */>
void socket_consumer<T>::parse(asio::buffer buf) { /* ... */ }

http::token::code::value my_parser_wrapper::code() const
{
    return code_;
}

void my_parser_wrapper::next()
{
    // Here I should use a case-insensitive comparison function.
    // For simplicity, this step is omitted.

    wrapped_parser.next();
    code_ = wrapped_parser.code();
    if (code_ == code::field_name
        && (wrapped_parser.value<token::field_name>()
            == "connection")) {
        code_ = code::skip;
        skip_next_value = true;
    } else if (code_ == code::field_value
               && skip_next_value) {
        code_ = code::skip;
        skip_next_value = false;
    }
}

How would the solution look like using the Boost.Beast parser? Let’s draft something:

template<class Derived>
class my_parser_wrapper
    : public basic_parser<true, my_parser_wrapper<Derived>>
{
public:
    void
    on_field_impl(field f, string_view name, string_view value, error_code& ec)
    {
        // Here I should use a case-insensitive comparison function.
        // For simplicity, this step is omitted.

        if (name == "connection") {
            return;
        }

        static_cast<Derived&>(*this).on_field_impl(f, name, value, ec);
    }

    void on_header_impl(error_code& ec)
    {
        static_cast<Derived&>(*this).on_header_impl(ec);
    }

    // ...
};

template<template <typename> class parser>
class custom_parser
    : public parser<custom_parser>
{
    // Here we have our original handler.
    // It'd be our `my_socket_consumer` from the Boost.Http example.
};

custom_parser<my_parser_wrapper>;

However, this solution is fully flawed. As my_parser_wrapper inherits directly from basic_parser, we cannot inject some my_parser_wrapper2 which applies another transformation in the chain.

I can’t emphasize enough that the problem is not about adding a “skip-this-set-of-HTTP-headers” function. The problem is about a fundamental building block which can solve more of the user needs. I could keep thinking about the different problems that could happen, but if you do not give a try to enter in the general problem and insist on a myopic vision, you’ll never grasp my message (just as an addicted to inductive reasoning will never understand someone who is using deductive reasoning). If all you have is a hammer, everything looks like a nail. We shall see more design implications later on as we continue this chapter.

As the tokens are found, the user callbacks are called. The function returns the number of parsed bytes.

Note
DESIGN IMPLICATIONS

And as each sentence goes on, it seems that I need to explain more design implications.

What if you want to reject messages as soon as one specific token is found? The point here is about avoiding unnecessary computation of parsing elements of a message that would be rejected anyway.

For Boost.Http parser, the control flow is yours to take and…​

Do what thou wilt shall be the whole of the Law.

— Aleister Crowley

A concrete example if you may:

// ...

case code::field_value:
    if (last_header_was_x && is_good(reader.value<token::field_value>())) {
        // stop the world (e.g. `return` or `throw`)
    }

// ...

As for Boost.Beast parser. There is an answer, but not with your current limited knowledge of the API. Let’s continue to present Boost.Beast API and come back at this “stop the world” problem later.

The behaviour usually found in push parsers is to parse the stream until the end of the feeded buffers and then return. This is the NodeJS’s parser approach from which Boost.Beast takes much inspiration. However, Boost.Beast takes a slightly different approach to this problem so it’s possible to parse only one token at a time. The Boost.Beast solution is the eager function:

void
eager(
    bool v);

Normally the parser returns after successfully parsing a structured element (header, chunk header, or chunk body) even if there are octets remaining in the input. This is necessary when attempting to parse the header first, or when the caller wants to inspect information which may be invalidated by subsequent parsing, such as a chunk extension. The eager option controls whether the parser keeps going after parsing structured element if there are octets remaining in the buffer and no error occurs. This option is automatically set or cleared during certain stream operations to improve performance with no change in functionality.

The default setting is false.

— Vinnie Falco
Boost.Beast documentation
Note
DESIGN IMPLICATIONS

And now, back at the “stop the world” problem…​

Simply put, Boost.Beast solution is just a hackishy way to implement a pull parser — the parser approach consciously chosen by Boost.Http parser.

Alternatively, you can just set the error_code& ec on the callback implementation to stop parsing, but this wouldn’t solve all the use cases (the reason why eager is provided).

Continuing this inductive reasoning of “hey! a problem appeared, let’s write yet another function, function_xyz, to solve use case 777”, a number of other functions are provided. One of them is header_limit:

void
header_limit(
    std::uint32_t v);

This function sets the maximum allowed size of the header including all field name, value, and delimiter characters and also including the CRLF sequences in the serialized input. If the end of the header is not found within the limit of the header size, the error http::header_limit is returned by http::basic_parser::put.

Setting the limit after any header octets have been parsed results in undefined behavior.

— Vinnie Falco
Boost.Beast documentation

Another function, body_limit, is provided in the same spirit of header_limit. What if I have a use case to limit request-target size? Then Boost.Beast author will add function_xyz2 to use case 778.

Note
DESIGN IMPLICATIONS

What is the Boost.Http solution to this problem 🤔? This is broken into two possible cases.

  1. The whole token is in the buffer: In such case you just need to check token_size.

  2. The buffer has been exhausted and no token is there: Here, just check expected_token.

It’ll work for any token (i.e. you don’t need one extra function for each possible token which would just complicate the implementation and inflate the object with a large Settings object of some sort).

With all this info, the Boost.Beast parser is mostly covered and we can delve into the implementation of such interface.

Note
DESIGN IMPLICATIONS

Now…​ let’s look at something different. Suppose the following scenario:

You have an embedded project and the headers must not be stored (as it’d imply heap memory of complex data structures). You process options with an in situ algorithm out from the headers. In Boost.Http parser, I’m imagining something in these lines:

enum last_header_code {
    OUT_OF_INTEREST_SET,
    FOO,
    BAR,
    FOOBAR
};

// ...

case code::field_name:
    {
        auto v = reader.value<token::field_name>();
        if (iequals(v, "foo")) {
            last_header = FOO;
        } else if (iequals(v, "bar")) {
            last_header = BAR;
        } else if (iequals(v, "foobar")) {
            last_header = FOOBAR;
        } else {
            last_header = OUT_OF_INTEREST_SET;
        }
        break;
    }
case code::field_value:
    if (last_header == FOO) {
        foo = process_foo(reader.value<token::field_value>());
    } else if (last_header == BAR) {
        bar = process_bar(reader.value<token::field_value>());
    } else if (last_header == FOOBAR) {
        foobar = process_foobar(reader.value<token::field_value>());
    }
    break;

// ...

Boost.Beast solution is not hard to imagine too:

// ...

void on_field_impl(field, string_view name, string_view value, error_code& ec)
{
    if (iequals(name, "foo")) {
        foo = process_foo(value);
    } else if (iequals(name, "bar")) {
        bar = process_bar(value);
    } else if (iequals(name, "foobar")) {
        foobar = process_foobar(value);
    }
}

// ...

So…​ what does each design implies? As Boost.Beast parser always parse field name + field value together, if both fields sum up more than the buffer size, you’re out of luck. Both tokens must fit in the buffer together.

Just as an exercise, let’s pursue the inductive reasoning applied to this problem. We could split the Boost.Beast’s on_field_impl callback into two:

void
on_field_name_impl(
    field f,
    string_view name,
    error_code& ec);

void
on_field_value_impl(
    string_view value,
    error_code& ec);

But then we create another problem:

[…​] it is the responsibility of the derived class to copy any information it needs before returning from the callback […​]

— Vinnie Falco
Boost.Beast documentation

If you don’t see a problem already, let me unveil it for you. Now, most of the uses of the parser, which want to store the HTTP headers in some sort of std::multimap structure will have to perform one extra allocation:

void on_field_name_impl(field, string_view name, error_code& ec)
{
    last_header = to_string(name);
}

Under the push parser model, these two cases are irreconcilable. Boost.Beast opts to solve the most common problem and this was a good design choice (let’s give credit where credit is due).

However, Boost.Http parser is a good choice in any of these two cases. It only feeds one token at a time. And as Boost.Http message framework demonstrate, we can use the first bytes of the buffer to store the HTTP field name.

And just to present a more readable alternative, you could play with copies of the reader object made in the stack of the my_socket_consumer::on_socket_callback function. This way, you have a point in time and you can make the parser “go back”. The copies are cheap because the reader object is just an integer-based state machine with a few indexes. The idea behind this solution is to mirror current Boost.Beast behaviour — field name and field value are always kept together in the buffer.

Remember…​ principles. I can attack other specific cases. As an exercise, try to find a few yourself.

2.6.2. Implementing the Boost.Beast interface

Note

As we previously seen, there are several functions in Boost.Beast parser that are just boilerplate inherited (e.g. eager) thanks to the choice of the wrong fundamental model (i.e. pull vs push).

We’ll skip some of this boilerplate as it is not of our interest. Our purpose with this tutorial was to show design implications derived from the choices of the fundamental models.

template<bool isRequest, class Derived>
class basic_parser;

// This template specialization is wrong, but is kept for simplification
// purposes.
template<bool isRequest, class Derived>
class basic_parser<true, Derived>
{
public:
    template<
        class ConstBufferSequence>
    std::size_t
    put(
        ConstBufferSequence const& buffers,
        error_code& ec)
    {
        // WARNING: the real implementation will have more trouble because of
        // the `ConstBufferSequence` concept, but for the reason of simplicity,
        // we don't show the real code here.
        reader.set_buffer(buffers);

        error_code ec;

        while (reader.code() != code::error_insufficient_data) {
            switch (reader.code()) {
            case code::skip:
                break;
            case code::method:
                method = reader.value<token::method>;
                break;
            case code::request_target:
                target = reader.value<token::request_target>;
                break;
            case code::version:
                static_cast<Derived&>(*this)
                    .on_request_impl(/*the enum code*/, method, target,
                                     reader.value<token::version>(), ec);
                if (ec) {
                    // TODO: extra code to enter in error state
                    return reader.parsed_count();
                }
                break;

            // ...

            case code::end_of_headers:
                static_cast<Derived&>(*this).on_header_impl(ec);
                if (ec) {
                    // TODO: extra code to enter in error state
                    return reader.parsed_count();
                }
                break;

            // ...
            }
            reader.next();
        }

        return reader.parsed_count();
    }

private:
    boost::http::reader::request reader;

    // It's possible and easy to create an implementation that doesn't allocate
    // memory. Just keep a copy of `reader` within the `put` function body and
    // you can go back. As `reader` is just an integer-based state machine with
    // a few indexes, the copy is cheap. I'm sorry I don't have the time to code
    // the demonstration right now.
    std::string method;
    std::string target;
};

A final note I want to add is that I plan more improvements to the parser. Just as Boost.Beast parser is an evolution of the wrong model chosen for the problem, my parser still has room to evolve. But from my judgment, this parser already is better than Boost.Beast parser can ever be (i.e. the problems I presented here are unfixable in Boost.Beast design…​ not to mention that Boost.Beast parser has almost the double amount of member-functions to solve the same problem [14]).

3. Design choices

3.1. FAQ

  1. Why build on top of Boost.Asio?

    One of the requirements of Boost.Http is scalable performance. Scalable performance requires an asynchronous design. Asio is expected to become the future C++ standard for sockets. So, Asio it is.

    Also, reuse existing std or Boost components is nice and Asio is the Boost solution for asynchronous socket I/O.

  2. Why C++11?

    Asynchronous code can be largely simplified with language support for lambdas. To speed up the development (and decrease bugs), C++11 was chosen, but interface-wise, the only real C++11 feature is enum classes, which can be emulated in C++98 easily.

    C++98 support might be added later. The priority is to prove the core set of abstractions is correct. Then I can move on the task to fatten the library. The proof comes in the act of passing the Boost review process.

    To be fair, I also depend on the C++11 definition of multimap, but Boost containers can do the job easily.

  3. Have you considered contribute to project X/Y?

    Yes, I have.

    But current Boost.Http is not like what I was expecting when I started to writing it. Boost.Http is better. The gap between Boost.Http and other projects became even larger. My previous research won’t mention every difference.

    Boost.Http supports pipelining. Pion has separate functions for chunking. Boost.Http is designed for multiple backends. POCO, QtHttp and Casablanca aren’t build on top of Asio. Pion and cpp-netlib will use their own thread-pool, instead adhering to Asio threading model.

  4. Why is it only server-side?

    Server-side and client-side are of interest to different applications. Initially, the focus was to provide a library just to server-side, but with the time spent on research and development, it became apparent that many of the proposed abstractions are also useful for client-side.

    After this fact, a lot of caution was devoted to design the interface to retain the usefulness in client-side, where it makes sense. But this is not enough. A lot of time was spent on research just to get the server-side right and I expect that much time (or more) to also get the client-side right.

    Before any serious effort is spent on client-side, I want to focus on server-side, where the application load may be way higher and C++ may be way more desired. And just as the server-side interface development was driven by a strict set of guidelines (multiple backends, modularity with specific use cases…​), we need to define what we want to achieve with the client-side abstraction. What kind of usage will be appropriate with such design.

  5. Why isn’t a router available?

    Advocates of tree-based routers tend to ignore the middleware-based approach and the other way around is also true. It happens that some even only know one way and don’t even stop to consider that their approach isn’t appropriate for every project. This subject will affect the life of the users a lot and can be rather polemic.

    I just provide the building blocks and you can create the router any way you want. Actually, I intend to implement them later, because implementing them now will just distract the attention of the reviewers and it’d be a waste of time if the review proves the core set of abstractions is wrong.

    Most of the designs I see propose dynamic routers, where you can change the routing rules at runtime, but this feature is rarely needed. Wouldn’t be wonderful if you could use great syntax sugars to declare routers that receive as much optimization as possible at compile-time? Wouldn’t be wonderful to use nested routers? Wouldn’t be wonderful if you could collaborate the tree-based and middleware-based approach very easily? Maybe even some kind of collaboration between the statically declared routers and dynamic routers? I hope this will be rather polemic and will require a lot of iterations to get it right, maybe with a mini-review for acceptance.

  6. How robust is this parser?

    It doesn’t try to parse URLs at all. It’ll ensure that only valid characters (according to HTTP request target BNF rule) are present, but invalid sequences are accepted.

    This parser is a little (but not too much) more liberal in what accepts and it’ll accept invalid sequences for rarely used elements that don’t impact upper layers of the application. The reason to accept such non-conformant sequences is a simpler algorithm that can be more performant (e.g. we only check for invalid chars, but not invalid sequences). Therefore, it’s advised to reframe the message if you intend to forward it to some other participant. Not doing so, might be a security issue if the participant you are forwarding your message to is know to show improper behaviour when parsing invalid streams. There are several references within the RFC7230 where similar decisions are suggested (e.g. if you receive redundant Content-Length header field, you must merge it into one before forwarding or reject the message as a whole).

  7. Why not make a parser using Boost.Spirit?

    Boost.Spirit needs backtracking to implement the OR operator. Boost.Spirit can’t build a state machine which would allow you to continue parsing from the suspended point/byte. Thanks to these characteristics, it can’t be used in our HTTP parser. Also, we don’t see much benefit in pursuing this effort.

  8. What is the recommended buffer size?

    A buffer of size 7990 is recommended (suggested request line of 8000 by section 3.1.1 of RFC7230 minus spaces, minus http version information minus the minimum size of the other token in the request line). However, the real suggested buffer size should be how long names you expect to have on your own servers.

  9. What are the differences between reader::request and reader::response?

    • response has the void set_method(view_type method) member-function.

      Warning
      This member-function MUST be called for each HTTP response message being parsed in the stream.
    • response has the void puteof() member-function.

    • code() member function return value has different guarantees in each class.

    • template<class T> typename T::type value() const member function accepts different input template arguments in each class.

3.2. Design choices

To convince you about the solution, I’ll start the text highlighting some problems and requirements. If you understand the problem, you’ll understand why the solution was proposed like that.

One of the wanted features for this library since the very beginning was to make it possible to make a small change in code to expose the HTTP service through a different communication mechanism. Like, change from embedded HTTP server to FastCGI-exposed server.

There are several libraries who will provide some object you can use to consume HTTP traffic from the world and will act as a request-reply door to create web applications. Libraries who will expose a request object you can use to consume TCP traffic and will export url and headers properties. The problem with this approach is the coupling between HTTP messages and HTTP communication channels.

In Boost.Http, HTTP messages and HTTP communication channels are decoupled, so it is easier to replace the communication channel later. You could easily use an unprotected embedded HTTP server on development environment to tests and replace it in favor of a full-blow solution during production.

Boost.Http defines some type requirements to abstract communication channels and provide some polymorphic adapters who will type erase them. The abstraction was specified carefully to allow robust applications. Your application will not hang trying to live stream a video because the request was done from an HTTP/1.0 client. Also, your handler won’t know what HTTP version (if any) the HTTP request was made with.

Also among the wanted features was to retain the usefulness of the library whether you’re using it to power an application intended to run from an low-end embedded device or an application intended to run on a cluster with plenty of resources to be made use of. An embdeded device may not have the luxury to host a pool or a cache layer, but a cluster may even demand these layers to properly handle thousands of requests every second. With this use case in mind, modularity was achieved.

The plan to finish such ambitious project was “to expose an HTTP abstraction able to make use of the HTTP power (chunking/streaming, pipelining, upgrade for supporting channels and multiplexing for supporting channels), at the same time that a complete separation of communication channels and HTTP messages is achieved”.

With the separation of HTTP messages and HTTP communication channels, alongside the use of an active model (you ask by the next request instead providing a handler and waiting for them), several of the requirements became very easy to fulfill, such as HTTP pipelining, custom memory allocation, buffers, cache layers and pools of objects.

With such very generalized abstractions, you may be worried about the need to type too much to get something done. This is being solved by providing higher level flexible abstractions, such as the file server you can already find.

3.2.1. The what

request handling

The above image shows an overview of how to build HTTP servers using Boost.Http.

Let’s assume you’re going to use the provided http::socket, suitable for embedding HTTP servers in your application.

You must instantiate a TCP socket acceptor and accept new connections. You should handle all connections concurrently.

Each connection you handle is a pipeline of request-reply pair. To receive the full request, your action may be required at several points. First, you should receive the request metadata, then it may be necessary to issue a 100-continue response. Then you need to issue reads for the body and the trailers until the whole request has been received.

You’re finally able to send the reply. You have two options, atomic messages or chunked messages. Chunked messages are not always available and must check if they can be used for each request you receive, using write_response_native_stream().

If you spread the handling logic among several functions, a good approach would be to always share the triplet <communication channel, request message, response message> around.

Still missing is URL parsing and request routing, so you must do this yourself, possibly managing pools of message and socket objects.

This system allows you to implement powerful schedulers doing fair share of resources over different IPs, whether the requests originate from HTTP or HTTPS, using all cores of your CPU and deferring new work when the work load is too high. You should be able to do all fine-grained tuning you need and also easily create higher level that are suitable for your application. Not only that, this library could become an interoperability layer for all higher-level that web application developers create.

request server
request pipeline
request request
request request metadata
request body

Also, if you pay attention, you’ll realize that this proposal just expose HTTP with a message oriented abstraction. All procedures in the diagram are related to HTTP events and actions. And this is a modern API and you can use pretty much every modern HTTP feature (persistent streams & HTTP pipelining, chunked entities, 100-continue status, …​). And you won’t handle any parsing or low-level detail at all. It’s abstracted enough to allow alternative backends.

However, this can easily become a callback hell, and futures wouldn’t help much, given the need to use while-constructs. If you use coroutines, there is hope your code will be readable. Boost.Http follows Asio extensible asynchronous model and you’re free to use callbacks, futures, coroutines or others.

3.2.2. ASIO familiarity

This library may be very pleasant to use for any ASIO-centered mind.

  • Completion tokens received as the last argument for aync functions.

  • Async operations have the async_ prefix.

  • User control the bufferring mechanism, passing the opaque asio::buffer type.

  • User provides output arguments as references and they’ll be “filled” by the time the operation completes.

  • Memory management is left for the user.

  • An active model is presented.

  • Similar nomenclature.

The ASIO way saved us from many problems that otherwise would force us to propose solutions to already know problems such as:

  • Object pools.

  • Deferring acceptance to later on high load scenarios.

  • HTTP pipelining problems.

  • Partially filling response objects from different layers of abstractions.

  • A wrapping/wrapped socket can take care of tasks such as synchronization/queueing and timeout.

3.2.3. The mysterious/weird/news API

One of the maybe surprising things to start with is the use of highly structured objects as opposed to things like opaque buffers. You pass a message object to the initiating function and you’ll have a fully decomposed object with an URL, a method and even an associative container for the headers!!!

If you do have special memory requirements for the messages, you’re free to implementing an alternative container, as long as it fulfills the documented Message concept. Connections channels and HTTP messages are not coupled together. You can reuse these pieces in many many different contexts.

The uncoupled architecture is more general and it is the default mode, but let’s say you work at a more constrained environment where memory copying is banned, for instance. You could provide your HTTP backend (e.g. a non-copying embedded server) tied to your specific HTTP message type implementing our ideas and you still may benefit from this libray. This library provides some HTTP algorithms and some HTTP handlers (e.g. file server) and these abstractions will save some time from you.

Another difference in this library is the presence of an associated state for reading and writing messages. I believe this abstraction can be extended to also support very simple HTTP clients. To avoid confusion, if some member-function cannot be used for both modes (clients and servers), it’ll have one of the following prefixes:

  • async_read_request

  • async_read_response

  • async_write_request

  • async_write_response

We gave special attention to read_state and write_state to make sure it’ll also be usable for simple and asynchronous HTTP clients.

3.2.4. The why

Boost.Http provides an HTTP socket, which can be used to manage a pipeline of HTTP messages (i.e. an HTTP request or an HTTP reply). HTTP is stateless and each message coming from the same socket is independent. The HTTP socket from Boost.Http is a concept and specific implementations from this concept may provide more guarantees about the communication properties. The reasons to provide few guarantees are (#1) because we want a common denominator from which we can provide implementation for multiple communication channels and (#2) because implementation details are usually not required for the application, which is only interested in a high-level abstraction. The provided boost::http::basic_socket implementation will handle actual HTTP traffic from TCP sockets and you can use it to handle HTTP/1.0 and HTTP/1.1 traffic from TCP and SSL sockets.

read_state() and write_state() are used to inspect the current state of interaction and react appropriately. There are rules regarding when the socket can mutate and change its states. Once you request the socket to read a new HTTP request, you’ll be notified as soon as the request metadata (request line and HTTP headers) are ready, then you can progressively download the body and react appropriately. This idea is very useful to improve communication between the library authors and application authors and also helps to create some tests.

You’ll have to inspect the socket to know whether the current message-exchange requires 100-continue, allows chunked entities (streaming response) and alike. There is like two kind of replies. With atomic replies, you write the whole message at once. With chunked message, you compose a message spreading its construction among several API calls. You may want to use chunked messages when you don’t know the whole body in advance (e.g. reading a file, video live stream…​), but chunked messages can only be used in certain message exchanges. The reason behind providing two kind of replies is to properly support a wider range of HTTP communication channels.

You create one HTTP socket for each HTTP client and should handle them all concurrently. In case you’re using the embeddable HTTP server backend, you must use an acceptor to initialize the basic_sockets' next_layer() and then consume them. basic_socket templatize the underlying internal socket, so you can use SSL, queue wrapping socket (to work around Asio’s composed operations) and so on. The intention of Boost.Http is not only to generalize over data structures and HTTP backends, but about any place where it may be helpful.

The choice to represent the HTTP messages in separate objects and the whole combination of this design ease supports for HTTP pipelining a lot. In passive styles, a request is generated and generated and you must act on them. In this active style, you explicitly request the next message, handle it and then request another one. In this scenario, two unrelated messages won’t be mixed up, because you won’t see the next message while you don’t handle the current one. The read and write states gives a mean to communicate how to use the API and how to detect some logical errors in the application.

The choice to hide details from the HTTP connection (HTTP version, socket object…​) was done to properly support multiple backends. The ability to query certain properties from the underlying communication channel is necessary to achieve reliability under this model. A lot of responsibilies and expected behaviour is documented on the type requirements for ServerSocket objects.

A C++11 multimap is used to represent HTTP headers because that’s what HTTP headers conceptually are. HTTP spec specifies you must handle HTTP header elements with equivalent keys as if there was a single header where the values are joined with commas. Some old headers don’t work with this approach and their values, when multiple elements with equivalent keys are present, must be stored separately. The order matters, just as the C++11 definition of multimap.

Runtime-based polymorphic behaviour isn’t used by default, because not all projects are willing to pay for this price. Well defined type requirements are provided and some polymorphic adaptors will convert models of these type requirements to classes inheriting a single specific abstract base class.

Member-functions as opposed to member-variables are used in HTTP messages, because some setup (e.g. a proxy who doesn’t want to reformat the messages) may want to move the HTTP parser to the HTTP message object. I want to allow a library who will beat C servers in every aspect.

As per RFC 7230, “a server MUST NOT apply a request to the target resource until the entire request header section is received, since later header fields might include conditionals, authentication credentials, or deliberately misleading duplicate header fields that would impact request processing”, so we define an interface who will only expose a message once the complete header section is ready. The message body can be progressively received later. The API also unifies HTTP messages and HTTP chunking.

URL-decomposed objects aren’t used because all an HTTP backend needs is some string-like container to push bytes. This container can implement an in-place URL parsing algorithm and it is all solved. The generic HTTP backends you find in Boost.Http won’t care about the url concrete type and you don’t need to expect any barrier from this side.

We do not use the message itself as a buffer object while we’re parsing the connection stream. We require a separate buffer to be able to properly handle HTTP pipelining (and futurely multiplexing in HTTP/2.0).

3.2.5. The when

I couldn’t resist the temptation of adding a “when” named section after I already had written a “what” and a “why” section.

Just too much research time went into this proposal. Really, a lot of time. I developed some broken HTTP projects some years ago, learned a lot of design with really different approaches (PHP, Django, Node.js) trying to solve this problem, developed my own serious project (Tufão) and continued to study and research a lot (the HTTP spec resurrection project, or RFC 7230, helped a lot). I’ve gathered info around where interoperability may be a problem if API doesn’t help and what features will be desired, sooner or later, by users, among other data. I’ve done real effort to gather feedback from C++ programmers for quite a while already.

A special thanks to Bjørn Reese for mentoring me on Asio quirks and API general design, the feedback which changed the proposal the most. Also a special thanks to any friend who helped to maintain my mind at a happy state.

3.3. Roadmap

  • C++98.

  • Client-side HTTP.

  • HTTP/2.0.

  • Request-router.

  • Forms and file uploads.

  • Cookies and sessions (RFC 6265).

  • WebSocket.

  • Alternative backends.

  • Increase test coverage a lot.

  • Benchmarks.

  • Compress replies.

  • WebDAV (it will depend on Boost.XML, which doesn’t exist yet).

  • World domination.

Parser layer
  • Parsers combinators.

  • Incremental message generator.

  • Iterator adaptors.

4. Reference

All declarations from this library resides within the boost::http namespace. For brevity, this prefix is not repeated on the documentation.

4.1. Summary

4.1.5. Error Codes

4.1.8. Macros

BOOST_HTTP_SOCKET_DEFAULT_BUFFER_SIZE

This macro defines the default buffer size for basic_buffered_socket. It’s safe to override this value (per using class or globally) and should be done before including the file <boost/http/buffered_socket.hpp>. The default provided value (i.e. the non-overriden version) is unspecified (e.g. can change among versions and platforms).

BOOST_HTTP_UPGRADE_HEAD_DISABLE_CHECK

Define this macro if you want to disable the check in basic_socket::upgrade_head. The check is there to ensure that you only uses this function in HTTP client mode. It’ll throw an exception to notify the violation.

BOOST_HTTP_SOCKET_DEFAULT_BODY_COPY_THRESHOLD

This macro defines the default value for basic_socket's Settings::body_copy_threshold. It represents the maximum amount of bytes basic_socket will copy from message bodies in an attempt to send a single buffer chunk to the underlying network write syscall. If 0, then no body is copied and a split write buffer will always be used. The default provided value (i.e. the non-overriden version) is unspecified (e.g. can change among versions and platforms).

4.2. Detailed

4.2.1. headers

#include <boost/http/headers.hpp>

headers is a simple typedef for some unspecified multimap container.

The user can safely assume that headers::key_type will be std::string and headers::mapped_type will be std::string. std::string is used because fulfills the requirements perfectly and is very unlikely it will ever cause any controversy.

The user can also assume that this type fulfills the Headers definition of message.

Note
Previously, headers was guaranteed to be a typedef for boost::container::flat_multimap.

4.2.2. request

#include <boost/http/request.hpp>

request is a simple typedef for basic_request. It’s defined as follows:

typedef basic_request<std::string, headers, std::vector<std::uint8_t>> request;

std::vector<std::uint8_t> is used over std::string, because fits the purpose of the body (binary data payload container) better (no '\0' character terminator, well-defined behaviours of capacity, size and iterator invalidation, …​).

4.2.2.1. See also

4.2.3. response

#include <boost/http/response.hpp>

response is a simple typedef for basic_response. It’s defined as follows:

typedef basic_response<std::string, headers, std::vector<std::uint8_t>> response;

std::vector<std::uint8_t> is used over std::string, because fits the purpose of the body (binary data payload container) better (no '\0' character terminator, well-defined behaviours of capacity, size and iterator invalidation, …​).

4.2.3.1. See also

4.2.4. socket

#include <boost/http/socket.hpp>

socket is a simple typedef for basic_socket. It’s defined as follows:

typedef basic_socket<boost::asio::ip::tcp::socket> socket;

4.2.5. buffered_socket

#include <boost/http/buffered_socket.hpp>

buffered_socket is a simple typedef for basic_buffered_socket. It’s defined as follows:

typedef basic_buffered_socket<boost::asio::ip::tcp::socket> buffered_socket;

4.2.6. request_response_wrapper

#include <boost/http/request_response_wrapper.hpp>

request_response_wrapper is an adapter which acts like a common type (akin to std::common_type) for different Request and Response types. There is no type erasure and only Message interface will be available.

The purpose of this class is not to be a polymorphic wrapper. Therefore, it’s not our concern to make sure compatible objects (i.e. objects with the same header_type and so on) are of the same request_response_wrapper type. In other words, request_response_wrapper will not free you from the template work if you want to support different Request/Response types. This is not a design issue. However, there is a templated constructor which can accept some different types.

4.2.6.1. Template parameters
Request

A model of the Message concept.

Response

A model of the Message concept.

Note
Response::headers_type must be the same as Request::headers_type. And the same applies to Response::body_type. Also, if const is applied to Request or Response, it must be applied to both.
4.2.6.2. Member types
headers_type

If Request is const, then it is defined as const typename Request::headers_type. Otherwise, it’s defined as typename Request::headers_type.

body_type

If Request is const, then it is defined as const typename Request::body_type. Otherwise, it’s defined as typename Request::body_type.

4.2.6.3. Member functions
request_response_wrapper(Request &request)

Constructs a request_response_wrapper from a Request.

request_response_wrapper(Response &response)

Constructs a request_response_wrapper from a Response.

template<class Request2, class Response2> request_response_wrapper(request_response_wrapper<Request2, Response2> &other)

Constructs a request_response_wrapper from another request_response_wrapper with compatible headers_type and body_type.

Message concept
headers_type &headers()

Returns the wrapped headers object.

const headers_type &headers() const

Returns the wrapped headers object.

body_type &body()

Returns the wrapped body object.

const body_type &body() const

Returns the wrapped body object.

headers_type &trailers()

Returns the wrapped trailers object.

const headers_type &trailers() const

Returns the wrapped trailers object.

4.2.6.4. See also

4.2.7. basic_poly_socket_base

#include <boost/http/poly_socket_base.hpp>

basic_poly_socket_base is the base class for all classes in the hierarchy defined for runtime-based polymorphic HTTP producers. It is an abstract class that only contains functionality useful for simultaneously both channel ends (client and server).

References for objects of this class are expected to fulfill the Socket concept.

This class has no state to ease multiple inheritance and it is virtual-inherited by basic_poly_server_socket and basic_poly_client_socket.

The design for the hierarchy started with this class was a little inspired by C++'s iostream and N3525: Polymorphic Allocators.

poly socket hierarchy
Figure 1. poly_socket_base hierarchy
4.2.7.1. Template parameters
Message

The message type.

4.2.7.2. Member types

These are the types chosen set in stone to guarantee ABI stability across binaries (including possible plugins).

typedef boost::asio::executor executor_type

The executor type.

typedef Message message_type

The message type usable within this class’s operations.

typedef asio::experimental::poly_handler<void(boost::system::error_code)> handler_type

The type for asynchronous operation completion handlers.

4.2.7.3. Member functions
Overwritable functions

These are the functions that subclasses need to implement in order to lose the abstract class property.

virtual executor_type get_executor() = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the Socket concept.

virtual bool is_open() const = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the Socket concept.

virtual read_state read_state() const = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the Socket concept.

virtual write_state write_state() const = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the Socket concept.

virtual void async_read_some(message_type &message, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the Socket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_read_trailers(message_type &message, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the Socket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write(const message_type &message, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the Socket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write_trailers(const message_type &message, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the Socket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write_end_of_message(handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the Socket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual ~basic_poly_socket_base() = 0

Destructor. Inline definition done with = default.

Wrappers to fulfill the ASIO extensible model

These functions rewrite usual function calls in terms of the ABI stable interface. They also enable the ASIO extensible model within this hierarchy.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_read_some(message_type &message, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_read_some.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_read_trailers(message_type &message, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_read_trailers.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write(const message_type &message, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_trailers(const message_type &message, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_trailers.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_end_of_message(CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_end_of_message.

4.2.7.4. See also

4.2.8. basic_poly_server_socket

#include <boost/http/poly_server_socket.hpp>

This class virtual-inherits basic_poly_socket_base<Message> and extends its interface with virtual functions useful for server sockets. This class itself is also an abstract class with no state (i.e. an interface).

References for objects of this class are expected to fulfill the ServerSocket concept.

4.2.8.1. Template parameters
Request

The request type.

Response

The response type.

Message = request_response_wrapper<Request, Response>

The message type.

4.2.8.2. Member types

These are the types chosen set in stone to guarantee ABI stability across binaries (including possible plugins).

typedef Request request_type

The request type.

typedef Response response_type

The response type.

4.2.8.3. Member functions
Overwritable functions

These are the functions that subclasses need to implement in order to lose the abstract class property.

virtual bool write_response_native_stream() const = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the ServerSocket concept.

virtual void async_read_request(request_type &request, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the ServerSocket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write_response(const response_type &response, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the ServerSocket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write_response_continue(handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the ServerSocket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual void async_write_response_metadata(const response_type &response, handler_type handler) = 0

The only difference between this function (besides mandatory virtual) and the one with the same name found on the ServerSocket concept is the lack of support for completion tokens and the use of a type erased handler instead.

virtual ~basic_poly_server_socket()

Destructor.

Wrappers to fulfill the ASIO extensible model

These functions rewrite usual function calls in terms of the ABI stable interface. They also enable the ASIO extensible model within this hierarchy.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_read_request(request_type &request, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_read_request.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_response(const response_type &response, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_response.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_response_continue(CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_response_continue.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_response_metadata(const response_type &response, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_response_metadata.

4.2.8.4. See also

4.2.9. basic_poly_client_socket

#include <boost/http/poly_client_socket.hpp>

This class virtual-inherits basic_poly_socket_base<Message> and extends its interface with virtual functions useful for client sockets. This class itself is also an abstract class with no state (i.e. an interface).

References for objects of this class are expected to fulfill the ClientSocket concept.

4.2.9.1. Template parameters
Request

The request type.

Response

The response type.

Message = request_response_wrapper<Request, Response>

The message type.

4.2.9.2. Member types

These are the types chosen set in stone to guarantee ABI stability across binaries (including possible plugins).

typedef Request request_type

The request type.

typedef Response response_type

The response type.

4.2.9.3. Member functions
Overwritable functions

These are the functions that subclasses need to implement in order to lose the abstract class property.

virtual void async_write_request(const request_type &request, handler_type handler) = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the ClientSocket concept.

virtual void async_write_request_metadata(const request_type &request, handler_type handler) = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the ClientSocket concept.

virtual void async_read_response(response_type &response, handler_type handler) = 0

This function presents no differences (besides mandatory virtual) from the one with the same name found on the ClientSocket concept.

virtual ~basic_poly_client_socket()

Destructor.

Wrappers to fulfill the ASIO extensible model

These functions rewrite usual function calls in terms of the ABI stable interface. They also enable the ASIO extensible model within this hierarchy.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_request(const request_type &request, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_request.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_request_metadata(const request_type &request, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_write_request_metadata.

template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_read_response(response_type &response, CompletionToken &&token)

Handle the token and dispatch the operation to the ABI stable async_read_response.

4.2.9.4. See also

4.2.10. basic_poly_socket

#include <boost/http/poly_socket.hpp>

This class inherits basic_poly_server_socket<Request, Response, Message> and basic_poly_client_socket<Request, Response, Message>. This class itself is also an abstract class with no state (i.e. an interface).

References for objects of this class are expected to fulfill the ServerSocket concept and the ClientSocket concept.

This class is inherited by socket_adaptor.

4.2.10.1. Template parameters
Request

The request type.

Response

The response type.

Message = request_response_wrapper<Request, Response>

The message type.

4.2.10.2. Member types

These are the types chosen set in stone to guarantee ABI stability across binaries (including possible plugins).

typedef Request request_type

Request message type.

typedef Response response_type

Response message type.

4.2.10.3. See also

4.2.11. poly_socket_base

#include <boost/http/poly_socket_base.hpp>

poly_socket_base is a simple typedef for basic_poly_socket_base. It’s defined as follows:

typedef basic_poly_socket_base<request_response_wrapper<request, response>>
  poly_socket_base;

4.2.12. poly_server_socket

#include <boost/http/poly_server_socket.hpp>

poly_server_socket is a simple typedef for basic_poly_server_socket. It’s defined as follows:

typedef basic_poly_server_socket<request, response>
  poly_server_socket;
4.2.12.1. See also

4.2.13. poly_client_socket

#include <boost/http/poly_client_socket.hpp>

poly_client_socket is a simple typedef for basic_poly_client_socket. It’s defined as follows:

typedef basic_poly_client_socket<request, response>
  poly_client_socket;
4.2.13.1. See also

4.2.14. poly_socket

#include <boost/http/poly_socket.hpp>

poly_socket is a simple typedef for basic_poly_socket. It’s defined as follows:

typedef basic_poly_socket<request, response> poly_socket;
4.2.14.1. See also

4.2.15. basic_request

#include <boost/http/request.hpp>

This template can be used to easily define classes fulfilling the Request concept and specializing the is_request_message trait.

4.2.15.1. Template parameters
String

The type to fulfill the X::string_type from the Request concept.

Headers

The type to fulfill the X::headers_type from the Request concept.

Body

The type to fulfill the X::body_type from the Request concept.

4.2.15.2. Member types
typedef String string_type

The type to fulfill the X::string_type from the Request concept.

typedef Headers headers_type

The type to fulfill the X::headers_type from the Request concept.

typedef Body body_type

The type to fulfill the X::body_type from the Request concept.

4.2.15.3. Member functions
string_type &method()

Returns the internal method object.

const string_type &method() const

Returns the internal method object.

string_type &target()

Returns the internal request target object.

const string_type &target() const

Returns the internal request target object.

headers_type &headers()

Returns the internal headers object.

const headers_type &headers() const

Returns the internal headers object.

body_type &body()

Returns the internal body object.

const body_type &body() const

Returns the internal body object.

headers_type &trailers()

Returns the internal trailers object.

const headers_type &trailers() const

Returns the internal trailers object.

4.2.15.4. See also

4.2.16. basic_response

#include <boost/http/response.hpp>

This template can be used to easily define classes fulfilling the Response concept and specializing the is_response_message trait.

4.2.16.1. Template parameters
String

The type to fulfill the X::string_type from the Response concept.

Headers

The type to fulfill the X::headers_type from the Response concept.

Body

The type to fulfill the X::body_type from the Response concept.

4.2.16.2. Member types
typedef String string_type

The type to fulfill the X::string_type from the Response concept.

typedef Headers headers_type

The type to fulfill the X::headers_type from the Response concept.

typedef Body body_type

The type to fulfill the X::body_type from the Response concept.

4.2.16.3. Member functions
std::uint_least16_t &status_code()

Returns the internal status code object.

const std::uint_least16_t &status_code() const

Returns the internal status code object.

string_type &reason_phrase()

Returns the internal reason phrase object.

const string_type &reason_phrase() const

Returns the internal reason phrase object.

headers_type &headers()

Returns the internal headers object.

const headers_type &headers() const

Returns the internal headers object.

body_type &body()

Returns the internal body object.

const body_type &body() const

Returns the internal body object.

headers_type &trailers()

Returns the internal trailers object.

const headers_type &trailers() const

Returns the internal trailers object.

4.2.16.4. See also

4.2.17. basic_socket

#include <boost/http/socket.hpp>

This template can be used to easily define classes fulfilling the strict ServerSocket concept and the strict ClientSocket concept (is_server_socket and is_client_socket traits are specialized). These classes exposes the HTTP/1.1 wire format (i.e. a builtin/standalone HTTP server) into an easy-to-use API.

The underlying I/O object is expected to have the following properties:

  • It is stream-oriented (i.e. no message boundaries; read or write operations may transfer fewer bytes than requested…​).

  • It fulfills the ASIO’s AsyncReadStream requirement.

  • It fulfills the ASIO’s AsyncWriteStream requirement.

  • It is backed by a reliable transport or session-layer “connection” with in-order delivery of octets (i.e. any 8-bit sequence of data).

Note

This class doesn’t restrict the message, method, path and reason phrase types. Therefore, the following members are not defined:

  • message_type

  • request_type

  • response_type

Warning
The API from this class is implemented in terms of composed operations. As such, you MUST NOT initiate any async read operation while there is another read operation in progress and you MUST NOT initiate any async write operation while there is another write operation in progress. If you cannot guarantee the ordering of the operations, you should use some queueing socket (e.g. AxioMQ’s basic_queue_socket).
Tip
You cannot detect the lack of network inactivity properly under this layer. If you need to implement timeouts, you should do so under the lower layer.
4.2.17.1. Template parameters
Socket

The underlying communication channel type. It MUST fulfill the requirements for ASIO’s AsyncReadStream and ASIO’s AsyncWriteStream.

Settings

Traits to define some settings. It defaults to unspecified.

As a reference, the default traits class has the following form:

struct
{
    typedef reader::request req_parser;
    typedef reader::response res_parser;
    static constexpr std::size_t body_copy_threshold
        = BOOST_HTTP_SOCKET_DEFAULT_BODY_COPY_THRESHOLD;
};
4.2.17.2. Member types
typedef Socket next_layer_type

The type of the underlying communication channel.

typedef typename next_layer_type::executor_type executor_type

The type of the executor associated with the object.

4.2.17.3. Member functions
basic_socket(boost::asio::io_context &io_context, boost::asio::mutable_buffer inbuffer)

Constructor. io_context is passed to the constructor from the underlying stream.

Exceptions:
  • std::invalid_argument: If buffer size is zero.

template<class…​ Args> basic_socket(boost::asio::mutable_buffer inbuffer, Args&&…​ args)

Constructor. args are forwarded to the constructor from the underlying stream.

Exceptions:
  • std::invalid_argument: If buffer size is zero.

next_layer_type &next_layer()

Returns a reference to the underlying stream.

const next_layer_type &next_layer() const

Returns a reference to the underlying stream.

void open()

Change socket state to open.

Note
See is_open()
Warning
You MUST cancel current ongoing operations and wait for their completion handlers to be called before call this function. Otherwise, undefined behaviour is invoked.
boost::asio::const_buffer upgrade_head() const

Return the buffer representing the first few bytes of the upgraded stream (may be empty).

void lock_client_to_http10()

Lock HTTP to HTTP/1.0 version if socket is used as a client socket.

template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken,void(system::error_code, std::size_t)) async_read_chunkext(Message &message, typename Message::headers_type &chunkext, CompletionToken &&token)

Initiate an asynchronous operation to read a part of the message body. Handler is called when at least one byte is read (called with no error set and value 0), when chunk extensions are found (called with no error set and some value different than 0), when the end of message is reached (called with no error set and value 0) or when some error occurs.

message.body(), message.trailers() and chunkext are left in an unspecified state while the operation is in progress. chunkext.clear() will be called by this function before the read operation is scheduled.

When valid chunk extensions are found, they are stored in chunkext and handler is called before the chunk bytes are stored in message.body(). The chunk size is passed to the handler and chunkext data refers to the next bytes that will be pushed into message.body() (i.e. you need to initiate another read to get the chunk data referred by chunkext).

This design is simple (no need for extra error code, simple to explain, simple to program for, …​) and does expose relevant information to the user (e.g. you can extract message boundaries) without compromising security (read is still limited by buffer size and you will not be exposed to — for instance — infinite appends into message.body()).

template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_chunkext(const Message &message, const typename Message::headers_type &chunkext, CompletionToken &&token)

Initiate an asynchronous operation to write a chunk of the HTTP body data payload (chunked message). Handler is called with an appropriate argument when the operation completes.

message.body() and chunkext MUST NOT be modified while the operation is in progress.

Socket concept

See the Socket concept.

  • executor_type get_executor()

  • bool is_open() const

  • read_state read_state() const

  • write_state write_state() const

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_some(Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_trailers(Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write(const Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_trailers(const Message &message, CompletionToken &&token)

  • template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_end_of_message(CompletionToken &&token)

ServerSocket concept
  • bool write_response_native_stream() const

  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_request(Request &request, CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response(const Response &response, CompletionToken &&token)

  • template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response_continue(CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response_metadata(const Response &response, CompletionToken &&token)

ClientSocket concept
  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_request(const Request &request, CompletionToken &&token)

  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_request_metadata(const Request &request, CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_response(Response &response, CompletionToken &&token)

4.2.18. basic_buffered_socket

#include <boost/http/buffered_socket.hpp>

This template can be used to easily define classes fulfilling the strict ServerSocket concept and ClientSocket concept. These classes exposes the HTTP/1.1 wire format (i.e. a builtin/standalone HTTP server) into an easy-to-use API.

The underlying I/O object is expected to have the following properties:

  • It is stream-oriented (i.e. no message boundaries; read or write operations may transfer fewer bytes than requested…​).

  • It fulfills the ASIO’s AsyncReadStream requirement.

  • It fulfills the ASIO’s AsyncWriteStream requirement.

  • It is backed by a reliable transport or session-layer “connection” with in-order delivery of octets (i.e. any 8-bit sequence of data).

Note

This class doesn’t restrict the message, method, path and reason phrase types. Therefore, the following members are not defined:

  • message_type

  • request_type

  • response_type

Warning
The API from this class is implemented in terms of composed operations. As such, you MUST NOT initiate any async read operation while there is another read operation in progress and you MUST NOT initiate any async write operation while there is another write operation in progress. If you cannot guarantee the ordering of the operations, you should use some queueing socket (e.g. AxioMQ’s basic_queue_socket).
Tip
You cannot detect the lack of network inactivity properly under this layer. If you need to implement timeouts, you should do so under the lower layer.
4.2.18.1. Template parameters
Socket

The underlying communication channel type. It MUST fulfill the requirements for ASIO’s AsyncReadStream and ASIO’s AsyncWriteStream.

Settings

Traits passed to the underlying Socket. It defaults to unspecified.

N

The internal buffer size. It defaults to BOOST_HTTP_SOCKET_DEFAULT_BUFFER_SIZE

4.2.18.2. Member types
typedef Socket next_layer_type

The type of the underlying communication channel.

typedef typename next_layer_type::executor_type executor_type

The type of the executor associated with the object.

4.2.18.3. Member functions
basic_buffered_socket(boost::asio::io_context &io_context)

Constructor. io_context is passed to the constructor from the underlying stream.

template<class…​ Args> basic_buffered_socket(Args&&…​ args)

Constructor. args are forwarded to the constructor from the underlying stream.

next_layer_type &next_layer()

Returns a reference to the underlying stream.

const next_layer_type &next_layer() const

Returns a reference to the underlying stream.

boost::asio::const_buffer upgrade_head() const

Return the buffer representing the first few bytes of the upgraded stream (may be empty).

void lock_client_to_http10()

Lock HTTP to HTTP/1.0 version if socket is used as a client socket.

template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken,void(system::error_code, std::size_t)) async_read_chunkext(Message &message, typename Message::headers_type &chunkext, CompletionToken &&token)

See documentation in basic_socket.

template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(system::error_code)) async_write_chunkext(const Message &message, const typename Message::headers_type &chunkext, CompletionToken &&token)

See documentation in basic_socket.

Socket concept

See the Socket concept.

  • executor_type get_executor()

  • bool is_open() const

  • read_state read_state() const

  • write_state write_state() const

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_some(Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_trailers(Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write(const Message &message, CompletionToken &&token)

  • template<class Message, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_trailers(const Message &message, CompletionToken &&token)

  • template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_end_of_message(CompletionToken &&token)

ServerSocket concept
  • bool write_response_native_stream() const

  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_request(Request &request, CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response(const Response &response, CompletionToken &&token)

  • template<class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response_continue(CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_response_metadata(const Response &response, CompletionToken &&token)

ClientSocket concept
  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_request(const Request &request, CompletionToken &&token)

  • template<class Request, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_write_request_metadata(const Request &request, CompletionToken &&token)

  • template<class Response, class CompletionToken> BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code)) async_read_response(Response &response, CompletionToken &&token)

4.2.19. server_socket_adaptor

#include <boost/http/server_socket_adaptor.hpp>

This class adapts a class fulfilling the ServerSocket concept to implement the basic_poly_server_socket<Request, Response, Message> interface.

This design was chosen taking a number of shortcomings in consideration.

If the user can control object construction, he might benefit by less levels of indirection by constructing both (the HTTP socket and its runtime-based polymorphic adaptor) at once and at a single memory space.

The scenario where the user don’t control the object construction was also taken in consideration. In these cases, it’s possible to use the std::reference_wrapper type found in the <functional> header. There is a server_socket_adaptor specialization that will do the job for std::reference_wrapper.

Also, if the user needs to query for the specific type at runtime, the user can do so with a single call to dynamic_cast to the specific polymorphic wrapper (rather than calling a second function to query for the wrapped object).

The design is simple to use, to learn and to read. These values were chosen to avoid misuse by the user’s part.

Although very different, the name and inspiration were borrowed from N3525: Polymorphic Allocators.

4.2.19.1. Template parameters
Socket

The type to be wrapped.

Request = request

The request message type. Default argument is request.

Response = response

The response message type. Default argument is response.

Message = request_response_wrapper<Request, Response>

The message type. Default argument is request_response_wrapper<Request, Response>.

4.2.19.2. Specializations
  • server_socket_adaptor<std::reference_wrapper<Socket>>

4.2.19.3. Member types
typedef Socket next_layer_type

The type of the wrapped socket.

4.2.19.4. Member functions
template<class…​ Args> server_socket_adaptor(Args&&…​ args)

Constructor. args are forwarded to the underlying socket constructor.

Note
Not available under the server_socket_adaptor<std::reference_wrapper<Socket>> specialization.
server_socket_adaptor(Socket &socket)

Constructor. socket is passed to the std::reference_wrapper constructor.

Note
Only available under the server_socket_adaptor<std::reference_wrapper<Socket>> specialization.
next_layer_type &next_layer()

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

const next_layer_type &next_layer() const

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

4.2.20. client_socket_adaptor

#include <boost/http/client_socket_adaptor.hpp>

This class adapts a class fulfilling the ClientSocket concept to implement the basic_poly_client_socket<Request, Response, Message> interface.

This design was chosen taking a number of shortcomings in consideration.

If the user can control object construction, he might benefit by less levels of indirection by constructing both (the HTTP socket and its runtime-based polymorphic adaptor) at once and at a single memory space.

The scenario where the user doesn’t control the object construction was also taken in consideration. In these cases, it’s possible to use the std::reference_wrapper type found in the <functional> header. There is a client_socket_adaptor specialization that will do the job for std::reference_wrapper.

Also, if the user needs to query for the specific type at runtime, the user can do so with a single call to dynamic_cast to the specific polymorphic wrapper (rather than calling a second function to query for the wrapped object).

The design is simple to use, to learn and to read. These values were chosen to avoid misuse by the user’s part.

Although very different, the name and inspiration were borrowed from N3525: Polymorphic Allocators.

4.2.20.1. Template parameters
Socket

The type to be wrapped.

Request = request

The request message type. Default argument is request.

Response = response

The response message type. Default argument is response.

Message = request_response_wrapper<Request, Response>

The message type. Default argument is request_response_wrapper<Request, Response>.

4.2.20.2. Specializations
  • client_socket_adaptor<std::reference_wrapper<Socket>>

4.2.20.3. Member types
typedef Socket next_layer_type

The type of the wrapped socket.

4.2.20.4. Member functions
template<class…​ Args> client_socket_adaptor(Args&&…​ args)

Constructor. args are forwarded to the underlying socket constructor.

Note
Not available under the client_socket_adaptor<std::reference_wrapper<Socket>> specialization.
client_socket_adaptor(Socket &socket)

Constructor. socket is passed to the std::reference_wrapper constructor.

Note
Only available under the client_socket_adaptor<std::reference_wrapper<Socket>> specialization.
next_layer_type &next_layer()

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

const next_layer_type &next_layer() const

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

4.2.21. socket_adaptor

#include <boost/http/socket_adaptor.hpp>

This class adapts a class fulfilling the ServerSocket concept and the ClientSocket concept to implement the basic_poly_socket<Request, Response, Message> interface.

This design was chosen taking a number of shortcomings in consideration.

If the user can control object construction, he might benefit by less levels of indirection by constructing both (the HTTP socket and its runtime-based polymorphic adaptor) at once and at a single memory space.

The scenario where the user doesn’t control the object construction was also taken into consideration. In these cases, it’s possible to use the std::reference_wrapper type found in the <functional> header. There is a socket_adaptor specialization that will do the job for std::reference_wrapper.

Also, if the user needs to query for the specific type at runtime, the user can do so with a single call to dynamic_cast to the specific polymorphic wrapper (rather than calling a second function to query for the wrapped object).

The design is simple to use, to learn and to read. These values were chosen to avoid misuse by the user’s part.

Although very different, the name and inspiration were borrowed from N3525: Polymorphic Allocators.

4.2.21.1. Template parameters
Socket

The type to be wrapped.

Request = request

The request message type. Default argument is request.

Response = response

The response message type. Default argument is response.

Message = request_response_wrapper<Request, Response>

The message type. Default argument is request_response_wrapper<Request, Response>.

4.2.21.2. Specializations
  • socket_adaptor<std::reference_wrapper<Socket>>

4.2.21.3. Member types
typedef Socket next_layer_type

The type of the wrapped socket.

4.2.21.4. Member functions
template<class…​ Args> socket_adaptor(Args&&…​ args)

Constructor. args are forwarded to the underlying socket constructor.

Note
Not available under the socket_adaptor<std::reference_wrapper<Socket>> specialization.
socket_adaptor(Socket &socket)

Constructor. socket is passed to the std::reference_wrapper constructor.

Note
Only available under the socket_adaptor<std::reference_wrapper<Socket>> specialization.
next_layer_type &next_layer()

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

const next_layer_type &next_layer() const

Socket isn’t exposed directly to avoid confusion over the duplication of interfaces.

The name socket is not used because both (the wrapped object and this object itself) are sockets and it would be confusing.

4.2.22. header_to_ptime

#include <boost/http/algorithm/header.hpp>
template<class StringView>
boost::posix_time::ptime header_to_ptime(const StringView &value)

Converts an HTTP-date [15] field value into boost::posix_time::ptime.

Note
Values containing extra whitespace at the beginning or at the end of value will be rejected and no conversion will be done. This behaviour is intentional.
4.2.22.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

4.2.22.2. Paremeters
const StringView &value

An HTTP-date.

4.2.22.3. Return value

The converted value if value is a valid HTTP-date or boost::posix_time::ptime(date_time::not_a_date_time) otherwise.

Tip
You can use ptime’s `is_not_a_date_time() member-function to check if the conversion failed.
4.2.22.4. See also

4.2.23. to_http_date

#include <boost/http/algorithm/header.hpp>
template<class String>
String to_http_date(const boost::posix_time::ptime &datetime)

Converts a boost::posix_time::ptime into the preferred string representation according to section 7.1.1.1 of RFC 7231 (i.e. fixed length/zone/capitalization subset of the format defined in section 3.3 of RFC 5322).

4.2.23.1. Template parameters
String

It MUST fulfill the requirements of the String concept (i.e. std::basic_string).

4.2.23.2. Parameters
const boost::posix_time::ptime &datetime

The timepoint to be converted. It MUST be in UTC timezone.

4.2.23.3. Return value

The string representation in the preferred format (a.k.a. IMF-fixdate).

4.2.23.4. Exceptions
  • std::out_of_range: If invalid datetime is given.

4.2.23.5. See also

4.2.24. header_value_all_of

#include <boost/http/algorithm/header.hpp>
template<class StringView, class Predicate>
bool header_value_all_of(const StringView &header_value, const Predicate &p)

Checks if unary predicate p returns true for all elements from the comma-separated list defined by the header_value HTTP field value.

Note
This algorithm is liberal in what it accepts and it will skip invalid elements. An invalid element is a sequence, possibly empty, containing no other character than optional white space (i.e. '\x20' or '\t').
4.2.24.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

Predicate

A type whose instances are callable and have the following signature:

bool(StringView)
4.2.24.2. Parameters
const StringView &header_value

The HTTP field value.

const Predicate &p

The functor predicate that will be called for the elements found on the comma-separated list.

Optional white space (only at the beginning and at the end) is trimmed before applying the element to p.

4.2.24.3. Return value

true if p doesn’t returns false for any element from the list and false otherwise. This also means that you’ll get the return value true for empty lists.

4.2.25. header_value_any_of

#include <boost/http/algorithm/header.hpp>
template<class StringView, class Predicate>
bool header_value_any_of(const StringView &header_value, const Predicate &p)

Checks if unary predicate p returns true for at least one element from the comma-separated list defined by the header_value HTTP field value.

Note
This algorithm is liberal in what it accepts and it will skip invalid elements. An invalid element is a sequence, possibly empty, containing no other character than optional white space (i.e. '\x20' or '\t').
4.2.25.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

Predicate

A type whose instances are callable and have the following signature:

bool(StringView)
4.2.25.2. Parameters
const StringView &header_value

The HTTP field value.

const Predicate &p

The functor predicate that will be called for the elements found on the comma-separated list.

Optional white space (only at the beginning and at the end) is trimmed before applying the element to p.

4.2.25.3. Return value

true if the p returns true for at least one element from the list and false otherwise. This also means that you’ll get the return value false for empty lists.

4.2.26. header_value_none_of

#include <boost/http/algorithm/header.hpp>
template<class StringView, class Predicate>
bool header_value_none_of(const StringView &header_value, const Predicate &p)

Checks if unary predicate p returns true for no elements from the comma-separated list defined by the header_value HTTP field value.

Note
This algorithm is liberal in what it accepts and it will skip invalid elements. An invalid element is a sequence, possibly empty, containing no other character than optional white space (i.e. '\x20' or '\t').
4.2.26.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

Predicate

A type whose instances are callable and have the following signature:

bool(StringView)
4.2.26.2. Parameters
const StringView &header_value

The HTTP field value.

const Predicate &p

The functor predicate that will be called for the elements found on the comma-separated list.

Optional white space (only at the beginning and at the end) is trimmed before applying the element to p.

4.2.26.3. Return value

true if p doesn’t returns true for any element from the list and false otherwise. This also means that you’ll get the return value true for empty lists.

4.2.27. header_value_for_each

#include <boost/http/algorithm/header.hpp>
template<class StringView, class F>
F header_value_for_each(const StringView &header_value, F f)

Apply f for each element from the comma-separated list defined by the header_value HTTP field value.

Note
This algorithm is liberal in what it accepts and it will skip invalid elements. An invalid element is a sequence, possibly empty, containing no other character than optional white space (i.e. '\x20' or '\t').
4.2.27.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

F

A type whose instances are callable and have the following signature:

void(StringView)
4.2.27.2. Parameters
const StringView &header_value

The HTTP field value.

F f

The functor that will be called for the elements found on the comma-separated list.

Optional white space (only at the beginning and at the end) is trimmed before applying the element to f.

4.2.27.3. Return value
std::move(f)

4.2.28. etag_match_strong

#include <boost/http/algorithm/header.hpp>
template<class StringView>
bool etag_match_strong(const StringView &a, const StringView &b)

Check if a and b match.

Note

This function doesn’t pedantically check if both arguments are actual entity tags and you should validate at least one of the arguments yourself.

Builtin validation is only done enough to protect against attacks.

4.2.28.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

4.2.28.2. Parameters
const StringView &a

The entity tag to compare against b.

const StringView &b

The entity tag to compare against a.

4.2.28.3. Return value

true if the entity tags match, using the strong comparison [16] or false otherwise.

4.2.29. etag_match_weak

#include <boost/http/algorithm/header.hpp>
template<class StringView>
bool etag_match_weak(const StringView &a, const StringView &b)

Check if a and b match.

Note

This function doesn’t pedantically check if both arguments are actual entity tags and you should validate at least one of the arguments yourself.

Builtin validation is only done enough to protect against attacks.

4.2.29.1. Template parameters
StringView

It MUST fulfill the requirements of the StringView concept (i.e. boost::basic_string_view).

4.2.29.2. Parameters
const StringView &a

The entity tag to compare against b.

const StringView &b

The entity tag to compare against a.

4.2.29.3. Return value

true if the entity tags match, using the weak comparison [17] or false otherwise.

4.2.30. request_continue_required

#include <boost/http/algorithm/query.hpp>
template<class Request>
bool request_continue_required(const Request &request)

Check if the request represented by request requires a “100 (Continue) response” [18].

If you can properly process and reply the message without its body, you’re free to go. Otherwise, you should send a “100 (Continue) response” to ask for the message body from the HTTP client.

This feature was designed to decrease network traffic, by allowing servers to sooner reject messages that would be discarded anyway.

The name required is used instead supported, because an action from the server is required.

4.2.30.1. Template parameters
Request

A type fulfilling the requirements for the Request concept.

4.2.30.2. Parameters
const Request &request

The read message.

4.2.30.3. Return value

Whether the request represented by request requires a “100 (Continue) response”.

4.2.31. request_upgrade_desired

#include <boost/http/algorithm/query.hpp>
template<class Request,
         class StringView = boost::basic_string_view<
             typename Request::headers_type::mapped_type::value_type>>
bool request_upgrade_desired(const Request &request)

Check if the client desires to initiate a protocol upgrade.

The desired protocols are present in the "upgrade" header as a comma-separated list.

Warning
You MUST NOT upgrade to a protocol listed in the "upgrade" header if this function returns false.

The upgrade desire can always be safely ignored.

The user MUST wait till the whole request is received before proceeding to the protocol upgrade.

4.2.31.1. Template parameters
Request

A type fulfilling the requirements for the Request concept.

StringView

A type fulfilling the requirements for the StringView concept (i.e. boost::basic_string_view).

4.2.31.2. Parameters
const Request &request

The read message.

4.2.31.3. Return value

Whether the client desires to initiate a protocol upgrade.

4.2.32. async_response_transmit_file

#include <boost/http/file_server.hpp>

This function has two overloads.

template<class ServerSocket, class Request, class Response,
         class CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
async_response_transmit_file(ServerSocket &socket, const Request &imessage,
                             Response &omessage,
                             const boost::filesystem::path &file,
                             CompletionToken &&token); // (1)
template<class ServerSocket, class Request, class Response,
         class CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
async_response_transmit_file(ServerSocket &socket, const Request &imessage,
                             Response &omessage,
                             const boost::filesystem::path &file,
                             bool is_head_request,
                             CompletionToken &&token); // (2)

This function will handle a big part of the file serving job for you, such as:

  • It’ll interpret and process the request headers. However, it doesn’t process the request method nor the the input path. This means you still have to guarantee the method is applicable and you’re also expected to resolve the input path to a valid local file path.

  • It’ll fill all the applicable response headers appropriate to the request.

  • It’ll interpret the file attributes to process conditional requests and partial download. However, MIME detection (the "content-type" optional header) is still left for the user to handle.

Summarizing your responsibilities:

  • Ensure it is a "GET" method or a method with similar semantics before calling this function. Overload 2 also accepts the "HEAD" method.

  • Resolve the input URL to the appropriate file.

  • Optional: MIME detection (the "content-type" header).

  • Optional: ETag detection (see below).

A method with semantics similar to the "GET" method is any method fulfilling the following conditions:

  • The body data payload for the response message is compatible.

  • The following headers for the request message have the same meaning:

    • "if-modified-since"

    • "if-range"

    • "if-unmodified-since"

    • "range"

  • The following headers for the response message have the same meaning or don’t affect the message processing:

    • "accept-ranges"

    • "content-range"

    • "content-type"

    • "date"

    • "last-modified"

  • The same set of status code for the response are applicable.

    • "200 OK"

    • "206 Partial Content"

    • "304 Not Modified"

    • "412 Precondition Failed"

    • "416 Range Not Satisfiable"

Note
This function will call the handler with file_server_errc::io_error if any operation on the file stream fails or throws an exception.
Note
The response headers filled by this function MUST NOT be sent through trailers. Therefore, this function will not do any operation and it will call the handler with an error_code (file_server_errc::write_state_not_supported) set if you pass a socket for which the headers were already sent.
Caution

async_response_transmit_file will make use of the streaming interface only if available.

If you want to avoid wasting memory under HTTP/1.0 and other non-streaming capable channels, starting points for two solutions are:

  • Reject channels without a streaming interface (e.g. HTTP/1.0). RFC 2068 (a.k.a. HTTP/1.1) has been available since January 1997.

  • Write a custom message type that adapts the filestream to the message concept and specialize the algorithm for such message.

Note
omessage.body() will be used as output buffer. If omessage.body().capacity() == 0, an unspecified buffer size will be used and it is very likely it’ll be highly inefficient.
4.2.32.1. ETags

An ETag is a string that identify a representation of a resource. The ETag can be used to perform conditional requests more robust than the ones done with dates (limited by HTTP to seconds-based precision). In the conditional request context, an etag is a validator.

If you want to make use of the ETag implementation, just set the "etag" header in the omessage object to the appropriate value, as described below (also described in more details in RFC7232).

The first decision you must do if you decide to provide an etag is if you’re going to provide a strong validator or a weak validator.

Strong validators

A strong validator changes whenever a change occurs to the representation data that would be observable in the payload body. A strong validator is unique across all versions of all representations associated with a particular resource over time.

A strong etag has the form (specified in Augmented Backus-Naur Form (ABNF) notation of [RFC5234]):

strong-etag = DQUOTE *etagc DQUOTE
etagc       = %x21 / %x23-7E / obs-text
            ; VCHAR except double quotes, plus obs-text
obs-text    = %x80-FF
Caution
Some recipients might perform backslash unescaping. Therefore, it is a good practice to avoid backslash characters.

Some examples:

  • "xyasdzzy"

  • "xyz9czy"

  • ""

Note
A strong validator might change for reasons other than a change to the representation data.
Note
There is no implication of uniqueness across representations of different resources.
Weak validators

A weak validator might not change for every change to the representation data.

A weak etag has the form (specified in Augmented Backus-Naur Form (ABNF) notation of [RFC5234]):

weak-etag  = weak opaque-tag
weak       = %x57.2F ; "W/", case-sensitive
opaque-tag = DQUOTE *etagc DQUOTE
etagc      = %x21 / %x23-7E / obs-text
           ; VCHAR except double quotes, plus obs-text
obs-text   = %x80-FF
Caution
Some recipients might perform backslash unescaping. Therefore, it is a good practice to avoid backslash characters.

Some examples:

  • W/"xyasdzzy"

  • W/"xyz9czy"

  • W/""

4.2.32.2. Template parameters
ServerSocket

Must fulfill the requirements for the ServerSocket concept.

Request

Must fulfill the requirements for the Request concept.

Response

Must fulfill the requirements for the Response concept.

Caution

Response::body_type MUST fulfill the following extra requirements:

  • Its elements MUST be stored contiguously (e.g. std::vector).

  • It MUST support C++11 std::vector capicity and data semantics (vector.capacity and vector.data, respectively).

These extra requirements are posed because file APIs are defined in terms of buffer [19] operations.

CompletionToken

Must fulfill the ASIO requirements for a completion token.

The used handler signature is void(boost::system::error_code).

4.2.32.3. Parameters
ServerSocket &socket

The socket associated with the imessage and omessage that will be used for the response.

const Request &imessage

The request message received.

Response &omessage

The message object that should be used to reply the message.

The user might be interested in filling some extra headers here like "content-type" or cache policies.

const filesystem::path &file

The requested file that should be transmitted.

Caution
If you cannot guarantee the file did not change twice during the second covered by the last write time, you should remove all "range" and "if-range" headers from imessage before calling this function. It’s possible to construct a more robust file server by making use of system-level APIs that can provide unique identifiers for file revisions.
bool is_head_request

Whether the request was made with a "HEAD" method.

If the received request isn’t "GET" nor "HEAD", you MAY remove all "range" and "if-range" headers and pass the value false to this argument.

Note
Available only for overload 2.
CompletionToken &&token

The token from which the handler and the return value are extracted.

The extracted handler is called when the operation completes.

4.2.32.4. Return value

Extracted using token.

4.2.33. async_response_transmit_dir

#include <boost/http/file_server.hpp>

This function has two overloads.

template<class ServerSocket, class ConvertibleToPath, class Request,
         class Response, class CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
async_response_transmit_dir(ServerSocket &socket,
                            const ConvertibleToPath &ipath,
                            const Request &imessage, Response &omessage,
                            const boost::filesystem::path &root_dir,
                            CompletionToken &&token); // (1)
template<class ServerSocket, class ConvertibleToPath, class Request,
         class Response, class Predicate, class CompletionToken>
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
async_response_transmit_dir(ServerSocket &socket,
                            const ConvertibleToPath &ipath,
                            const Request &imessage, Response &omessage,
                            const boost::filesystem::path &root_dir,
                            Predicate filter, CompletionToken &&token); // (2)

This function does a lot more than just sending bytes. It carries the responsibilities from async_response_transmit_file, but add a few more of its own:

  • It’ll also handle the HTTP method.

  • It’ll also handle the file resolution. It does so with respect to the given root_dir argument.

The only feature missing is mime support ("content-type" header). It cannot be done reliably within this abstraction.

This function will handle even the start line and the only acceptable write_state is empty. It’ll fail on any other write_state.

All urls are absolute, but are absolute with respect to the given root_dir. This interface provides no means to disable this security check. If the user needs that much complex logic, then it should write its own path resolving solution and use async_response_transmit_file.

Caution
This function will call the handler with file_server_category::file_not_found if the requested file, with respect to the given root_dir, cannot be found. The channel remains untouched in this case, giving the user the opportunity to send custom 404 messages.
Caution
This function will call the handler with file_server_category::file_type_not_supported if resolution finishes but this function cannot process the result because the file is not regular (directories, block devices…​). The channel is also left untouched, giving the user the opportunity to use another HTTP consumer.
4.2.33.1. Template parameters
ServerSocket

Must fulfill the requirements for the ServerSocket concept.

ConvertibleToPath

A type whose instances can be used to construct a boost::filesystem::path object.

Request

Must fulfill the requirements for the Request concept.

Response

Must fulfill the requirements for the Response concept.

Predicate

A type whose instances are callable and have the following signature:

bool(boost::filesystem::path &resolved_path)
CompletionToken

Must fulfill the ASIO requirements for a completion token.

The used handler signature is void(boost::system::error_code).

4.2.33.2. Parameters
ServerSocket &socket

The socket associated with the imessage and omessage that will be used for the response.

const ConvertibleToPath &ipath

ipath (standing for input path) is the parsed path from the requested url.

It’s guaranteed that it’ll only be used to construct a boost::filesystem::path object. Thus, the user can fake the requested path to force an internal redirect.

Warning
This is the input path component, not the input URL. Therefore, you MUST parse the input url before dispatching it to this function and only the path component must be forwarded. Extracting the path is an extra responsibility for the user, but it is an useful abstraction for scenarios where the user doesn’t control the served root dir. Thanks to the security check, this internal redirect trick doesn’t work for files outside the root_dir.
const Request &imessage

The request message received.

Note
Any request method is acceptable, but any method other than "GET" and "HEAD" will be responded with "405 Method Not Allowed".
Response &omessage

The message object that should be used to reply the message.

The user might be interested in filling some extra headers here like "content-type" or cache policies.

const boost::filesystem::path &root_dir

The dir to be interpreted as the root dir of requested files.

Predicate filter

It is applied to the resolved path as the last step before proceeding to file and network operations.

If filter returns false, the functions finishes before touching the channel, with the error_code file_server_category::filter_set.

Tip
It’s possible to use a stateful non-pure filter to add response headers and properly process mime types (content-type header).
Tip
filter can also be used to redirect files by modifying the input arg.
Note
This function might throw if filter throws. In this case, we provide the basic exception guarantee.
Note
Available only for overload 2.
CompletionToken &&token

The token from which the handler and the return value are extracted.

The extracted handler is called when the operation completes.

4.2.33.3. Return value

Extracted using token.

4.2.34. read_state

#include <boost/http/read_state.hpp>
enum class read_state

Represents the current state in the HTTP incoming request or HTTP incoming response.

Be prepared to face multiple state changes after a single action is scheduled (e.g. you issue read_message action and the state already changed to finished when the handler is invoked).

4.2.34.1. Member constants (incoming request)
read request state
Figure 2. Incoming request
empty

This is the initial state. It means that the request object wasn’t read yet.

At this state, you can only issue a read_request action.

message_ready

This state is reached from the empty state, once you ask for a new message.

No more read_request actions can be issued from this state.

From this state, you can issue the read_some action. The state will change to body_ready once all body was read. In streaming connections (e.g. HTTP/1.1 chunked entities), this condition (body fully received) might never happen.

Once this state is reached, you can safely use the read start line and the headers.

body_ready

This state is reached from the message_ready, once the http producer (e.g. embedded server) fully received the message body.

From this state, you can only issue the read_trailers action.

Once this state is reached, you can safely assume that no more body parts will be received.

finished

It means the message is complete and you can no longer issue another read_request until something else is done (e.g. send another http response). This is a different/special value, because the “something else to do” might not be related to read actions.

It can be reached from body_ready state, after all trailers have been received. It’s safe to assume that all message data is available at the time this state is reached.

4.2.34.2. Member constants (incoming response)
read response state
Figure 3. Incoming response
empty

This is the initial state.

There are two ways to interpret this state. It might mean that the response object wasn’t read yet.

Another interpretation is that it was reached from the body_ready state (directly — through a call to read_trailers — or indirectly — through a call to read_some or read_response), after all trailers have been received. It’s safe to assume that all message data is available if this is the case.

At this state, you can only issue a read_response action.

message_ready

This state is reached from the empty state, once you ask for a new message.

No more read_response actions can be issued from this state.

From this state, you can issue the read_some action. The state will change to body_ready once all body was read. In streaming connections (e.g. HTTP/1.1 chunked entities), this condition (body fully received) might never happen.

Once this state is reached, you can safely use the read start line and the headers.

body_ready

This state is reached from the message_ready, once the http producer (e.g. an http client) fully received the message body.

From this state, you can only issue the read_trailers action.

Once this state is reached, you can safely assume that no more body parts will be received.

finished (UNUSED)
Note
Only makes sense in server mode, when reading an incoming request. In client mode, empty target state is used instead.
4.2.34.3. See also

4.2.35. write_state

#include <boost/http/write_state.hpp>
enum class write_state

Represents the current state in the HTTP outgoing response or HTTP outgoing request.

4.2.35.1. Member constants (outgoing response)
write response state
Figure 4. Outgoing response
empty

This is the initial state.

It means that the response object hasn’t been sent yet.

At this state, you can only issue the metadata or issue a continue action, if continue is supported/used in this HTTP session. Even if continue was requested, issue a continue action is optional and only required if you need the request’s body.

continue_issued

This state is reached from the empty state, once you issue a continue action.

No more continue actions can be issued from this state.

metadata_issued

This state can be reached either from empty or continue_issued.

It happens when the metadata (start line + header section) is issued (through write_response_metadata).

From this state, you can only issue the body, the trailers or the end of the message.

finished

The message is considered complete once this state is reached.

You can no longer issue anything once this state is reached. The underlying channel will change the outgoing_state to empty once some unspecified event occurs. This event is usually a new request.

4.2.35.2. Member constants (outgoing request)
write request state
Figure 5. Outgoing request
empty

This is the initial state.

It means that the request object hasn’t been sent yet.

At this state, you can only issue the metadata.

continue_issued (UNUSED)
Note
Only makes sense in server mode, when sending an outgoing response.
metadata_issued

This state can be reached from empty.

It happens when the metadata (start line + header section) is issued (through write_request_metadata).

From this state, you can only issue the body, the trailers or the end of the message.

finished (UNUSED)
Note
Only makes sense in server mode, when sending an outgoing response. In client mode, empty target state is used instead.
4.2.35.3. See also

4.2.36. status_code

#include <boost/http/status_code.hpp>
enum class status_code: std::uint_fast16_t

This scoped enumeration defines the values for the “Hypertext Transfer Protocol (HTTP) Status Code Registry”.

4.2.36.1. Member constants
continue_request

100

switching_protocols

101

processing

102

ok

200

created

201

accepted

202

non_authoritative_information

203

no_content

204

reset_content

205

partial_content

206

multi_status

207

already_reported

208

im_used

226

multiple_choices

300

moved_permanently

301

found

302

see_other

303

not_modified

304

use_proxy

305

switch_proxy

306

Note
No longer used.
temporary_redirect

307

permanent_redirect

308

bad_request

400

unauthorized

401

payment_required

402

forbidden

403

not_found

404

method_not_allowed

405

not_acceptable

406

proxy_authentication_required

407

request_timeout

408

conflict

409

gone

410

length_required

411

precondition_failed

412

payload_too_large

413

uri_too_long

414

unsupported_media_type

415

requested_range_not_satisfiable

416

expectation_failed

417

unprocessable_entity

422

locked

423

failed_dependency

424

upgrade_required

426

precondition_required

428

too_many_requests

429

request_header_fields_too_large

431

internal_server_error

500

not_implemented

501

bad_gateway

502

service_unavailable

503

gateway_timeout

504

http_version_not_supported

505

variant_also_negotiates

506

insufficient_storage

507

loop_detected

508

not_extended

510

network_authentication_required

511

4.2.36.2. Non-member functions
bool operator==(status_code lhs, std::uint_fast16_t rhs)

Tests if lhs and rhs are equal.

bool operator==(std::uint_fast16_t lhs, status_code rhs)

Tests if lhs and rhs are equal.

template<class String> String to_string(status_code sc)

Returns the textual representation (i.e. the reason phrase) of sc.

4.2.37. http_errc

#include <boost/http/http_errc.hpp>
enum class http_errc

This scoped enumeration defines the values for the standard error codes reported by HTTP message producers and consumers. They are intended to be generic and usable by a variety of HTTP producers.

They are designed to work together http_category.

The traits boost::system::is_error_code_enum and boost::system::is_error_condition_enum are specialized to recognize http_errc.

4.2.37.1. Member constants
out_of_order

Actions issued on the wrong order by the library user.

Make sure to check the examples and the return value from socket.read_state() and socket.write_state().

native_stream_unsupported

The issued action can only be used when the underlying channel supports native stream, as defined in the Socket concept page.

Tip
If you’re using a type fulfilling the ServerSocket concept, you may be interested in the write_response_native_stream() member function.
parsing_error

The underlying communication channel sent an invalid message.

buffer_exhausted

This error should only happen if a poor parser is used.

wrong_direction

For flexible sockets that select the channel type upon the first use. It happens if you started the socket operations behaving like an HTTP client and later started to behave as an HTTP server, or vice versa, on the same channel.

4.2.37.2. Non-member functions
boost::system::error_code make_error_code(http_errc e)

Creates an error code using e and http_category.

boost::system::error_condition make_error_condition(http_errc e)

Creates an error codition using e and http_category.

4.2.37.3. http_category
const boost::system::error_category& http_category()

Obtains a reference to the static error category object for HTTP errors. The object overrides the member function name to return "http" and overrides message to support all values from http_errc.

4.2.38. file_server_errc

#include <boost/http/file_server.hpp>
enum class file_server_errc

This scoped enumeration defines the values for the error codes reported by HTTP file server abstraction shipped with this library.

They’re designed to work with file_server_category.

The traits boost::system::is_error_code_enum and boost::system::is_error_condition_enum are specialized to recognize file_server_errc.

4.2.38.1. Member constants
io_error

When any operation on the file stream fails or throws an exception.

Note
It’s guaranteed that no operations on the underlying socket were done when this error happens.
irrecoverable_io_error

When any operation on the file stream fails or throws an exception AFTER some operation on the underlying socket already was issued.

write_state_not_supported

If some write operation already was issued before the call to the function that raised this error code.

file_not_found

The requested file wasn’t found. The channel is left untouched to give the user the opportunity to send a custom “404 response” or to further forward the request.

file_type_not_supported

The requested file was found but it is not regular (e.g. directories, block devices, links…​). Channel remains untouched.

filter_set

The user provided filter predicate returned false to cancel the operation. Channel remains untouched.

4.2.38.2. Non-member functions
boost::system::error_code make_error_code(file_server_errc e)

Creates an error code using e and file_server_category

boost::system::error_condition make_error_condition(file_server_errc e)

Creates an error code using e and file_server_category

4.2.38.3. file_server_category
const boost::system::error_category& file_server_category();

Obtains a reference to the static error category object for the file server errors. The object overrides the member function name to return "file_server" and overrides message to support all values from file_server_errc.

4.2.39. Message

A container able to hold generic HTTP messages.

4.2.39.1. Definitions
HTTP field name

A string encoded with the ISO-8859-1 charset whose contents are limited to the chars listed below (case-sensitive):

  • A digit (i.e. '0', '1', '2', '3', '4', '5', '6', '7', '8' or '9').

  • A lowercase alphabetic (i.e. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y' or 'z').

  • A few special characters: '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', backtick (i.e. '\x60'), '|' or '~'.

    Note
    Any uppercase character received through the wire MUST be normalized (i.e. converted to lowercase).
HTTP field value

A string encoded with the ISO-8859-1 charset whose contents are limited to the chars listed below (whether they’re case-sensitive or not is defined on a header-by-header basis and, as such, they all are considered case-sensitive in this layer of abstraction):

  • Any visible USASCII character.

  • Any character in the closed interval (i.e. both ends are inclusive) between '\x80' and '\xFF'. The use of these characters within the HTTP field value is obsolete and should be avoided.

  • Space (i.e. '\x20') and horizontal tab (i.e. '\t'). These characters are not allowed in the beginning or in the end of the HTTP field value.

HTTP field

A pair whose first element is an HTTP field name and second element is an HTTP field value.

HTTP header section

A set of HTTP fields received in the same chunk (e.g. the HTTP header section defined in the RFC 7230).

Note
For fields with equivalent field names, the relative order is preserved.
4.2.39.2. Notation
X

A type that is a model of Message.

Headers

A type fulfilling the following requirements:

  • The C++11 concept of associative containers (associative.reqmts) [20].

  • It supports equivalent keys.

  • Value type is equal to pair<const Key, T>.

  • mapped_type is available with the same semantics for multimap.

  • Headers::key_type MUST fulfill the requirements for the String concept (i.e. std::basic_string).

    Headers::key_type::value_type MUST be able to represent all values in the ISO-8859-1 charset except for the upper case versions of the alphabetic characters.

    Warning
    Inserting elements in Headers instances whose keys contains uppercase char(s) invoke undefined behaviour.
  • Headers::mapped_type MUST fulfill the requirements for the String concept (i.e. std::basic_string).

    Headers::mapped_type::value_type MUST be able to represent all values in the ISO-8859-1 charset.

Body

A type fulfilling the C++ concept of sequence containers (sequence.reqmts) whose value_type can represent byte octets.

a

Object of type X.

ca

Object of type const X.

4.2.39.3. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_message<X>>

std::true_type

X::headers_type

Headers

X::body_type

Body

a.headers()

X::headers_type&

  • It should be used to represent the HTTP header section received/to-be-sent before the HTTP body data payload.

  • Always returns a reference to the same object.

  • The returned object MUST NOT be shared with the one returned by a.trailers().

ca.headers()

const X::headers_type&

  • It should be used to represent the HTTP header section received/to-be-sent before the HTTP body data payload.

  • Always returns a reference to the same object.

  • The returned object MUST NOT be shared with the one returned by a.trailers().

a.body()

X::body_type&

  • It should be used to represent chunks of the HTTP body data payload.

  • Always returns a reference to the same object.

ca.body()

const X::body_type&

  • It should be used to represent chunks of the HTTP body data payload.

  • Always returns a reference to the same object.

a.trailers()

X::headers_type&

  • It should be used to represent the HTTP header section received/to-be-sent after the HTTP body data payload.

  • Always returns a reference to the same object.

  • The returned object MUST NOT be shared with the one returned by a.headers().

ca.trailers()

const X::headers_type&

  • It should be used to represent the HTTP header section received/to-be-sent after the HTTP body data payload.

  • Always returns a reference to the same object.

  • The returned object MUST NOT be shared with the one returned by a.headers().

  1. Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.

4.2.39.4. See also

4.2.40. Request

A container able to hold HTTP request messages.

4.2.40.1. Refinement of
4.2.40.2. Notation
X

A type that is a model of Request.

String

A type that is a model of C++'s String.

a

Object of type X.

ca

Object of type const X.

4.2.40.3. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_request_message<X>>

std::true_type

X::string_type

String

a.method()

X::string_type&

  • It should be used to represent the HTTP request method.

  • Always returns a reference to the same object.

ca.method()

const X::string_type&

  • It should be used to represent the HTTP request method.

  • Always returns a reference to the same object.

a.target()

X::string_type&

  • It should be used to represent the HTTP request target.

  • Always returns a reference to the same object.

ca.target()

const X::string_type&

  • It should be used to represent the HTTP request target.

  • Always returns a reference to the same object.

4.2.40.4. Models

4.2.41. Response

A container able to hold HTTP response messages.

4.2.41.1. Refinement of
4.2.41.2. Notation
X

A type that is a model of Response.

String

A type that is a model of C++'s String.

a

Object of type X.

ca

Object of type const X.

4.2.41.3. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_response_message<X>>

std::true_type

X::string_type

String

a.status_code()

std::uint_least16_t&

  • It should be used to represent the HTTP response status code.

  • Always returns a reference to the same object.

ca.status_code()

const std::uint_least16_t&

  • It should be used to represent the HTTP response status code.

  • Always returns a reference to the same object.

a.reason_phrase()

X::string_type&

  • It should be used to represent the HTTP response reason phrase.

  • Always returns a reference to the same object.

ca.reason_phrase()

const X::string_type&

  • It should be used to represent the HTTP response reason phrase.

  • Always returns a reference to the same object.

4.2.41.4. Models

4.2.42. Socket

Common operations between request and response that the underlying channel should provide.

4.2.42.1. Definitions
Types fulfilling the Socket concept and also possessing the strict property

Instances from this type will not insert illegal characters [21] in the HTTP fields.

Native stream

The message size doesn’t need to be know prior to writing the response message. The use of the streaming API can only be used when this property holds. Buffering the body MUST NOT be done and the message parts MUST be written as soon as convenient. If this property holds, the user MAY safely use the socket to transmit a live video stream, for instance.

Message metadata

The HTTP status line plus the header section.

Atomic message

A message which is issued and fully know with a single API call.

Every message is either atomic or chunked.

Chunked message

A message which is issued among several API calls.

Every message is either atomic or chunked.

Chunked messages are useful if you don’t know the message in advance (e.g. video streaming). These messages can only be used if native stream is supported.

You always need to be prepared to receive chunked messages and it’s only useful to differ operations which only apply to atomic messages or chunked messages when you’re about to send a message.

4.2.42.2. Notation
Message

A type fulfilling the requirements for the Message concept.

m

Object of type Message.

cm

Object of type const Message.

CompletionToken

A type fulfilling the concept of a completion token, as defined in N4045: Library Foundations for Asynchronous Operations, Revision 2.

AsyncResultType
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
token

An object of the type CompletionToken.

X

A type that is a model of Socket.

a

Object of type X.

ca

Object of type const X.

4.2.42.3. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_socket<X>>

std::true_type

X::executor_type

The type of the executor associated with the object.

a.get_executor()

X::executor_type

Returns the executor associated with the object.

X::message_type

Socket only supports operations involving a single type of message.

Must be a type fulfilling the Message concept.

ca.is_open()

bool

Determine whether the socket is open.

ca.read_state()

read_state

Returns the state associated with the Socket meaningful for reading operations.

ca.write_state()

write_state

Returns the state associated with the Socket meaningful for writing operations.

a.async_read_some(m, token)

AsyncResultType

a.read_state() == read_state::message_ready

Initiate an asynchronous operation to read a part of the message body. Handler is called when at least one byte is read (called with no error set), when the end of message is reached (called with no error set) or when some error occurs.

m.body() and m.trailers() are left in an unspecified state while the operation is in progress.

By the time the handler is called, the part of the message read (if any) is appended to m.body(), if no error happened.

a.async_read_some(m, token)

AsyncResultType

a.read_state() != read_state::message_ready

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_read_trailers(m, token)

AsyncResultType

a.read_state() == read_state::body_ready

Initiate an asynchronous operation to read the trailers. Handler is called when the rest of the message is fully received (called with no error set) or when some error occurs.

m.trailers() is left in an unspecified state while the operation is in progress.

By the time the handler is called, if no error happened, the read trailers (if any) are inserted into m.trailers().

a.async_read_trailers(m, token)

AsyncResultType

a.read_state() != read_state::body_ready

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_write(cm, token)

AsyncResultType

a.write_state() == write_state ::metadata_issued

Initiate an asynchronous operation to write a chunk of the HTTP body data payload (chunked message). Handler is called when the operation completes with an appropriate parameter.

cm.body() MUST NOT be modified while the operation is in progress.

By the time the handler is called, the cm.body() data is considered delivered, if no error happened.

a.async_write(cm, token)

AsyncResultType

a.write_state() != write_state ::metadata_issued

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_write_trailers(cm, token)

AsyncResultType

a.write_state() == write_state ::metadata_issued

Initiate an asynchronous operation to write the trailer part of the message (chunked message). Handler is called when the operation completes with an appropriate parameter.

cm.trailers() MUST NOT be modified while the operation is in progress.

By the time the operation completes, the cm.trailers() data is considered delivered, if no error happened.

a.async_write_trailers(cm, token)

AsyncResultType

a.write_state() != write_state ::metadata_issued

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_write_end_of_message (token)

AsyncResultType

a.write_state() == write_state ::metadata_issued

Initiate an asynchronous operation to signalize the sent message is complete (chunked message). Handler is called when the operation completes with an appropriate parameter.

By the time the operation completes, the message is considered complete, if no error happened.

a.async_write_end_of_message (token)

AsyncResultType

a.write_state() != write_state ::metadata_issued

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

  1. Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.

  2. Any HTTP field name received through the wire is normalized (i.e. uppercase characters are converted to lowercase) before they’re inserted into objects of type Message::headers_type.

  3. The Socket object has the freedom to store information required to further process the incoming message in the user-provided message object. Thus, the library user MUST NOT use different message objects in the functions that initiate read operations, in the context of the same message exchange (i.e. the user can use a different message object to receive a different message). This requirement is extended to refinements of this concept.

  4. The Socket object MUST NOT insert HTTP headers with empty keys (i.e. "") in message, request or response objects provided by the user.

  5. You MUST NOT write messages with the "transfer-encoding: chunked" header.

  6. You MUST NOT write atomic messages with the "transfer-encoding" header.

4.2.42.5. See also

4.2.43. ServerSocket

Provides operations for HTTP servers.

4.2.43.1. Refinement of
4.2.43.2. Definitions
Types fulfilling the ServerSocket concept and also possessing the strict property

Instances from this type will not insert illegal characters [22] in the HTTP fields.

4.2.43.3. Notation
Request

A type fulfilling the requirements for the Request concept.

Response

A type fulfilling the requirements for the Response concept.

im

Object of type Request.

om

Object of type const Response.

CompletionToken

A type fulfilling the concept of a completion token, as defined in N4045: Library Foundations for Asynchronous Operations, Revision 2.

AsyncResultType
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
token

An object of the type CompletionToken.

X

A type that is a model of ServerSocket.

a

Object of type X.

ca

Object of type const X.

4.2.43.4. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_server_socket<X>>

std::true_type

X::request_type

ServerSocket only supports operations involving a single type of request message.

Must be a type fulfilling the requirements for the Request concept.

X::response_type

ServerSocket only supports operations involving a single type of response message.

Must be a type fulfilling the requirements for the Response concept.

ca.write_response_native_stream()

bool

a.read_state() != read_state::empty

Returns whether the current message exchange supports native stream.

The same value and property is maintained until the end of the current message exchange.

a.async_read_request(im, token)

AsyncResultType

a.read_state() == read_state::empty

Initiate an asynchronous operation to read enough of the message to apply the request to a target resource (i.e. request line plus the header section). Handler is called when the operation completes with an appropriate parameter.

im is left in a unspecified state while the operation is in progress.

The ServerSocket object MUST prevent the user from issuing new replies while the request isn’t ready. The prevention MUST be done by changing the write state to write_state::finished while the read_request operation is in progress.

By the time the handler is called, im.method() value represents the read method, im.target() represents the read url and all headers for the current request are inserted into m.headers(), if no error happened.

a.async_read_request(im, token)

AsyncResultType

a.read_state() != read_state::empty

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_write_response(om, token)

AsyncResultType

a.write_state() == write_state::empty || a.write_state() == write_state::continue_issued

Initiate an asynchronous operation to write the response message (atomic message). Handler is called with an appropriate argument when the operation completes.

om MUST NOT be modified while the operation is in progress.

  • a.write_state() == write_state ::finished

  • a.read_state() == read_state ::empty

  • By the time the handler is called, the om message is considered delivered, if no error happened.

a.async_write_response(om, token)

AsyncResultType

a.write_state() != write_state::empty && a.write_state() != write_state::continue_issued

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_write_response_continue (token)

AsyncResultType

a.write_state() == write_state::empty

Initiate an asynchronous operation to write a response with the semantics from a “100 (Continue) response” [23]. Handler is called when the operation completes with an appropriate parameter.

  • a.write_state() == write_state ::continue_issued

  • By the time the handler is called, the “100 (Continue) response” is considered delivered.

a.async_write_response_continue (token)

AsyncResultType

a.write_state() != write_state::empty

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a .async_write_response_metadata(om, token)

AsyncResultType

(a.write_state() == write_state::empty || a.write_state() == write_state::continue_issued) && a.write_response_native_stream() == true

Initiate an asynchronous operation to write the response metadata (chunked message). Handler is called with an appropriate argument when the operation completes.

om MUST NOT be modified while the operation is in progress.

  • a.write_state() == write_state ::metadata_issued

  • By the time the handler is called, the response metadata (i.e. om.status_code(), om.reason_phrase() and cm.headers()) is considered delivered, if no error happened.

a .async_write_response_metadata(om, token)

AsyncResultType

a.write_state() != write_state::empty && a.write_state() != write_state::continue_issued

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order} [24].

a .async_write_response_metadata(om, token)

AsyncResultType

(a.write_state() == write_state::empty || a.write_state() == write_state::continue_issued) && a.write_response_native_stream() == false

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc ::native_stream_unsupported}.

The following Socket operations are refined with extra semantics/postconditions:

Expression Precondition Extra semantics Extra postcondition

a.async_read_trailers(m, token)

a.read_state() == read_state::body_ready

By the time the handler is called, if no error happened, a.read_state() == http::read_state::finished.

a.async_write_trailers(cm, token)

a.write_state() == write_state ::metadata_issued

  • a.write_state() == write_state::finished

  • a.read_state() == read_state ::empty

a.async_write_end_of_message (token)

a.write_state() == write_state ::metadata_issued

  • a.write_state() == write_state::finished

  • a.read_state() == read_state ::empty

  1. Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.

  2. Any HTTP field name received through the wire is normalized (i.e. uppercase characters are converted to lowercase) before they’re inserted into objects of type Request::headers_type.

  3. If the user pass a "connection: close" header on the message object passed as argument to the async_write_response or async_write_response_metadata member-functions, the ServerSocket MUST change the state to closed (i.e. is_open() will return false).

    This behaviour is intended for the communication between the user of this library and the ServerSocket and can differ from the communication between the ServerSocket and the underlying channel.

  4. If the ServerSocket reads a message that expects a “100 (Continue) response”, it MUST insert the "expect: 100-continue" header and only one element with the HTTP field name "expect" MUST be present.

    This behaviour is intended for the communication between the user of this library and the ServerSocket and can differ from the communication between the ServerSocket and the underlying channel.

  5. If the ServerSocket reads a message that does NOT expect a “100 (Continue) response”, it MUST erase all the "expect: 100-continue" headers.

    This behaviour is intended for the communication between the user of this library and the ServerSocket and can differ from the communication between the ServerSocket and the underlying channel.

  6. If the ServerSocket reads a message that represent a desire from the HTTP client to initiate a protocol upgrade, the ServerSocket supports a protocol upgrade and it’ll communicate the client desire to the user of this library, it MUST communicate the desire ensuring all of the following conditions:

    • Ensuring that the "upgrade" (case-insensitive) string is present in the comma-separated list of values from some "connection" header. This rule implictly requires the presence of at least one "connection" header.

    • There is at least one "upgrade" header and all of the "upgrade" headers respect the conditions established in the section 6.7 of the RFC7230.

    This behaviour is intended for the communication between the user of this library and the ServerSocket and can differ from the communication between the ServerSocket and the underlying channel.

  7. If the ServerSocket isn’t willing to provide a protocol upgrade, then no "upgrade" headers can be present (in other words, all "upgrade" headers MUST be erased before delivering the message to the user of this library).

    This behaviour is intended for the communication between the user of this library and the ServerSocket and can differ from the communication between the ServerSocket and the underlying channel.

  8. If the "content-length" header is provided to async_write_response, then the ServerSocket MUST ignore the message body (i.e. there is no data payload in the reply message) and SHOULD use the user-provided header.

    The ServerSocket MUST adopt a behaviour that is compatible with the behaviour defined in the section 3.3.2 of the RFC 7230.

  9. The ServerSocket object MUST NOT insert HTTP headers with empty keys (i.e. "") in message, request or response objects provided by the user.

  10. Informational responses (i.e. 1xx class of status code) indicates an interim response and do not change read_state or write_state. Also, these responses carry no body, so all body is ignored (and if the user tries to send such responses using chunks/async_write_response_metadata, error http_errc::native_stream_unsupported will be reported).

  11. Some models of ServerSocket might discard informational responses (i.e. 1xx class of status code) that you try to send.

4.2.44. ClientSocket

Provides operations for HTTP clients.

4.2.44.1. Refinement of
4.2.44.2. Definitions
Types fulfilling the ClientSocket concept and also possessing the strict property

Instances from this type will not insert illegal characters [25] in the HTTP fields.

4.2.44.3. Notation
Request

A type fulfilling the requirements for the Request concept.

Response

A type fulfilling the requirements for the Response concept.

im

Object of type Response.

om

Object of type const Request.

CompletionToken

A type fulfilling the concept of a completion token, as defined in N4045: Library Foundations for Asynchronous Operations, Revision 2.

AsyncResultType
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::system::error_code))
token

An object of the type CompletionToken.

X

A type that is a model of ClientSocket.

a

Object of type X.

ca

Object of type const X.

4.2.44.4. Requirements
Expression Return type Precondition Semantics Postcondition

std::is_base_of<std::true_type, http::is_client_socket<X>>

std::true_type

X::request_type

ClientSocket only supports operations involving a single type of request message.

Must be a type fulfilling the requirements for the Request concept.

X::response_type

ClientSocket only supports operations involving a single type of response message.

Must be a type fulfilling the requirements for the Response concept.

a.async_write_request(om, token)

AsyncResultType

a.write_state() == write_state::empty

Initiate an asynchronous operation to write the request message (atomic message). Handler is called with an appropriate argument when the operation completes.

om MUST NOT be modified while the operation is in progress.

By the time the handler is called, the om message is considered delivered, if no error happened.

a.async_write_request(om, token)

AsyncResultType

a.write_state() != write_state::empty

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a .async_write_request_metadata(om, token)

AsyncResultType

a.write_state() == write_state::empty

Initiate an asynchronous operation to write the request metadata (chunked message). Handler is called with an appropriate argument when the operation completes.

om MUST NOT be modified while the operation is in progress.

  • a.write_state() == write_state ::metadata_issued

  • By the time the handler is called, the request metadata (i.e. om.method(), om.target() and cm.headers()) is considered delivered, if no error happened.

a .async_write_request_metadata(om, token)

AsyncResultType

a.write_state() != write_state::empty

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

a.async_read_response(im, token)

AsyncResultType

a.read_state() == read_state::empty

Initiate an asynchronous operation to read enough of the message to fill the response metadata (i.e. status line plus the header section). Handler is called when the operation completes with an appropriate parameter.

im is left in a unspecified state while the operation is in progress.

By the time the handler is called, im.status_code() value represents the read status code, im.reason_phrase() represents the read reason phrase and all headers for the current response are inserted into im.headers(), if no error happened.

a.async_read_response(im, token)

AsyncResultType

a.read_state() != read_state::empty

No actions are done and the handler from the completion token is called with boost::system::error_code {http_errc::out_of_order}.

The following Socket operations are refined with extra semantics/postconditions:

Expression Precondition Extra semantics Extra postcondition

a.async_read_trailers(m, token)

a.read_state() == read_state::body_ready

By the time the handler is called, if no error happened, a.read_state() == http::read_state::empty.

a.async_write_trailers(cm, token)

a.write_state() == write_state ::metadata_issued

a.write_state() == write_state::empty

a.async_write_end_of_message (token)

a.write_state() == write_state ::metadata_issued

a.write_state() == write_state::empty

  1. Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.

  2. Any HTTP field name received through the wire is normalized (i.e. uppercase characters are converted to lowercase) before they’re inserted into objects of type Response::headers_type.

  3. Upon receiving a message (i.e. async_read_response, async_read_some or async_read_trailers), if connection is gracefully closed in the HTTP-equivalent layer (e.g. "connection: close" header), ClientSocket MUST change the state to closed (i.e. is_open() will return false).

    This behaviour is intended for the communication between the user of this library and the ClientSocket and can differ from the communication between the ClientSocket and the underlying channel.

  4. The user communicates the intent to wait for a “100 (Continue) response” by inserting the "expect: 100-continue" header (and more than one element with the HTTP field name "expect" MUST NOT be present in the sent request metadata).

    This behaviour is intended for the communication between the user of this library and the ClientSocket and can differ from the communication between the ClientSocket and the underlying channel. For instance, if the ClientSocket doesn’t intend to implement such semantics, it can omit this header from the message sent to the underlying channel and fill a “100 (Continue) response” in the next call the user does to read_response whether this response was received from the underlying channel or not (i.e. fill a virtual response).

  5. If the ClientSocket isn’t willing to provide support for protocol upgrade, then no "upgrade" headers should be sent (in other words, all "upgrade" headers MUST be skipped before delivering the message from the user of this library — or a behaviour that is equivalent in the underlying channel).

    This behaviour is intended for the communication between the user of this library and the ClientSocket and can differ from the communication between the ClientSocket and the underlying channel.

  6. The ClientSocket object MUST NOT insert HTTP headers with empty keys (i.e. "") in message, request or response objects provided by the user.

  7. The user of this library MUST NOT insert "content-length" or "transfer-encoding" headers in om.

4.2.45. <boost/http/algorithm.hpp>

A shorthand to write:

#include <boost/http/algorithm/header.hpp>
#include <boost/http/algorithm/query.hpp>

4.2.47. <boost/http/algorithm/query.hpp>

Import the following symbols:

4.2.48. <boost/http/file_server.hpp>

Import the following symbols:

4.2.49. <boost/http/headers.hpp>

Import the following symbol:

4.2.50. <boost/http/http_category.hpp>

Import the http_category symbol documented at http_errc page.

4.2.51. <boost/http/http_errc.hpp>

Import all the symbols documented at the http_errc page.

4.2.52. <boost/http/request.hpp>

Import the following symbols:

4.2.53. <boost/http/response.hpp>

Import the following symbols:

4.2.54. <boost/http/request_response_wrapper.hpp>

Import the following symbols:

4.2.56. <boost/http/poly_server_socket.hpp>

Import the following symbols:

4.2.57. <boost/http/poly_client_socket.hpp>

Import the following symbols:

4.2.58. <boost/http/poly_socket_base.hpp>

Import the following symbols:

4.2.59. <boost/http/read_state.hpp>

Import the following symbol:

4.2.60. <boost/http/server_socket_adaptor.hpp>

Import the following symbols:

4.2.61. <boost/http/client_socket_adaptor.hpp>

Import the following symbols:

4.2.62. <boost/http/socket_adaptor.hpp>

Import the following symbols:

4.2.63. <boost/http/socket.hpp>

Import the following symbols:

4.2.64. <boost/http/buffered_socket.hpp>

Import the following symbols:

4.2.65. <boost/http/status_code.hpp>

Import the following symbol:

4.2.66. <boost/http/write_state.hpp>

Import the following symbol:

4.2.67. <boost/http/traits.hpp>

Import the following symbols:

4.2.68. <boost/http/basic_router.hpp>

Import the following symbols:

4.2.69. <boost/http/regex_router.hpp>

Import the following symbols:

4.2.70. is_message

#include <boost/http/traits.hpp>

If T is an object fulfilling the Message concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving Message objects.

Initially, it was considered to create a trait that would automatically detect if T is fullfilling the Message concept, but the idea was abandoned, because the Message concept includes behaviour that can only be detected at runtime.

The default definition follows:

template<class T>
struct is_message
    : public std::integral_constant<bool,
                                    is_request_message<T>::value
                                    || is_response_message<T>::value>
{};
4.2.70.1. Template parameters
T

The type to query.

4.2.71. is_request_message

#include <boost/http/traits.hpp>

If T is an object fulfilling the Request concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving Request objects.

4.2.71.1. Template parameters
T

The type to query.

4.2.72. is_response_message

#include <boost/http/traits.hpp>

If T is an object fulfilling the Response concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving Response objects.

4.2.72.1. Template parameters
T

The type to query.

4.2.73. is_socket

#include <boost/http/traits.hpp>

If T is an object fulfilling the Socket concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving Socket objects. If your user-defined type already specializes is_server_socket or is_client_socket, there is no need to also specialize this template, because this template will, by default, inherit std::true_type if is_server_socket<T>::value || is_client_socket<T>::value evaluates to true.

Initially, it was considered to create a trait that would automatically detect if T is fullfilling the Socket concept, but the idea was abandoned, because the Socket concept includes behaviour that can only be detected at runtime.

4.2.73.1. Template parameters
T

The type to query.

4.2.73.2. See also

4.2.74. is_server_socket

#include <boost/http/traits.hpp>

If T is an object fulfilling the ServerSocket concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving ServerSocket objects.

Initially, it was considered to create a trait that would automatically detect if T is fullfilling the ServerSocket concept, but the idea was abandoned, because the ServerSocket concept includes behaviour that can only be detected at runtime.

4.2.74.1. Template parameters
T

The type to query.

4.2.74.2. See also

4.2.75. is_client_socket

#include <boost/http/traits.hpp>

If T is an object fulfilling the ClientSocket concept, this template inherits std::true_type. For any other type, this template inherits std::false_type.

This template may be specialized for a user-defined type to indicate that the type is eligible for operations involving ClientSocket objects.

Initially, it was considered to create a trait that would automatically detect if T is fullfilling the ClientSocket concept, but the idea was abandoned, because the ClientSocket concept includes behaviour that can only be detected at runtime.

4.2.75.1. Template parameters
T

The type to query.

4.2.75.2. See also

4.2.76. basic_router

#include <boost/http/basic_router.hpp>

Router based on simple test functions. It is implemented as a vector of pairs<test,router>. Where test is a functor that tests a specific path received by a connection, and if returns ture, calls the router function with an arbitary number of arguments.

A test functor could be as simple as bool test_path(string path) { return path.empty(); }.

The router is an arbitary function, that must match the call to the router itself. So if we call the route as basic_router(path, arg1, arg2) the router function will be called with router(arg1, arg2).

4.2.76.1. Template parameters
unary_predicate

Functor that recieves std::string path as an argument and returns true if this route should be selected.

route_function_type

Functor of the route destination function.

typename…​ arguments

List of argument type to be passed onto the route destination function.

4.2.76.2. See also

4.2.77. regex_router

#include <boost/http/regex_router.hpp>

Router based on regular expressions. It is implemented as a vector of pairs<regex,router>. Where regex is a std::regex that tests a specific path received by a connection, and if matches, calls the router function with an arbitary number of arguments.

The router is an arbitary function, that must match the call to the router itself. So if we call the route as basic_router(path, arg1, arg2) the router function will be called with router(arg1, arg2).

4.2.77.1. Template parameters
route_function_type

Functor of the route destination function.

typename…​ arguments

List of argument type to be passed onto the route destination function.

4.2.77.2. See also

4.2.78. token::code::value

#include <boost/http/token.hpp>
namespace token {

struct code
{
    enum value
    {
        error_insufficient_data,
        error_set_method,
        error_use_another_connection,
        error_invalid_data,
        error_no_host,
        error_invalid_content_length,
        error_content_length_overflow,
        error_invalid_transfer_encoding,
        error_chunk_size_overflow,
        skip,
        method,
        request_target,
        version,
        status_code,
        reason_phrase,
        field_name,
        field_value,
        end_of_headers,
        body_chunk,
        end_of_body,
        trailer_name,
        trailer_value,
        end_of_message
    };
};

} // namespace token
error_insufficient_data

token_size() of this token will always be zero.

4.2.79. token::symbol::value

#include <boost/http/token.hpp>
namespace token {

struct symbol
{
    enum value
    {
        error,

        skip,

        method,
        request_target,
        version,
        status_code,
        reason_phrase,
        field_name,
        field_value,

        end_of_headers,

        body_chunk,

        end_of_body,

        trailer_name,
        trailer_value,

        end_of_message
    };

    static value convert(code::value);
};

} // namespace token

4.2.80. token::category::value

#include <boost/http/token.hpp>
namespace token {

struct category
{
    enum value
    {
        status,
        data,
        structural
    };

    static value convert(code::value);
    static value convert(symbol::value);
};

} // namespace token

4.2.81. token::skip

#include <boost/http/token.hpp>
namespace token {

struct skip
{
    static const token::code::value code = token::code::skip;
};

} // namespace token

Used to skip unneeded bytes so user can keep buffer small when asking for more data.

4.2.82. token::field_name

#include <boost/http/token.hpp>
namespace token {

struct field_name
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::field_name;
};

} // namespace token

4.2.83. token::field_value

#include <boost/http/token.hpp>
namespace token {

struct field_value
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::field_value;
};

} // namespace token

4.2.84. token::body_chunk

#include <boost/http/token.hpp>
namespace token {

struct body_chunk
{
    typedef asio::const_buffer type;
    static const token::code::value code = token::code::body_chunk;
};

} // namespace token

4.2.85. token::end_of_headers

#include <boost/http/token.hpp>
namespace token {

struct end_of_headers
{
    static const token::code::value code = token::code::end_of_headers;
};

} // namespace token

4.2.86. token::end_of_body

#include <boost/http/token.hpp>
namespace token {

struct end_of_body
{
    static const token::code::value code = token::code::end_of_body;
};

} // namespace token

4.2.87. token::trailer_name

#include <boost/http/token.hpp>
namespace token {

struct trailer_name
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::trailer_name;
};

} // namespace token
Note

This token is “implicitly convertible” to field_name, so to speak. In other words, you can treat it as field_name at value extraction time (i.e. the reader::{request,response}::value<T>() function).

4.2.88. token::trailer_value

#include <boost/http/token.hpp>
namespace token {

struct trailer_value
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::trailer_value;
};

} // namespace token
Note

This token is “implicitly convertible” to field_value, so to speak. In other words, you can treat it as field_value at value extraction time (i.e. the reader::{request,response}::value<T>() function).

4.2.89. token::end_of_message

#include <boost/http/token.hpp>
namespace token {

struct end_of_message
{
    static const token::code::value code = token::code::end_of_message;
};

} // namespace token

4.2.90. token::method

#include <boost/http/token.hpp>
namespace token {

struct method
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::method;
};

} // namespace token

4.2.91. token::request_target

#include <boost/http/token.hpp>
namespace token {

struct request_target
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::request_target;
};

} // namespace token

4.2.92. token::version

#include <boost/http/token.hpp>
namespace token {

struct version
{
    typedef int type;
    static const token::code::value code = token::code::version;
};

} // namespace token

4.2.93. token::status_code

#include <boost/http/token.hpp>
namespace token {

struct status_code
{
    typedef uint_least16_t type;
    static const token::code::value code = token::code::status_code;
};

} // namespace token

4.2.94. token::reason_phrase

#include <boost/http/token.hpp>
namespace token {

struct reason_phrase
{
    typedef boost::string_view type;
    static const token::code::value code = token::code::reason_phrase;
};

} // namespace token

4.2.95. reader::request

#include <boost/http/reader/request.hpp>

This class represents an HTTP/1.1 (and HTTP/1.0) incremental parser. It’ll use the token definitions found in token::code::value. You may want to check the basic parsing tutorial to learn the basics.

Important

Once the parser enters in an error state (and the error is different than token::code::error_insufficient_data), the internal buffer is said to be in an invalidated state. Therefore, the parser won’t access the data anymore and the user is free to invalidate the data (e.g. resize/free it) without calling set_buffer() or reset() first.

If you want to reuse the same reader object to parse another stream, just call reset().

4.2.95.1. Member types
typedef std::size_t size_type

Type used to represent sizes.

typedef const char value_type

Type used to represent the value of a single element in the buffer.

typedef value_type *pointer

Pointer-to-value type.

typedef boost::string_view view_type

Type used to refer to non-owning string slices.

4.2.95.2. Member functions
request()

Constructor.

void reset()

After a call to this function, the object has the same internal state as an object that was just constructed.

token::code::value code() const

Use it to inspect current token. Returns code.

Note

The following values are never returned:

  • token::code::error_set_method.

  • token::code::error_use_another_connection.

  • token::code::status_code.

  • token::code::reason_phrase.

token::symbol::value symbol() const

Use it to inspect current token. Returns symbol.

Note

The following values are never returned:

  • token::symbol::status_code.

  • token::symbol::reason_phrase.

token::category::value category() const

Use it to inspect current token. Returns category.

size_type token_size() const

Returns the size of current token.

Note

After you call next(), you’re free to remove, from the buffer, the amount of bytes equals to the value returned here.

If you do remove the parsed data from the buffer, the address of the data shouldn’t change (i.e. you must not invalidate the pointers/iterators to old unparsed data). If you do change the address of old unparsed data, call set_buffer before using this object again.

Example
std::size_t nparsed = reader.token_size();
reader.next();
buffer.erase(0, nparsed);
reader.set_buffer(buffer);
Warning
Do not use string_length(reader.value<T>()) to compute the token size. string_length(reader.value<T>()) and reader.token_size() may differ. Check the advanced parsing tutorial for more details.
template<class T> typename T::type value() const

Extracts the value of current token and returns it.

T must be one of:

  • token::method.

  • token::request_target.

  • token::version.

  • token::field_name.

  • token::field_value.

  • token::body_chunk.

    Warning
    The assert(code() == T::code) precondition is assumed.
    Note
    This parser doesn’t buffer data. The value is extracted directly from buffer.
token::code::value expected_token() const

Returns the expected token code.

Useful when the buffer has been exhausted and code() == token::code::error_insufficient_data. Use it to respond with “URL/HTTP-header/…​ too long” or another error-handling strategy.

Warning

The returned value is a heuristic, not a truth. If your buffer is too small, the buffer will be exhausted with too little info to know which element is expected for sure.

For instance, expected_token() might return token::code::field_name, but when you have enough info in the buffer, the actual token happens to be token::code::end_of_headers.

void next()

Consumes the current token and advances in the buffer.

Note
Given the current token is complete (i.e. code() != token::code::error_insufficient_data), a call to this function always consumes the current token.
void set_buffer(asio::const_buffer inbuffer)

Sets buffer to inbuffer.

Note

inbuffer should hold the data at the same point of unparsed data from the internal buffer from before this call.

Example
std::size_t nparsed = reader.token_size();

// now unparsed data becomes ahead
// of `buffer.begin()`
reader.next();

reader.set_buffer(buffer + nparsed);
Warning
The reader object follows the HTTP stream orchestrated by the continuous flow of set_buffer() and next(). You should treat this region as read-only. For instance, if I pass "header-a: something" to the reader and then change the contents to "header-a: another thing", there are no guarantees about the reader object behaviour. You can safely change only the contents of the buffer region not yet exposed to reader through reader.set_buffer(some_buffer) (i.e. the region outside of some_buffer never seen by reader).
Note

You’re free to pass larger buffers at will.

You’re also free to pass a buffer just as big as current token (i.e. token_size()). In other words, you’re free to shrink the buffer if the new buffer is at least as big as current token.

Tip

If you want to free the buffer while maintaining the reader object valid, just set the buffer to current token size, call next() and then set buffer to an empty buffer.

Do notice that this will consume current token as well. And as values are decoded directly from the buffer, this strategy is the only choice.

Example
reader.set_buffer(boost::asio::buffer(buffer, reader.token_size()));
reader.next();
reader.set_buffer(boost::asio::const_buffer());
buffer.clear();
size_type parsed_count() const

Returns the number of bytes parsed since set_buffer was last called.

Tip

You can use it to go away with the nparsed variable shown in the principles on parsing tutorial. I’m sorry about the “you must keep track of the number of discarded bytes” lie I told you before, but as one great explainer once told:

As I look upon you…​ it occurs to me that you may not have the necessary level of maturity to handle the truth.

— Scott Meyers
C++ and Beyond 2012: Universal References in C++11

That lie was useful to explain some core concepts behind this library.

4.2.96. reader::response

#include <boost/http/reader/response.hpp>

This class represents an HTTP/1.1 (and HTTP/1.0) incremental parser. It’ll use the token definitions found in token::code::value. You may want to check the basic parsing tutorial to learn the basics.

Important

Once the parser enters in an error state (and the error is different than token::code::error_insufficient_data), the internal buffer is said to be in an invalidated state. Therefore, the parser won’t access the data anymore and the user is free to invalidate the data (e.g. resize/free it) without calling set_buffer() or reset() first.

If you want to reuse the same reader object to parse another stream, just call reset().

4.2.96.1. Member types
typedef std::size_t size_type

Type used to represent sizes.

typedef const char value_type

Type used to represent the value of a single element in the buffer.

typedef value_type *pointer

Pointer-to-value type.

typedef boost::string_view view_type

Type used to refer to non-owning string slices.

4.2.96.2. Member functions
response()

Constructor.

void set_method(view_type method)

Use it to inform the request method of the request message associated with this response message. This is necessary internally to compute the body size. If you do not call this function when code() == token::code::status_code, then token::code::error_set_method will be the next token.

Warning
The assert(code() == token::code::status_code) precondition is assumed.
void reset()

After a call to this function, the object has the same internal state as an object that was just constructed.

void puteof()

If the connection is closed, call this function. HTTP/1.0 used this event to signalize token::code::end_of_body.

token::code::value code() const

Use it to inspect current token. Returns code.

Note

The following values are never returned:

  • token::code::error_no_host.

  • token::code::method.

  • token::code::request_target.

token::symbol::value symbol() const

Use it to inspect current token. Returns symbol.

Note

The following values are never returned:

  • token::symbol::method.

  • token::symbol::request_target.

token::category::value category() const

Use it to inspect current token. Returns category.

size_type token_size() const

Returns the size of current token.

Note

After you call next(), you’re free to remove, from the buffer, the amount of bytes equals to the value returned here.

If you do remove the parsed data from the buffer, the address of the data shouldn’t change (i.e. you must not invalidate the pointers/iterators to old unparsed data). If you do change the address of old unparsed data, call set_buffer before using this object again.

Example
std::size_t nparsed = reader.token_size();
reader.next();
buffer.erase(0, nparsed);
reader.set_buffer(buffer);
Warning
Do not use string_length(reader.value<T>()) to compute the token size. string_length(reader.value<T>()) and reader.token_size() may differ. Check the advanced parsing tutorial for more details.
template<class T> typename T::type value() const

Extracts the value of current token and returns it.

T must be one of:

  • token::status_code.

  • token::version.

  • token::reason_phrase.

  • token::field_name.

  • token::field_value.

  • token::body_chunk.

    Warning
    The assert(code() == T::code) precondition is assumed.
    Note
    This parser doesn’t buffer data. The value is extracted directly from buffer.
token::code::value expected_token() const

Returns the expected token code.

Useful when the buffer has been exhausted and code() == token::code::error_insufficient_data. Use it to log error to cout or another error-handling strategy.

Warning

The returned value is a heuristic, not a truth. If your buffer is too small, the buffer will be exhausted with too little info to know which element is expected for sure.

For instance, expected_token() might return token::code::field_name, but when you have enough info in the buffer, the actual token happens to be token::code::end_of_headers.

void next()

Consumes the current token and advances in the buffer.

Note
Given the current token is complete (i.e. code() != token::code::error_insufficient_data), a call to this function always consumes the current token.
void set_buffer(asio::const_buffer inbuffer)

Sets buffer to inbuffer.

Note

inbuffer should hold the data at the same point of unparsed data from the internal buffer from before this call.

Example
std::size_t nparsed = reader.token_size();

// now unparsed data becomes ahead
// of `buffer.begin()`
reader.next();

reader.set_buffer(buffer + nparsed);
Warning
The reader object follows the HTTP stream orchestrated by the continuous flow of set_buffer() and next(). You should treat this region as read-only. For instance, if I pass "header-a: something" to the reader and then change the contents to "header-a: another thing", there are no guarantees about the reader object behaviour. You can safely change only the contents of the buffer region not yet exposed to reader through reader.set_buffer(some_buffer) (i.e. the region outside of some_buffer never seen by reader).
Note

You’re free to pass larger buffers at will.

You’re also free to pass a buffer just as big as current token (i.e. token_size()). In other words, you’re free to shrink the buffer if the new buffer is at least as big as current token.

Tip

If you want to free the buffer while maintaining the reader object valid, just set the buffer to current token size, call next() and then set buffer to an empty buffer.

Do notice that this will consume current token as well. And as values are decoded directly from the buffer, this strategy is the only choice.

Example
reader.set_buffer(boost::asio::buffer(buffer, reader.token_size()));
reader.next();
reader.set_buffer(boost::asio::const_buffer());
buffer.clear();
size_type parsed_count() const

Returns the number of bytes parsed since set_buffer was last called.

Tip

You can use it to go away with the nparsed variable shown in the principles on parsing tutorial. I’m sorry about the “you must keep track of the number of discarded bytes” lie I told you before, but as one great explainer once told:

As I look upon you…​ it occurs to me that you may not have the necessary level of maturity to handle the truth.

— Scott Meyers
C++ and Beyond 2012: Universal References in C++11

That lie was useful to explain some core concepts behind this library.

4.2.97. syntax::chunk_size

#include <boost/http/syntax/chunk_size.hpp>
namespace syntax {

template<class CharT>
struct chunk_size {
    typedef basic_string_view<CharT> view_type;

    BOOST_SCOPED_ENUM_DECLARE_BEGIN(result)
    {
        invalid,
        ok,
        overflow
    }
    BOOST_SCOPED_ENUM_DECLARE_END(result)

    static std::size_t match(view_type view);

    template<class Target>
    static result decode(view_type in, Target &out);
};

} // namespace syntax

4.2.98. syntax::content_length

#include <boost/http/syntax/content_length.hpp>
namespace syntax {

template<class CharT>
struct content_length {
    typedef basic_string_view<CharT> view_type;

    BOOST_SCOPED_ENUM_DECLARE_BEGIN(result)
    {
        invalid,
        ok,
        overflow
    }
    BOOST_SCOPED_ENUM_DECLARE_END(result)

    template<class Target>
    static result decode(view_type in, Target &out);
};

} // namespace syntax

4.2.99. syntax::strict_crlf

#include <boost/http/syntax/crlf.hpp>
namespace syntax {

template<class CharT>
struct strict_crlf {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);
};

} // namespace syntax

4.2.100. syntax::liberal_crlf

#include <boost/http/syntax/crlf.hpp>
namespace syntax {

template<class CharT>
struct liberal_crlf {
    typedef basic_string_view<CharT> view_type;

    BOOST_SCOPED_ENUM_DECLARE_BEGIN(result)
    {
        crlf,
        lf,
        insufficient_data,
        invalid_data,
    }
    BOOST_SCOPED_ENUM_DECLARE_END(result)

    static result match(view_type view);
};

} // namespace syntax

4.2.101. syntax::field_name

#include <boost/http/syntax/field_name.hpp>
namespace syntax {

template<class CharT>
struct field_name {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);
};

} // namespace syntax

4.2.102. syntax::left_trimmed_field_value

#include <boost/http/syntax/field_value.hpp>
namespace syntax {

template<class CharT>
struct left_trimmed_field_value {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);
};

} // namespace syntax

4.2.103. syntax::ows

#include <boost/http/syntax/ows.hpp>
namespace syntax {

template<class CharT>
struct ows {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);
};

} // namespace syntax

4.2.104. syntax::reason_phrase

#include <boost/http/syntax/reason_phrase.hpp>
namespace syntax {

template<class CharT>
struct reason_phrase {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);
};

} // namespace syntax

4.2.105. syntax::status_code

#include <boost/http/syntax/status_code.hpp>
namespace syntax {

template<class CharT>
struct status_code {
    typedef basic_string_view<CharT> view_type;

    static std::size_t match(view_type view);

    static uint_least16_t decode(view_type view);
};

} // namespace syntax

4.2.107. <boost/http/reader/request.hpp>

Import the following symbols:

4.2.108. <boost/http/reader/response.hpp>

Import the following symbols:

4.2.109. <boost/http/syntax/chunk_size.hpp>

Import the following symbols:

4.2.110. <boost/http/syntax/content_length.hpp>

Import the following symbols:

4.2.111. <boost/http/syntax/crlf.hpp>

Import the following symbols:

4.2.112. <boost/http/syntax/field_name.hpp>

Import the following symbols:

4.2.113. <boost/http/syntax/field_value.hpp>

Import the following symbols:

4.2.114. <boost/http/syntax/ows.hpp>

Import the following symbols:

4.2.115. <boost/http/syntax/reason_phrase.hpp>

Import the following symbols:

4.2.116. <boost/http/syntax/status_code.hpp>

Import the following symbols:


1. The larger explanation being: incomplete tokens are kept in buffer, so you are not required to allocate them on a secondary buffer, but you’re still allowed to mutate/move/grow the buffer (as opposed to…​ say…​ some C++ iterators that would get invalidated once the container changes).
2. parsing is done one token at a time no matter how much data is buffered
3. Proper extraction of header fields (some popular parsers like NodeJS’s will force you to know protocol details and parser internals to manually remove leading and trailing whitespace from field values). This point is not only about header field extraction, but really about a parser that indeed abstract and understand the protocol and that is easy to use right by people that don’t know HTTP.
4. Useful to avoid the gotchas of ASIO composed operations. See http://sourceforge.net/p/asio/mailman/message/32259256/ for more information.
5. See an example at http://sourceforge.net/p/axiomq/code/ci/master/tree/include/axiomq/basic_queue_socket.hpp.
6. for tests
7. Used in the core library.
8. For tests.
9. For the examples and tests.
10. For the file_server.
11. For the documentation.
12. Like axiomq’s basic_queue_socket
13. If I continue to develop the Boost.Http’s message framework, that’s the solution that will be adopted to this particular problem.
14. Consult Occam’s razor.
15. Defined in RFC 7231, section 7.1.1.1.
16. Defined in RFC 7232.
17. Defined in RFC 7232.
18. Defined in RFC 7231, section 5.1.1.
19. objects with contiguous storage of bytes.
20. The C++11 update gives extra guarantees about preserving the insertion ordering for elements with equivalent keys.
21. Defined in the Message concept’s “definitions” section.
22. Defined in the Message concept’s “definitions” section.
23. Defined in RFC 7231, section 5.1.1.
24. The notification of the error http_errc::out_of_order has priority over http_errc::native_stream_unsupported because these errors present a programming logic error that always will happen (i.e. they aren’t tied to specific runtime behaviour). If they aren’t fixed, the program won’t work either way.
25. Defined in the Message concept’s “definitions” section.