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.
-
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.
-
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:
-
async_write_response_metadata
once. -
async_write
zero or more times. -
async_write_trailers
orasync_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
.
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 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 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
|
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 beforecode::end_of_message
even if nocode::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:
-
Set the buffer used by the reader.
-
Consume tokens.
-
Check
code()
return. -
Possibly call
value<T>()
to extract token value. -
Call
next()
.
-
-
Remove parsed data from the buffer.
-
You’ll need to keep state of parsed vs unparsed data by calling
token_size()
.
-
-
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
|
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:
-
Get the buffer right with
parser.set_buffer(buf)
. -
Loop to consume —
parser.next()
— tokens whilehttp::token::code::error_insufficient_data
.-
Examine token with
parser.code()
. -
Maybe handle error.
-
Extract data with
parser.value<T>()
if a data token.
-
-
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):
But if you use Boost.Http parser, this requirement vanishes:
|
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 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
Just as a concrete illustration of what I meant by the Boost.Http solution:
How would the solution look like using the Boost.Beast parser? Let’s draft something:
However, this solution is fully flawed. As 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…
— Aleister Crowley
A concrete example if you may:
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
.
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 |
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 byhttp::basic_parser::put
.Setting the limit after any header octets have been parsed results in undefined behavior.
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.
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 |
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:
Boost.Beast solution is not hard to imagine too:
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
But then we create another problem:
— 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
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
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. 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
-
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.
-
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. -
Have you considered contribute to project X/Y?
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.
-
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.
-
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.
-
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).
-
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.
-
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.
-
What are the differences between
reader::request
andreader::response
?-
response
has thevoid set_method(view_type method)
member-function.WarningThis member-function MUST be called for each HTTP response message being parsed in the stream. -
response
has thevoid 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
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.
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_socket
s' 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.
-
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.1. Classes
-
Tokens
-
Structural parsers
4.1.2. Class Templates
-
Content parsers
4.1.3. Free Functions
4.1.4. Enumerations
4.1.5. Error Codes
4.1.6. Type Requirements
4.1.7. Headers
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
'sSettings::body_copy_threshold
. It represents the maximum amount of bytesbasic_socket
will copy from message bodies in an attempt to send a single buffer chunk to the underlying network write syscall. If0
, 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.NoteResponse::headers_type
must be the same asRequest::headers_type
. And the same applies toResponse::body_type
. Also, ifconst
is applied toRequest
orResponse
, it must be applied to both.
4.2.6.2. Member types
headers_type
-
If
Request
isconst
, then it is defined asconst typename Request::headers_type
. Otherwise, it’s defined astypename Request::headers_type
. body_type
-
If
Request
isconst
, then it is defined asconst typename Request::body_type
. Otherwise, it’s defined astypename Request::body_type
.
4.2.6.3. Member functions
request_response_wrapper(Request &request)
-
Constructs a
request_response_wrapper
from aRequest
. request_response_wrapper(Response &response)
-
Constructs a
request_response_wrapper
from aResponse
. template<class Request2, class Response2> request_response_wrapper(request_response_wrapper<Request2, Response2> &other)
-
Constructs a
request_response_wrapper
from anotherrequest_response_wrapper
with compatibleheaders_type
andbody_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_base
hierarchy4.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.
This class is inherited by basic_poly_socket<Request,
Response, Message>
, server_socket_adaptor
and
socket_adaptor
.
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.
This class is inherited by basic_poly_socket<Request,
Response, Message>
, client_socket_adaptor
and
socket_adaptor
.
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.11.1. See also
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 theRequest
concept. Headers
-
The type to fulfill the
X::headers_type
from theRequest
concept. Body
-
The type to fulfill the
X::body_type
from theRequest
concept.
4.2.15.2. Member types
typedef String string_type
-
The type to fulfill the
X::string_type
from theRequest
concept. typedef Headers headers_type
-
The type to fulfill the
X::headers_type
from theRequest
concept. typedef Body body_type
-
The type to fulfill the
X::body_type
from theRequest
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 theResponse
concept. Headers
-
The type to fulfill the
X::headers_type
from theResponse
concept. Body
-
The type to fulfill the
X::body_type
from theResponse
concept.
4.2.16.2. Member types
typedef String string_type
-
The type to fulfill the
X::string_type
from theResponse
concept. typedef Headers headers_type
-
The type to fulfill the
X::headers_type
from theResponse
concept. typedef Body body_type
-
The type to fulfill the
X::body_type
from theResponse
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:
|
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’sAsyncWriteStream
. 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.
NoteSee is_open()
WarningYou 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 than0
), when the end of message is reached (called with no error set and value0
) or when some error occurs.message.body()
,message.trailers()
andchunkext
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 inmessage.body()
. The chunk size is passed to the handler andchunkext
data refers to the next bytes that will be pushed intomessage.body()
(i.e. you need to initiate another read to get the chunk data referred bychunkext
).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()
andchunkext
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
See the 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
See the 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:
|
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’sAsyncWriteStream
. 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
See the 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
See the 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.NoteNot available under the server_socket_adaptor<std::reference_wrapper<Socket>>
specialization. server_socket_adaptor(Socket &socket)
-
Constructor.
socket
is passed to thestd::reference_wrapper
constructor.NoteOnly 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.NoteNot available under the client_socket_adaptor<std::reference_wrapper<Socket>>
specialization. client_socket_adaptor(Socket &socket)
-
Constructor.
socket
is passed to thestd::reference_wrapper
constructor.NoteOnly 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.NoteNot available under the socket_adaptor<std::reference_wrapper<Socket>>
specialization. socket_adaptor(Socket &socket)
-
Constructor.
socket
is passed to thestd::reference_wrapper
constructor.NoteOnly 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.31.4. See also
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:
|
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.CautionResponse::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.
CautionIf 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 valuefalse
to this argument.NoteAvailable 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.32.5. See also
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.WarningThis 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.
NoteAny 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_codefile_server_category::filter_set
.TipIt’s possible to use a stateful non-pure filter to add response headers and properly process mime types (content-type header). Tipfilter can also be used to redirect files by modifying the input arg. NoteThis function might throw if filter throws. In this case, we provide the basic exception guarantee. NoteAvailable 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.33.4. See also
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)
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 tobody_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)
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 toread_trailers
— or indirectly — through a call toread_some
orread_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 tobody_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)
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
orcontinue_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)
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
NoteNo 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.36.3. See also
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()
andsocket.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.TipIf you’re using a type fulfilling the ServerSocket
concept, you may be interested in thewrite_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.
NoteIt’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.38.4. See also
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'~'
.NoteAny 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).
NoteFor 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 theString
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.WarningInserting elements in Headers
instances whose keys contains uppercase char(s) invoke undefined behaviour. -
Headers::mapped_type
MUST fulfill the requirements for theString
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 |
---|---|---|---|---|
|
|
|||
|
|
|||
|
|
|||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
-
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 |
---|---|---|---|---|
|
|
|||
|
|
|||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
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 |
---|---|---|---|---|
|
|
|||
|
|
|||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
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 |
---|---|---|---|---|
|
|
|||
|
The type of the executor associated with the object. |
|||
|
|
Returns the executor associated with the object. |
||
|
|
Must be a type fulfilling the |
||
|
|
Determine whether the socket is open. |
||
|
Returns the state associated with the |
|||
|
Returns the state associated with the |
|||
|
|
|
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. |
By the time the handler is called, the part of the message read (if any) is
appended to |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
By the time the handler is called, if no error happened, the read trailers (if
any) are inserted into |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
By the time the handler is called, the |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
By the time the operation completes, the |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
|
|
|
No actions are done and the handler from the completion token is called with
|
-
Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.
-
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
. -
The
Socket
object has the freedom to store information required to further process the incoming message in the user-providedmessage
object. Thus, the library user MUST NOT use differentmessage
objects in the functions that initiate read operations, in the context of the same message exchange (i.e. the user can use a differentmessage
object to receive a different message). This requirement is extended to refinements of this concept. -
The
Socket
object MUST NOT insert HTTP headers with empty keys (i.e.""
) in message, request or response objects provided by the user. -
You MUST NOT write messages with the
"transfer-encoding: chunked"
header. -
You MUST NOT write atomic messages with the
"transfer-encoding"
header.
4.2.42.4. Models
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 |
---|---|---|---|---|
|
|
|||
|
|
Must be a type fulfilling the requirements for the |
||
|
|
Must be a type fulfilling the requirements for the
|
||
|
|
|
Returns whether the current message exchange supports native stream. |
The same value and property is maintained until the end of the current message exchange. |
|
|
|
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. The |
By the time the handler is called, |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
Initiate an asynchronous operation to write the response message (atomic message). Handler is called with an appropriate argument when the operation completes. |
|
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
|
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
Initiate an asynchronous operation to write the response metadata (chunked message). Handler is called with an appropriate argument when the operation completes. |
|
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
No actions are done and the handler from the completion token is called with
|
The following Socket
operations are refined with extra
semantics/postconditions:
Expression | Precondition | Extra semantics | Extra postcondition |
---|---|---|---|
|
|
By the time the handler is called, if no error happened, |
|
|
|
|
|
|
|
|
-
Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.
-
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
. -
If the user pass a
"connection: close"
header on the message object passed as argument to theasync_write_response
orasync_write_response_metadata
member-functions, theServerSocket
MUST change the state to closed (i.e.is_open()
will returnfalse
).This behaviour is intended for the communication between the user of this library and the
ServerSocket
and can differ from the communication between theServerSocket
and the underlying channel. -
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 theServerSocket
and the underlying channel. -
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 theServerSocket
and the underlying channel. -
If the
ServerSocket
reads a message that represent a desire from the HTTP client to initiate a protocol upgrade, theServerSocket
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 theServerSocket
and the underlying channel. -
-
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 theServerSocket
and the underlying channel. -
If the
"content-length"
header is provided toasync_write_response
, then theServerSocket
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. -
The
ServerSocket
object MUST NOT insert HTTP headers with empty keys (i.e.""
) in message, request or response objects provided by the user. -
Informational responses (i.e. 1xx class of status code) indicates an interim response and do not change
read_state
orwrite_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
, errorhttp_errc::native_stream_unsupported
will be reported). -
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 |
---|---|---|---|---|
|
|
|||
|
|
Must be a type fulfilling the requirements for the |
||
|
|
Must be a type fulfilling the requirements for the
|
||
|
|
|
Initiate an asynchronous operation to write the request message (atomic message). Handler is called with an appropriate argument when the operation completes. |
By the time the handler is called, the |
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
Initiate an asynchronous operation to write the request metadata (chunked message). Handler is called with an appropriate argument when the operation completes. |
|
|
|
|
No actions are done and the handler from the completion token is called with
|
|
|
|
|
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. |
By the time the handler is called, |
|
|
|
No actions are done and the handler from the completion token is called with
|
The following Socket
operations are refined with extra
semantics/postconditions:
Expression | Precondition | Extra semantics | Extra postcondition |
---|---|---|---|
|
|
By the time the handler is called, if no error happened, |
|
|
|
|
|
|
|
|
-
Failing to comply with the “MUST” and “MUST NOT” conditions described previously invokes undefined behaviour.
-
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
. -
Upon receiving a message (i.e.
async_read_response
,async_read_some
orasync_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 returnfalse
).This behaviour is intended for the communication between the user of this library and the
ClientSocket
and can differ from the communication between theClientSocket
and the underlying channel. -
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 theClientSocket
and the underlying channel. For instance, if theClientSocket
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 toread_response
whether this response was received from the underlying channel or not (i.e. fill a virtual response). -
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 theClientSocket
and the underlying channel. -
The
ClientSocket
object MUST NOT insert HTTP headers with empty keys (i.e.""
) in message, request or response objects provided by the user. -
The user of this library MUST NOT insert
"content-length"
or"transfer-encoding"
headers inom
.
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.45.1. See also
4.2.46. <boost/http/algorithm/header.hpp>
Import the following symbols:
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.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.54. <boost/http/request_response_wrapper.hpp>
Import the following symbols:
4.2.55. <boost/http/poly_socket.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.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.63. <boost/http/socket.hpp>
Import the following symbols:
4.2.64. <boost/http/buffered_socket.hpp>
Import the following symbols:
4.2.67. <boost/http/traits.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.70.2. See also
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.71.2. See also
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.72.2. See also
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 |
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 |
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
If you want to reuse the same reader object to parse another stream, just call
|
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.
NoteThe 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.
NoteThe 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.
NoteAfter 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.Examplestd::size_t nparsed = reader.token_size(); reader.next(); buffer.erase(0, nparsed); reader.set_buffer(buffer);
WarningDo not use string_length(reader.value<T>())
to compute the token size.string_length(reader.value<T>())
andreader.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
.WarningThe assert(code() == T::code)
precondition is assumed.NoteThis 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.WarningThe 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 returntoken::code::field_name
, but when you have enough info in the buffer, the actual token happens to betoken::code::end_of_headers
. void next()
-
Consumes the current token and advances in the buffer.
NoteGiven 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.
Noteinbuffer should hold the data at the same point of unparsed data from the internal buffer from before this call.
Examplestd::size_t nparsed = reader.token_size(); // now unparsed data becomes ahead // of `buffer.begin()` reader.next(); reader.set_buffer(buffer + nparsed);
WarningThe reader object follows the HTTP stream orchestrated by the continuous flow of set_buffer()
andnext()
. 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 toreader
throughreader.set_buffer(some_buffer)
(i.e. the region outside ofsome_buffer
never seen byreader
).NoteYou’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.TipIf 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.
Examplereader.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.TipYou 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++11That lie was useful to explain some core concepts behind this library.
4.2.95.3. See also
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
If you want to reuse the same reader object to parse another stream, just call
|
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
, thentoken::code::error_set_method
will be the next token.WarningThe 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 signalizetoken::code::end_of_body
. token::code::value code() const
-
Use it to inspect current token. Returns code.
NoteThe 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.
NoteThe 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.
NoteAfter 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.Examplestd::size_t nparsed = reader.token_size(); reader.next(); buffer.erase(0, nparsed); reader.set_buffer(buffer);
WarningDo not use string_length(reader.value<T>())
to compute the token size.string_length(reader.value<T>())
andreader.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
.WarningThe assert(code() == T::code)
precondition is assumed.NoteThis 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.WarningThe 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 returntoken::code::field_name
, but when you have enough info in the buffer, the actual token happens to betoken::code::end_of_headers
. void next()
-
Consumes the current token and advances in the buffer.
NoteGiven 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.
Noteinbuffer should hold the data at the same point of unparsed data from the internal buffer from before this call.
Examplestd::size_t nparsed = reader.token_size(); // now unparsed data becomes ahead // of `buffer.begin()` reader.next(); reader.set_buffer(buffer + nparsed);
WarningThe reader object follows the HTTP stream orchestrated by the continuous flow of set_buffer()
andnext()
. 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 toreader
throughreader.set_buffer(some_buffer)
(i.e. the region outside ofsome_buffer
never seen byreader
).NoteYou’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.TipIf 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.
Examplereader.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.TipYou 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++11That lie was useful to explain some core concepts behind this library.
4.2.96.3. See also
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.106. <boost/http/token.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.113. <boost/http/syntax/field_value.hpp>
Import the following symbols: