...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Beast provides synchronous and asynchronous algorithms to parse and serialize HTTP/1 wire format messages on streams. These functions form the message-oriented stream interface:
Table 1.19. Message Stream Operations
Name |
Description |
---|---|
Read a |
|
Read a |
|
Write a |
|
Write a |
All synchronous stream operations come in two varieties. One which throws
an exception upon error, and another which accepts as the last parameter
an argument of type error_code&
.
If an error occurs this argument will be set to contain the error code.
Because a serialized header is not length-prefixed, algorithms which parse messages from a stream may read past the end of a message for efficiency. To hold this surplus data, all stream read operations use a passed-in DynamicBuffer which must be persisted between calls until the end of stream is reached or the stream object is destroyed. Each read operation may consume bytes remaining in the buffer, and leave behind new bytes. In this example we declare the buffer and a message variable, then read a complete HTTP request synchronously:
flat_buffer buffer; // (The parser is optimized for flat buffers) request<string_body> req; read(sock, buffer, req);
This example uses flat_buffer
. Beast's basic_parser
is optimized for structured
HTTP data located in a single contiguous (flat) memory
buffer. When not using a flat buffer the implementation may perform an additional
memory allocations to restructure the input into a single buffer for parsing.
Tip | |
---|---|
Other Implementations of DynamicBuffer may avoid parser memory allocation by always returning buffer sequences of length one. |
Messages may also be read asynchronously. When performing asynchronous stream read operations the stream, buffer, and message variables must remain valid until the operation has completed. Beast asynchronous initiation functions use Asio's completion handler model. This call reads a message asynchronously and reports the error code upon completion. The handler is called with the error, set to any that occurs, and the number of bytes parsed. This number may be used to measure the relative amount of work performed, or it may be ignored as this example shows.
flat_buffer buffer; response<string_body> res; async_read(sock, buffer, res, [&](error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); std::cerr << ec.message() << std::endl; });
If a read stream algorithm cannot complete its operation without exceeding
the maximum specified size of the dynamic buffer provided, the error buffer_overflow
is returned. This is one technique which may be used to impose a limit on
the maximum size of an HTTP message header for protection from buffer overflow
attacks. The following code will print the error message:
// This buffer's max size is too small for much of anything flat_buffer buffer{10}; // Try to read a request error_code ec; request<string_body> req; read(sock, buffer, req, ec); if(ec == http::error::buffer_overflow) std::cerr << "Buffer limit exceeded!" << std::endl;
A set of free functions allow serialization of an entire HTTP message to a stream. This example constructs and sends an HTTP response:
response<string_body> res; res.version(11); res.result(status::ok); res.set(field::server, "Beast"); res.body() = "Hello, world!"; res.prepare_payload(); error_code ec; write(sock, res, ec);
The asynchronous version could be used instead:
async_write(sock, res, [&](error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); if(ec) std::cerr << ec.message() << std::endl; });
The completion handler is called with the number of bytes written to the stream, which includes protocol specific data such as the delimiters in the header and line endings. The number may be used to measure the amount of data transferred, or it may be ignored as in the example.