Files
boost_beast/doc/4_4_serializer_streams.qbk
T
2017-06-04 14:51:40 -07:00

307 lines
9.7 KiB
Plaintext

[/
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
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)
]
[section:serializer_streams Serializer Stream Operations]
Algorithms for sending entire messages to streams are intended for light
duty use-cases such as simple clients and low utilization servers.
Sophisticated algorithms will need to do more:
* Send the message header first.
* Send a message incrementally: bounded work in each I/O cycle.
* Use a custom chunk decorator or allocator when sending messages.
* Use a series of caller-provided buffers to represent the body.
All of these operations require callers to manage the lifetime of state
information associated with the operation, by constructing a __serializer__
object with the message to be sent. The serializer type has this declaration:
```
template<
bool isRequest,
class Body,
class Fields,
class ChunkDecorator = no_chunk_decorator,
class Allocator = std::allocator<char>
>
class serializer;
```
The choices for template types must match the message passed on construction.
This code creates an HTTP response and the corresponding serializer:
```
response<string_body> res;
...
serializer<false, string_body, fields> sr{res};
```
The convenience function
[link beast.ref.http__make_serializer `make_serializer`]
is provided to avoid repetition of template argument types. The declaration
for `sr` in the code above may be written as:
```
...
auto sr = make_serializer(res);
```
The stream operations which work on serializers are:
[table Serializer Stream Operations
[[Name][Description]]
[[
[link beast.ref.http__write_some.overload1 [*write_some]]
][
Send some __serializer__ buffer data to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_some [*async_write_some]]
][
Send some __serializer__ buffer data asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write_header.overload1 [*write_header]]
][
Send only the header from a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write_header [*async_write_header]]
][
Send only the header from a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
[[
[link beast.ref.http__write.overload1 [*write]]
][
Send everything in a __serializer__ to a __SyncWriteStream__.
]]
[[
[link beast.ref.http__async_write.overload1 [*async_write]]
][
Send everything in a __serializer__ asynchronously to an __AsyncWriteStream__.
]]
]
Here is an example of using a serializer to send a message on a stream
synchronously. This performs the same operation as calling `write(stream, m)`:
```
template<class SyncWriteStream, bool isRequest, class Body, class Fields>
void send(SyncWriteStream& stream, message<isRequest, Body, Fields> const& m)
{
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
serializer<isRequest, Body, Fields> sr{m};
do
{
write_some(stream, sr);
}
while(! sr.is_done());
}
```
[heading Example: Expect 100-continue]
The Expect field with the value "100-continue" in a request is special. It
indicates that the after sending the message header, a client desires an
immediate informational response before sending the the message body, which
presumably may be expensive to compute or large. This behavior is described in
[@https://tools.ietf.org/html/rfc7231#section-5.1.1 rfc7231 section 5.1.1].
Invoking the 100-continue behavior is implemented easily in a client by
constructing a __serializer__ to send the header first, then receiving
the server response, and finally conditionally send the body using the same
serializer instance. A synchronous, simplified version (no timeout) of
this client action looks like this:
```
/** Send a request with Expect: 100-continue
This function will send a request with the Expect: 100-continue
field by first sending the header, then waiting for a successful
response from the server before continuing to send the body. If
a non-successful server response is received, the function
returns immediately.
@param stream The remote HTTP server stream.
@param buffer The buffer used for reading.
@param req The request to send. This function modifies the object:
the Expect header field is inserted into the message if it does
not already exist, and set to "100-continue".
@param ec Set to the error, if any occurred.
*/
template<
class SyncStream,
class DynamicBuffer,
class Body, class Fields>
void
send_expect_100_continue(
SyncStream& stream,
DynamicBuffer& buffer,
request<Body, Fields>& req)
{
static_assert(is_sync_stream<SyncStream>::value,
"SyncStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Insert or replace the Expect field
req.fields.replace("Expect", "100-continue");
// Create the serializer
auto sr = make_serializer(req);
// Send just the header
write_header(stream, sr);
// Read the response from the server.
// A robust client could set a timeout here.
{
response<string_body> res;
read(stream, buffer, res);
if(res.status != 100)
{
// The server indicated that it will not
// accept the request, so skip sending the body.
return;
}
}
// Server is OK with the request, send the body
write(stream, sr);
}
```
[heading Example: Using Manual Buffers]
Sometimes it is necessary to send a message whose body is not conveniently
described by a single container. For example, when implementing an HTTP relay
function a robust implementation needs to present body buffers individually
as they become available from the downstream host. These buffers should be
fixed in size, otherwise creating the unnecessary and inefficient burden of
reading the complete message body before forwarding it to the upstream host.
To enable these use-cases, the body type __buffer_body__ is provided. This
body uses a caller-provided pointer and size instead of an owned container.
To use this body, instantiate an instance of the serializer and fill in
the pointer and size fields before calling a stream write function.
This example reads from a child process and sends the output back in an
HTTP response. The output of the process is sent as it becomes available:
```
/** Send the output of a child process as an HTTP response.
The output of the child process comes from a @b SyncReadStream. Data
will be sent continuously as it is produced, without the requirement
that the entire process output is buffered before being sent. The
response will use the chunked transfer encoding.
@param input A stream to read the child process output from.
@param output A stream to write the HTTP response to.
@param ec Set to the error, if any occurred.
*/
template<
class SyncReadStream,
class SyncWriteStream>
void
send_cgi_response(
SyncReadStream& input,
SyncWriteStream& output,
error_code& ec)
{
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_sync_write_stream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
// Set up the response. We use the buffer_body type,
// allowing serialization to use manually provided buffers.
message<false, buffer_body, fields> res;
res.status = 200;
res.version = 11;
res.fields.insert("Server", "Beast");
res.fields.insert("Transfer-Encoding", "chunked");
// No data yet, but we set more = true to indicate
// that it might be coming later. Otherwise the
// serializer::is_done would return true right after
// sending the header.
res.body.data = nullptr;
res.body.more = true;
// Create the serializer. We set the split option to
// produce the header immediately without also trying
// to acquire buffers from the body (which would return
// the error http::need_buffer because we set `data`
// to `nullptr` above).
auto sr = make_serializer(res);
// Send the header immediately.
write_header(output, sr, ec);
if(ec)
return;
// Alternate between reading from the child process
// and sending all the process output until there
// is no more output.
do
{
// Read a buffer from the child process
char buffer[2048];
auto bytes_transferred = input.read_some(
boost::asio::buffer(buffer, sizeof(buffer)), ec);
if(ec == boost::asio::error::eof)
{
ec = {};
// `nullptr` indicates there is no buffer
res.body.data = nullptr;
// `false` means no more data is coming
res.body.more = false;
}
else
{
if(ec)
return;
// Point to our buffer with the bytes that
// we received, and indicate that there may
// be some more data coming
res.body.data = buffer;
res.body.size = bytes_transferred;
res.body.more = true;
}
// Write everything in the body buffer
write(output, sr, ec);
// This error is returned by body_buffer during
// serialization when it is done sending the data
// provided and needs another buffer.
if(ec == error::need_buffer)
{
ec = {};
continue;
}
if(ec)
return;
}
while(! sr.is_done());
}
```
[endsect]