forked from boostorg/beast
307 lines
9.7 KiB
Plaintext
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]
|