mirror of
https://github.com/boostorg/beast.git
synced 2025-07-31 13:27:33 +02:00
Refactor HTTP serialization (API Change):
A new class `serializer` is introduced to permit incremental serialization of HTTP messages. Existing free functions are re-implemented in terms of this new class. * The BodyReader concept is refined to support a greater variety of strategies for providing buffers representing the body to the serialization algorithms. * Added buffer_body, a new model of Body which allows the caller to provide a series of owned buffers using their own serialization loop. * Added empty_body, a model of Body which is for serialization only, to represent HTTP messages with no content body. * Removed overloads of write and async_write which send only the HTTP header. * Removed public interfaces for performing low-level chunk encoding.
This commit is contained in:
@ -5,6 +5,7 @@ Version 46
|
|||||||
API Changes:
|
API Changes:
|
||||||
|
|
||||||
* Remove HTTP header aliases
|
* Remove HTTP header aliases
|
||||||
|
* Refactor HTTP serialization
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
179
doc/concept/Writer.qbk
Normal file
179
doc/concept/Writer.qbk
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
[/
|
||||||
|
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:Writer Writer requirements]
|
||||||
|
|
||||||
|
A [*Writer] provides an online algorithm to obtain a sequence of zero
|
||||||
|
or more buffers from a body during serialization. The implementation creates
|
||||||
|
an instance of this type when needed, and calls into it zero or more times to
|
||||||
|
retrieve buffers with body octets. The interface of [*Writer] is intended to
|
||||||
|
allow serialization in these scenarios:
|
||||||
|
|
||||||
|
* A body that does not entirely fit in memory.
|
||||||
|
* A body produced incrementally from coroutine output.
|
||||||
|
* A body represented by zero or more buffers already in memory.
|
||||||
|
* A body as a series of buffers when the content size is not known ahead of time.
|
||||||
|
* Body data generated on demand from other threads.
|
||||||
|
* Body data computed algorithmically.
|
||||||
|
|
||||||
|
In this table:
|
||||||
|
|
||||||
|
* `X` denotes a type meeting the requirements of [*Writer].
|
||||||
|
|
||||||
|
* `a` denotes a value of type `X`.
|
||||||
|
|
||||||
|
* `m` denotes a value of type `message const&` where
|
||||||
|
`std::is_same<decltype(m.body), Body::value_type>:value == true`.
|
||||||
|
|
||||||
|
* `ec` is a value of type [link beast.ref.error_code `error_code&`].
|
||||||
|
|
||||||
|
* `B<T>` is the type `boost::optional<std::pair<T, bool>>`.
|
||||||
|
|
||||||
|
[table Writer requirements
|
||||||
|
[[operation] [type] [semantics, pre/post-conditions]]
|
||||||
|
[
|
||||||
|
[`X::const_buffers_type`]
|
||||||
|
[]
|
||||||
|
[
|
||||||
|
A nested type which meets the requirements of __ConstBufferSequence__.
|
||||||
|
This is the type of buffer returned by `X::get`.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[`X(m);`]
|
||||||
|
[]
|
||||||
|
[
|
||||||
|
Constructible from `m`. The lifetime of `m` is guaranteed
|
||||||
|
to end no earlier than after the `X` is destroyed.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[`a.init(ec)`]
|
||||||
|
[`void`]
|
||||||
|
[
|
||||||
|
Called immediately after construction. If the function sets an
|
||||||
|
error code in `ec`, the serialization is aborted and the error
|
||||||
|
is propagated to the caller.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[`a.content_length()`]
|
||||||
|
[`std::uint64_t`]
|
||||||
|
[
|
||||||
|
If this member is present, it is called after initialization
|
||||||
|
and before calls to provide buffers. The serialized message will
|
||||||
|
have the Content-Length field set to the value returned from
|
||||||
|
this function. If this member is absent, the serialized message
|
||||||
|
body will be chunk-encoded for HTTP versions 1.1 and later, else
|
||||||
|
the serialized message body will be sent unmodified, with the
|
||||||
|
error `boost::asio::error::eof` returned to the caller, to notify
|
||||||
|
they should close the connection to indicate the end of the message.
|
||||||
|
This function must be `noexcept`.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[`a.get(ec)`]
|
||||||
|
[`B<X::const_buffers_type>`]
|
||||||
|
[
|
||||||
|
Called repeatedly after `init` succeeds. This function returns
|
||||||
|
`boost::none` if all buffers representing the body have been
|
||||||
|
returned in previous calls or if it sets `ec` to indicate an
|
||||||
|
error. Otherwise, if there are buffers remaining the function
|
||||||
|
should return a pair with the first element containing a non-zero
|
||||||
|
length buffer sequence representing the next set of octets in
|
||||||
|
the body, while the second element is a `bool` meaning `true`
|
||||||
|
if there may be additional buffers returned on a subsequent call,
|
||||||
|
or `false` if the buffer returned on this call is the last
|
||||||
|
buffer representing the body.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[
|
||||||
|
[`http::is_Writer<X>`]
|
||||||
|
[`std::true_type`]
|
||||||
|
[
|
||||||
|
An alias for `std::true_type` for `X`, otherwise an
|
||||||
|
alias for `std::false_type`.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
[note
|
||||||
|
Definitions for required `Writer` member functions should be declared
|
||||||
|
inline so the generated code can become part of the implementation.
|
||||||
|
]
|
||||||
|
|
||||||
|
Exemplar:
|
||||||
|
```
|
||||||
|
struct writer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Controls when the implementation requests buffers.
|
||||||
|
|
||||||
|
If false, the implementation will request the first buffer
|
||||||
|
immediately and try to send both the header and the body
|
||||||
|
buffer in a single call to the stream's `write_some`
|
||||||
|
function.
|
||||||
|
*/
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
/** The type of buffer returned by `get`.
|
||||||
|
*/
|
||||||
|
using const_buffers_type = boost::asio::const_buffers_1;
|
||||||
|
|
||||||
|
/** Construct the writer.
|
||||||
|
|
||||||
|
The msg object is guaranteed to exist for the lifetime of the writer.
|
||||||
|
|
||||||
|
@param msg The message whose body is to be written.
|
||||||
|
*/
|
||||||
|
template<bool isRequest, class Body, class Headers>
|
||||||
|
explicit
|
||||||
|
writer(message<isRequest, Body, Headers> const& msg);
|
||||||
|
|
||||||
|
/** Initialize the writer.
|
||||||
|
|
||||||
|
Called once immediately after construction.
|
||||||
|
The writer can perform initialization which may fail.
|
||||||
|
|
||||||
|
@param ec Contains the error code if any errors occur.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
init(error_code& ec);
|
||||||
|
|
||||||
|
/** Returns the content length.
|
||||||
|
|
||||||
|
If this member is present, the implementation will set the
|
||||||
|
Content-Length field accordingly. If absent, the implementation will
|
||||||
|
use chunk-encoding or terminate the connection to indicate the end
|
||||||
|
of the message.
|
||||||
|
*/
|
||||||
|
std::uint64_t
|
||||||
|
content_length();
|
||||||
|
|
||||||
|
/** Returns the next buffer in the body.
|
||||||
|
|
||||||
|
@li If the return value is `boost::none` (unseated optional) and
|
||||||
|
`ec` does not contain an error, this indicates the end of the
|
||||||
|
body, no more buffers are present.
|
||||||
|
|
||||||
|
@li If the optional contains a value, the first element of the
|
||||||
|
pair represents a @b ConstBufferSequence containing one or
|
||||||
|
more octets of the body data. The second element indicates
|
||||||
|
whether or not there are additional octets of body data.
|
||||||
|
A value of `true` means there is more data, and that the
|
||||||
|
implementation will perform a subsequent call to `get`.
|
||||||
|
A value of `false` means there is no more body data.
|
||||||
|
|
||||||
|
@li If `ec` contains an error code, the return value is ignored.
|
||||||
|
*/
|
||||||
|
template<class WriteFunction>
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(error_code& ec);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
[endsect]
|
@ -193,6 +193,15 @@ used in the examples:
|
|||||||
`value_type` of [link beast.ref.multi_buffer `multi_buffer`]: an efficient storage
|
`value_type` of [link beast.ref.multi_buffer `multi_buffer`]: an efficient storage
|
||||||
object which uses multiple octet arrays of varying lengths to represent data.
|
object which uses multiple octet arrays of varying lengths to represent data.
|
||||||
|
|
||||||
|
* [link beast.ref.http__buffer_body [*`buffer_body`:]] A write-only body
|
||||||
|
with a `value_type` representing a __ConstBufferSequence__. This special
|
||||||
|
body allows the caller to implement their own write loop for advanced
|
||||||
|
use-cases such as relaying.
|
||||||
|
|
||||||
|
* [link beast.ref.http__empty_body [*`empty_body`:]] A write-only body
|
||||||
|
with an empty `value_type` representing an HTTP message with no content
|
||||||
|
body.
|
||||||
|
|
||||||
[heading Advanced]
|
[heading Advanced]
|
||||||
|
|
||||||
User-defined types are possible for the message body, where the type meets the
|
User-defined types are possible for the message body, where the type meets the
|
||||||
|
@ -112,14 +112,14 @@ provides implementations of the HTTP and WebSocket protocols.
|
|||||||
|
|
||||||
[section:ref Reference]
|
[section:ref Reference]
|
||||||
[xinclude quickref.xml]
|
[xinclude quickref.xml]
|
||||||
[include types/Body.qbk]
|
[include concept/Body.qbk]
|
||||||
[include types/BufferSequence.qbk]
|
[include concept/BufferSequence.qbk]
|
||||||
[include types/DynamicBuffer.qbk]
|
[include concept/DynamicBuffer.qbk]
|
||||||
[include types/Field.qbk]
|
[include concept/Field.qbk]
|
||||||
[include types/FieldSequence.qbk]
|
[include concept/FieldSequence.qbk]
|
||||||
[include types/Reader.qbk]
|
[include concept/Reader.qbk]
|
||||||
[include types/Streams.qbk]
|
[include concept/Streams.qbk]
|
||||||
[include types/Writer.qbk]
|
[include concept/Writer.qbk]
|
||||||
[include reference.qbk]
|
[include reference.qbk]
|
||||||
[endsect]
|
[endsect]
|
||||||
|
|
||||||
|
@ -32,15 +32,19 @@
|
|||||||
<member><link linkend="beast.ref.http__basic_dynamic_body">basic_dynamic_body</link></member>
|
<member><link linkend="beast.ref.http__basic_dynamic_body">basic_dynamic_body</link></member>
|
||||||
<member><link linkend="beast.ref.http__basic_fields">basic_fields</link></member>
|
<member><link linkend="beast.ref.http__basic_fields">basic_fields</link></member>
|
||||||
<member><link linkend="beast.ref.http__basic_parser">basic_parser</link></member>
|
<member><link linkend="beast.ref.http__basic_parser">basic_parser</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__buffer_body">buffer_body</link></member>
|
||||||
<member><link linkend="beast.ref.http__dynamic_body">dynamic_body</link></member>
|
<member><link linkend="beast.ref.http__dynamic_body">dynamic_body</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__empty_body">empty_body</link></member>
|
||||||
<member><link linkend="beast.ref.http__fields">fields</link></member>
|
<member><link linkend="beast.ref.http__fields">fields</link></member>
|
||||||
<member><link linkend="beast.ref.http__header">header</link></member>
|
<member><link linkend="beast.ref.http__header">header</link></member>
|
||||||
<member><link linkend="beast.ref.http__header_parser">header_parser</link></member>
|
<member><link linkend="beast.ref.http__header_parser">header_parser</link></member>
|
||||||
<member><link linkend="beast.ref.http__message">message</link></member>
|
<member><link linkend="beast.ref.http__message">message</link></member>
|
||||||
<member><link linkend="beast.ref.http__message_parser">message_parser</link></member>
|
<member><link linkend="beast.ref.http__message_parser">message_parser</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__empty_decorator">empty_decorator</link></member>
|
||||||
<member><link linkend="beast.ref.http__request">request</link></member>
|
<member><link linkend="beast.ref.http__request">request</link></member>
|
||||||
<member><link linkend="beast.ref.http__response">response</link></member>
|
<member><link linkend="beast.ref.http__response">response</link></member>
|
||||||
<member><link linkend="beast.ref.http__string_body">string_body</link></member>
|
<member><link linkend="beast.ref.http__string_body">string_body</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__write_stream">write_stream</link></member>
|
||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">rfc7230</bridgehead>
|
<bridgehead renderas="sect3">rfc7230</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<simplelist type="vert" columns="1">
|
||||||
@ -57,16 +61,15 @@
|
|||||||
<member><link linkend="beast.ref.http__async_read">async_read</link></member>
|
<member><link linkend="beast.ref.http__async_read">async_read</link></member>
|
||||||
<member><link linkend="beast.ref.http__async_read_some">async_read_some</link></member>
|
<member><link linkend="beast.ref.http__async_read_some">async_read_some</link></member>
|
||||||
<member><link linkend="beast.ref.http__async_write">async_write</link></member>
|
<member><link linkend="beast.ref.http__async_write">async_write</link></member>
|
||||||
<member><link linkend="beast.ref.http__chunk_encode">chunk_encode</link></member>
|
|
||||||
<member><link linkend="beast.ref.http__chunk_encode_final">chunk_encode_final</link></member>
|
|
||||||
<member><link linkend="beast.ref.http__swap">swap</link></member>
|
|
||||||
<member><link linkend="beast.ref.http__is_keep_alive">is_keep_alive</link></member>
|
<member><link linkend="beast.ref.http__is_keep_alive">is_keep_alive</link></member>
|
||||||
<member><link linkend="beast.ref.http__is_upgrade">is_upgrade</link></member>
|
<member><link linkend="beast.ref.http__is_upgrade">is_upgrade</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__make_write_stream">make_write_stream</link></member>
|
||||||
<member><link linkend="beast.ref.http__operator_ls_">operator<<</link></member>
|
<member><link linkend="beast.ref.http__operator_ls_">operator<<</link></member>
|
||||||
<member><link linkend="beast.ref.http__prepare">prepare</link></member>
|
<member><link linkend="beast.ref.http__prepare">prepare</link></member>
|
||||||
<member><link linkend="beast.ref.http__read">read</link></member>
|
<member><link linkend="beast.ref.http__read">read</link></member>
|
||||||
<member><link linkend="beast.ref.http__read_some">read_some</link></member>
|
<member><link linkend="beast.ref.http__read_some">read_some</link></member>
|
||||||
<member><link linkend="beast.ref.http__reason_string">reason_string</link></member>
|
<member><link linkend="beast.ref.http__reason_string">reason_string</link></member>
|
||||||
|
<member><link linkend="beast.ref.http__swap">swap</link></member>
|
||||||
<member><link linkend="beast.ref.http__write">write</link></member>
|
<member><link linkend="beast.ref.http__write">write</link></member>
|
||||||
</simplelist>
|
</simplelist>
|
||||||
<bridgehead renderas="sect3">Type Traits</bridgehead>
|
<bridgehead renderas="sect3">Type Traits</bridgehead>
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
[/
|
|
||||||
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:Writer Writer requirements]
|
|
||||||
|
|
||||||
A `Writer` serializes the message body. The implementation creates an instance
|
|
||||||
of this type when serializing a message, and calls into it zero or more times
|
|
||||||
to provide buffers containing the data. The interface of `Writer` is intended
|
|
||||||
to allow serialization in these scenarios:
|
|
||||||
|
|
||||||
* A body that does not entirely fit in memory.
|
|
||||||
* A body produced incrementally from coroutine output.
|
|
||||||
* A body represented by zero or more buffers already in memory.
|
|
||||||
* A body as a series of buffers when the content size is not known ahead of time.
|
|
||||||
* Body data generated on demand from other threads.
|
|
||||||
* Body data computed algorithmically.
|
|
||||||
|
|
||||||
In this table:
|
|
||||||
|
|
||||||
* `X` denotes a type meeting the requirements of `Writer`.
|
|
||||||
|
|
||||||
* `a` denotes a value of type `X`.
|
|
||||||
|
|
||||||
* `m` denotes a value of type `message const&` where
|
|
||||||
`std::is_same<decltype(m.body), Body::value_type>:value == true`.
|
|
||||||
|
|
||||||
* `ec` is a value of type [link beast.ref.error_code `error_code&`]
|
|
||||||
|
|
||||||
* `wf` is a [*write function]: a function object of unspecified type provided
|
|
||||||
by the implementation which accepts any value meeting the requirements
|
|
||||||
of __ConstBufferSequence__ as its single parameter.
|
|
||||||
|
|
||||||
[table Writer requirements
|
|
||||||
[[operation] [type] [semantics, pre/post-conditions]]
|
|
||||||
[
|
|
||||||
[`X a(m);`]
|
|
||||||
[]
|
|
||||||
[
|
|
||||||
`a` is constructible from `m`. The lifetime of `m` is guaranteed
|
|
||||||
to end no earlier than after `a` is destroyed. This function must
|
|
||||||
be `noexcept`.
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[
|
|
||||||
[`a.init(ec)`]
|
|
||||||
[`void`]
|
|
||||||
[
|
|
||||||
Called immediately after construction. If the function sets an
|
|
||||||
error code in `ec`, the serialization is aborted and the error
|
|
||||||
is propagated to the caller. This function must be `noexcept`.
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[
|
|
||||||
[`a.content_length()`]
|
|
||||||
[`std::uint64_t`]
|
|
||||||
[
|
|
||||||
If this member is present, it is called after initialization
|
|
||||||
and before calls to provide buffers. The serialized message will
|
|
||||||
have the Content-Length field set to the value returned from
|
|
||||||
this function. If this member is absent, the serialized message
|
|
||||||
body will be chunk-encoded for HTTP versions 1.1 and later, else
|
|
||||||
the serialized message body will be sent unmodified, with the
|
|
||||||
error `boost::asio::error::eof` returned to the caller, to notify
|
|
||||||
they should close the connection to indicate the end of the message.
|
|
||||||
This function must be `noexcept`.
|
|
||||||
]
|
|
||||||
]
|
|
||||||
[
|
|
||||||
[`a.write(ec, wf)`]
|
|
||||||
[`bool`]
|
|
||||||
[
|
|
||||||
Called repeatedly after `init` succeeds. `wf` is a function object
|
|
||||||
which takes as its single parameter any value meeting the requirements
|
|
||||||
of __ConstBufferSequence__. Buffers provided to this write function
|
|
||||||
must remain valid until the next member function of `writer` is
|
|
||||||
invoked (which may be the destructor). This function returns `true`
|
|
||||||
to indicate all message body data has been written, or `false` if
|
|
||||||
there is more body data.
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
[note
|
|
||||||
Definitions for required `Writer` member functions should be declared
|
|
||||||
inline so the generated code can become part of the implementation.
|
|
||||||
]
|
|
||||||
|
|
||||||
Exemplar:
|
|
||||||
```
|
|
||||||
struct writer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/** Construct the writer.
|
|
||||||
|
|
||||||
The msg object is guaranteed to exist for the lifetime of the writer.
|
|
||||||
|
|
||||||
Exceptions:
|
|
||||||
No-throw guarantee.
|
|
||||||
|
|
||||||
@param msg The message whose body is to be written.
|
|
||||||
*/
|
|
||||||
template<bool isRequest, class Body, class Headers>
|
|
||||||
explicit
|
|
||||||
writer(message<isRequest, Body, Headers> const& msg) noexcept;
|
|
||||||
|
|
||||||
/** Initialize the writer.
|
|
||||||
|
|
||||||
Called once immediately after construction.
|
|
||||||
The writer can perform initialization which may fail.
|
|
||||||
|
|
||||||
@param ec Contains the error code if any errors occur.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
init(error_code& ec) noexcept;
|
|
||||||
|
|
||||||
/** Returns the content length.
|
|
||||||
|
|
||||||
If this member is present, the implementation will set the
|
|
||||||
Content-Length field accordingly. If absent, the implementation will
|
|
||||||
use chunk-encoding or terminate the connection to indicate the end
|
|
||||||
of the message.
|
|
||||||
*/
|
|
||||||
std::uint64_t
|
|
||||||
content_length() noexcept;
|
|
||||||
|
|
||||||
/** Write zero or one buffer representing the message body.
|
|
||||||
|
|
||||||
Postconditions:
|
|
||||||
|
|
||||||
If return value is `true`:
|
|
||||||
* Callee made zero or one calls to `write`.
|
|
||||||
* There is no more data remaining to write.
|
|
||||||
|
|
||||||
If return value is `false`:
|
|
||||||
* Callee does not take ownership of resume.
|
|
||||||
* Callee made one call to `write`.
|
|
||||||
|
|
||||||
@param ec Set to indicate an error. This will cause an
|
|
||||||
asynchronous write operation to complete with the error.
|
|
||||||
|
|
||||||
@param write A functor the writer will call to provide the next
|
|
||||||
set of buffers. Ownership of the buffers is not transferred,
|
|
||||||
the writer must guarantee that the buffers remain valid until the
|
|
||||||
next member function is invoked, which may be the destructor.
|
|
||||||
|
|
||||||
@return `true` if there is no more data to send,
|
|
||||||
`false` when there may be more data.
|
|
||||||
*/
|
|
||||||
template<class WriteFunction>
|
|
||||||
bool
|
|
||||||
write(
|
|
||||||
error_code&,
|
|
||||||
WriteFunction&& wf) noexcept;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
[endsect]
|
|
@ -13,6 +13,7 @@
|
|||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@ -33,12 +34,18 @@ struct file_body
|
|||||||
std::size_t buf_len_;
|
std::size_t buf_len_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using is_deferred = std::true_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
|
writer(writer&&) = default;
|
||||||
writer(writer const&) = delete;
|
writer(writer const&) = delete;
|
||||||
writer& operator=(writer const&) = delete;
|
writer& operator=(writer const&) = delete;
|
||||||
|
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
writer(message<isRequest,
|
writer(message<isRequest,
|
||||||
file_body, Fields> const& m) noexcept
|
file_body, Fields> const& m)
|
||||||
: path_(m.body)
|
: path_(m.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -50,7 +57,7 @@ struct file_body
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
init(error_code& ec) noexcept
|
init(error_code& ec)
|
||||||
{
|
{
|
||||||
file_ = fopen(path_.c_str(), "rb");
|
file_ = fopen(path_.c_str(), "rb");
|
||||||
if(! file_)
|
if(! file_)
|
||||||
@ -61,14 +68,13 @@ struct file_body
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t
|
std::uint64_t
|
||||||
content_length() const noexcept
|
content_length() const
|
||||||
{
|
{
|
||||||
return size_;
|
return size_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class WriteFunction>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
bool
|
get(error_code& ec)
|
||||||
write(error_code& ec, WriteFunction&& wf) noexcept
|
|
||||||
{
|
{
|
||||||
if(size_ - offset_ < sizeof(buf_))
|
if(size_ - offset_ < sizeof(buf_))
|
||||||
buf_len_ = static_cast<std::size_t>(
|
buf_len_ = static_cast<std::size_t>(
|
||||||
@ -79,14 +85,13 @@ struct file_body
|
|||||||
buf_, 1, sizeof(buf_), file_);
|
buf_, 1, sizeof(buf_), file_);
|
||||||
if(ferror(file_))
|
if(ferror(file_))
|
||||||
{
|
{
|
||||||
ec = error_code(errno,
|
ec = error_code(errno, system_category());
|
||||||
system_category());
|
return boost::none;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
BOOST_ASSERT(nread != 0);
|
BOOST_ASSERT(nread != 0);
|
||||||
offset_ += nread;
|
offset_ += nread;
|
||||||
wf(boost::asio::buffer(buf_, nread));
|
return {{const_buffers_type{buf_, nread},
|
||||||
return offset_ >= size_;
|
offset_ >= size_}};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -114,9 +114,9 @@ public:
|
|||||||
write_some(
|
write_some(
|
||||||
ConstBufferSequence const& buffers, error_code&)
|
ConstBufferSequence const& buffers, error_code&)
|
||||||
{
|
{
|
||||||
auto const n = buffer_size(buffers);
|
|
||||||
using boost::asio::buffer_size;
|
using boost::asio::buffer_size;
|
||||||
using boost::asio::buffer_cast;
|
using boost::asio::buffer_cast;
|
||||||
|
auto const n = buffer_size(buffers);
|
||||||
str.reserve(str.size() + n);
|
str.reserve(str.size() + n);
|
||||||
for(auto const& buffer : buffers)
|
for(auto const& buffer : buffers)
|
||||||
str.append(buffer_cast<char const*>(buffer),
|
str.append(buffer_cast<char const*>(buffer),
|
||||||
|
@ -11,8 +11,9 @@
|
|||||||
#include <beast/config.hpp>
|
#include <beast/config.hpp>
|
||||||
|
|
||||||
#include <beast/http/basic_parser.hpp>
|
#include <beast/http/basic_parser.hpp>
|
||||||
#include <beast/http/chunk_encode.hpp>
|
#include <beast/http/buffer_body.hpp>
|
||||||
#include <beast/http/dynamic_body.hpp>
|
#include <beast/http/dynamic_body.hpp>
|
||||||
|
#include <beast/http/empty_body.hpp>
|
||||||
#include <beast/http/error.hpp>
|
#include <beast/http/error.hpp>
|
||||||
#include <beast/http/fields.hpp>
|
#include <beast/http/fields.hpp>
|
||||||
#include <beast/http/header_parser.hpp>
|
#include <beast/http/header_parser.hpp>
|
||||||
|
126
include/beast/http/buffer_body.hpp
Normal file
126
include/beast/http/buffer_body.hpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BEAST_HTTP_BUFFER_BODY_HPP
|
||||||
|
#define BEAST_HTTP_BUFFER_BODY_HPP
|
||||||
|
|
||||||
|
#include <beast/http/concepts.hpp>
|
||||||
|
#include <beast/http/error.hpp>
|
||||||
|
#include <beast/http/message.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
/** A serializable body represented by caller provided buffers.
|
||||||
|
|
||||||
|
This body type permits incremental message sending of caller
|
||||||
|
provided buffers using a @ref serializer.
|
||||||
|
|
||||||
|
@par Example
|
||||||
|
@code
|
||||||
|
template<class SyncWriteStream>
|
||||||
|
void send(SyncWriteStream& stream)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
@tparam isDeferred A `bool` which, when set to `true`,
|
||||||
|
indicates to the serialization implementation that it should
|
||||||
|
send the entire HTTP Header before attempting to acquire
|
||||||
|
buffers representing the body.
|
||||||
|
|
||||||
|
@tparam ConstBufferSequence The type of buffer sequence
|
||||||
|
stored in the body by the caller.
|
||||||
|
*/
|
||||||
|
template<
|
||||||
|
bool isDeferred,
|
||||||
|
class ConstBufferSequence>
|
||||||
|
struct buffer_body
|
||||||
|
{
|
||||||
|
static_assert(is_const_buffer_sequence<ConstBufferSequence>::value,
|
||||||
|
"ConstBufferSequence requirements not met");
|
||||||
|
|
||||||
|
/** The type of the body member when used in a message.
|
||||||
|
|
||||||
|
When engaged, the first element of the pair represents
|
||||||
|
the current buffer sequence to be written.
|
||||||
|
|
||||||
|
The second element of the pair indicates whether or not
|
||||||
|
additional buffers will be available. A value of `false`
|
||||||
|
indicates the end of the message body.
|
||||||
|
|
||||||
|
If the buffer in the value is disengaged, and the second
|
||||||
|
element of the pair is `true`, @ref serializer operations
|
||||||
|
will return @ref http::error::need_more. This signals the
|
||||||
|
calling code that a new buffer should be placed into the
|
||||||
|
body, or that the caller should indicate that no more
|
||||||
|
buffers will be available.
|
||||||
|
*/
|
||||||
|
using value_type =
|
||||||
|
std::pair<boost::optional<ConstBufferSequence>, bool>;
|
||||||
|
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
|
/// The algorithm used when serializing this body
|
||||||
|
using writer = implementation_defined;
|
||||||
|
#else
|
||||||
|
class writer
|
||||||
|
{
|
||||||
|
bool toggle_ = false;
|
||||||
|
value_type const& body_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using is_deferred =
|
||||||
|
std::integral_constant<bool, isDeferred>;
|
||||||
|
|
||||||
|
using const_buffers_type = ConstBufferSequence;
|
||||||
|
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
explicit
|
||||||
|
writer(message<isRequest, buffer_body,
|
||||||
|
Fields> const& msg)
|
||||||
|
: body_(msg.body)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init(error_code&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<ConstBufferSequence, bool>>
|
||||||
|
get(error_code& ec)
|
||||||
|
{
|
||||||
|
if(toggle_)
|
||||||
|
{
|
||||||
|
if(body_.second)
|
||||||
|
{
|
||||||
|
toggle_ = false;
|
||||||
|
ec = error::need_more;
|
||||||
|
}
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
if(body_.first)
|
||||||
|
{
|
||||||
|
toggle_ = true;
|
||||||
|
return {{*body_.first, body_.second}};
|
||||||
|
}
|
||||||
|
if(body_.second)
|
||||||
|
ec = error::need_more;
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
|
||||||
|
#endif
|
@ -1,75 +0,0 @@
|
|||||||
//
|
|
||||||
// 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)
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef BEAST_HTTP_CHUNK_ENCODE_HPP
|
|
||||||
#define BEAST_HTTP_CHUNK_ENCODE_HPP
|
|
||||||
|
|
||||||
#include <beast/config.hpp>
|
|
||||||
#include <beast/core/buffer_cat.hpp>
|
|
||||||
#include <beast/http/detail/chunk_encode.hpp>
|
|
||||||
#include <boost/asio/buffer.hpp>
|
|
||||||
#include <boost/assert.hpp>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <iterator>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace beast {
|
|
||||||
namespace http {
|
|
||||||
|
|
||||||
/** Returns a chunk-encoded ConstBufferSequence.
|
|
||||||
|
|
||||||
This returns a buffer sequence representing the
|
|
||||||
first chunk of a chunked transfer coded body.
|
|
||||||
|
|
||||||
@param fin `true` if this is the last chunk.
|
|
||||||
|
|
||||||
@param buffers The input buffer sequence.
|
|
||||||
|
|
||||||
@return A chunk-encoded ConstBufferSequence representing the input.
|
|
||||||
|
|
||||||
@see <a href=https://tools.ietf.org/html/rfc7230#section-4.1.3>rfc7230 section 4.1.3</a>
|
|
||||||
*/
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
#if BEAST_DOXYGEN
|
|
||||||
implementation_defined
|
|
||||||
#else
|
|
||||||
beast::buffers_view<
|
|
||||||
detail::chunk_encode_delim,
|
|
||||||
ConstBufferSequence,
|
|
||||||
boost::asio::const_buffers_1>
|
|
||||||
#endif
|
|
||||||
chunk_encode(bool fin, ConstBufferSequence const& buffers)
|
|
||||||
{
|
|
||||||
using boost::asio::buffer_size;
|
|
||||||
return buffer_cat(
|
|
||||||
detail::chunk_encode_delim{buffer_size(buffers)},
|
|
||||||
buffers,
|
|
||||||
fin ? boost::asio::const_buffers_1{"\r\n0\r\n\r\n", 7}
|
|
||||||
: boost::asio::const_buffers_1{"\r\n", 2});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a chunked encoding final chunk.
|
|
||||||
|
|
||||||
@see <a href=https://tools.ietf.org/html/rfc7230#section-4.1.3>rfc7230 section 4.1.3</a>
|
|
||||||
*/
|
|
||||||
inline
|
|
||||||
#if BEAST_DOXYGEN
|
|
||||||
implementation_defined
|
|
||||||
#else
|
|
||||||
boost::asio::const_buffers_1
|
|
||||||
#endif
|
|
||||||
chunk_encode_final()
|
|
||||||
{
|
|
||||||
return boost::asio::const_buffers_1{"0\r\n\r\n", 5};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // http
|
|
||||||
} // beast
|
|
||||||
|
|
||||||
#endif
|
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <beast/config.hpp>
|
#include <beast/config.hpp>
|
||||||
#include <beast/core/error.hpp>
|
#include <beast/core/error.hpp>
|
||||||
|
#include <beast/core/string_view.hpp>
|
||||||
#include <beast/core/type_traits.hpp>
|
#include <beast/core/type_traits.hpp>
|
||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
@ -19,8 +20,18 @@
|
|||||||
namespace beast {
|
namespace beast {
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
|
template<bool, class, class>
|
||||||
|
struct message;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
struct fields_model
|
||||||
|
{
|
||||||
|
string_view method() const;
|
||||||
|
string_view reason() const;
|
||||||
|
string_view target() const;
|
||||||
|
};
|
||||||
|
|
||||||
struct write_function
|
struct write_function
|
||||||
{
|
{
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
@ -50,43 +61,6 @@ struct has_content_length<T, beast::detail::void_t<decltype(
|
|||||||
"Writer::content_length requirements not met");
|
"Writer::content_length requirements not met");
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T, class M>
|
|
||||||
class is_Writer
|
|
||||||
{
|
|
||||||
template<class U, class R = decltype(
|
|
||||||
std::declval<U>().init(std::declval<error_code&>()),
|
|
||||||
std::true_type{})>
|
|
||||||
static R check1(int);
|
|
||||||
template<class>
|
|
||||||
static std::false_type check1(...);
|
|
||||||
using type1 = decltype(check1<T>(0));
|
|
||||||
|
|
||||||
// VFALCO This is unfortunate, we have to provide the template
|
|
||||||
// argument type because this is not a deduced context?
|
|
||||||
//
|
|
||||||
template<class U, class R =
|
|
||||||
std::is_convertible<decltype(
|
|
||||||
std::declval<U>().template write<detail::write_function>(
|
|
||||||
std::declval<error_code&>(),
|
|
||||||
std::declval<detail::write_function>()))
|
|
||||||
, bool>>
|
|
||||||
static R check2(int);
|
|
||||||
template<class>
|
|
||||||
static std::false_type check2(...);
|
|
||||||
using type2 = decltype(check2<T>(0));
|
|
||||||
|
|
||||||
public:
|
|
||||||
static_assert(std::is_same<
|
|
||||||
typename M::body_type::writer, T>::value,
|
|
||||||
"Mismatched writer and message");
|
|
||||||
|
|
||||||
using type = std::integral_constant<bool,
|
|
||||||
std::is_nothrow_constructible<T, M const&>::value
|
|
||||||
&& type1::value
|
|
||||||
&& type2::value
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // detail
|
} // detail
|
||||||
|
|
||||||
/// Determine if `T` meets the requirements of @b Body.
|
/// Determine if `T` meets the requirements of @b Body.
|
||||||
@ -173,18 +147,34 @@ struct is_Reader<T, M, beast::detail::void_t<decltype(
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Determine if `T` meets the requirements of @b Writer for `M`.
|
/** Determine if a @b Body type has a writer which meets requirements.
|
||||||
|
|
||||||
@tparam T The type to test.
|
@tparam T The body type to test.
|
||||||
|
|
||||||
@tparam M The message type to test with, which must be of
|
|
||||||
type `message`.
|
|
||||||
*/
|
*/
|
||||||
template<class T, class M>
|
|
||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
|
template<class T>
|
||||||
struct is_Writer : std::integral_constant<bool, ...> {};
|
struct is_Writer : std::integral_constant<bool, ...> {};
|
||||||
#else
|
#else
|
||||||
using is_Writer = typename detail::is_Writer<T, M>::type;
|
template<class T, class = void>
|
||||||
|
struct is_Writer : std::false_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_Writer<T, beast::detail::void_t<
|
||||||
|
typename T::writer,
|
||||||
|
typename T::writer::const_buffers_type,
|
||||||
|
decltype(
|
||||||
|
std::declval<typename T::writer&>().init(std::declval<error_code&>()),
|
||||||
|
std::declval<boost::optional<std::pair<
|
||||||
|
typename T::writer::const_buffers_type, bool>>&>() =
|
||||||
|
std::declval<typename T::writer>().get(std::declval<error_code&>()),
|
||||||
|
(void)0)>> : std::integral_constant<bool,
|
||||||
|
is_const_buffer_sequence<
|
||||||
|
typename T::writer::const_buffers_type>::value &&
|
||||||
|
std::is_constructible<typename T::writer,
|
||||||
|
message<true, T, detail::fields_model> const& >::value
|
||||||
|
>
|
||||||
|
{
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // http
|
} // http
|
||||||
|
@ -17,20 +17,22 @@ namespace beast {
|
|||||||
namespace http {
|
namespace http {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
class chunk_encode_delim
|
/** A buffer sequence containing a chunk-encoding header
|
||||||
|
*/
|
||||||
|
class chunk_header
|
||||||
{
|
{
|
||||||
boost::asio::const_buffer cb_;
|
boost::asio::const_buffer cb_;
|
||||||
|
|
||||||
// Storage for the longest hex string we might need, plus delimiters.
|
// Storage for the longest hex string we might need
|
||||||
std::array<char, 2 * sizeof(std::size_t) + 2> buf_;
|
char buf_[2 * sizeof(std::size_t)];
|
||||||
|
|
||||||
template<class = void>
|
template<class = void>
|
||||||
void
|
void
|
||||||
copy(chunk_encode_delim const& other);
|
copy(chunk_header const& other);
|
||||||
|
|
||||||
template<class = void>
|
template<class = void>
|
||||||
void
|
void
|
||||||
setup(std::size_t n);
|
prepare_impl(std::size_t n);
|
||||||
|
|
||||||
template<class OutIter>
|
template<class OutIter>
|
||||||
static
|
static
|
||||||
@ -55,15 +57,27 @@ public:
|
|||||||
|
|
||||||
using const_iterator = value_type const*;
|
using const_iterator = value_type const*;
|
||||||
|
|
||||||
chunk_encode_delim(chunk_encode_delim const& other)
|
/** Constructor (default)
|
||||||
|
|
||||||
|
Default-constructed chunk headers are in an
|
||||||
|
undefined state.
|
||||||
|
*/
|
||||||
|
chunk_header() = default;
|
||||||
|
|
||||||
|
/// Copy constructor
|
||||||
|
chunk_header(chunk_header const& other)
|
||||||
{
|
{
|
||||||
copy(other);
|
copy(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Construct a chunk header
|
||||||
|
|
||||||
|
@param n The number of octets in this chunk.
|
||||||
|
*/
|
||||||
explicit
|
explicit
|
||||||
chunk_encode_delim(std::size_t n)
|
chunk_header(std::size_t n)
|
||||||
{
|
{
|
||||||
setup(n);
|
prepare_impl(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
const_iterator
|
const_iterator
|
||||||
@ -77,31 +91,63 @@ public:
|
|||||||
{
|
{
|
||||||
return begin() + 1;
|
return begin() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
prepare(std::size_t n)
|
||||||
|
{
|
||||||
|
prepare_impl(n);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class>
|
template<class>
|
||||||
void
|
void
|
||||||
chunk_encode_delim::
|
chunk_header::
|
||||||
copy(chunk_encode_delim const& other)
|
copy(chunk_header const& other)
|
||||||
{
|
{
|
||||||
|
using boost::asio::buffer_copy;
|
||||||
auto const n =
|
auto const n =
|
||||||
boost::asio::buffer_size(other.cb_);
|
boost::asio::buffer_size(other.cb_);
|
||||||
buf_ = other.buf_;
|
auto const mb = boost::asio::mutable_buffers_1(
|
||||||
cb_ = boost::asio::const_buffer(
|
&buf_[sizeof(buf_) - n], n);
|
||||||
&buf_[buf_.size() - n], n);
|
cb_ = *mb.begin();
|
||||||
|
buffer_copy(mb,
|
||||||
|
boost::asio::const_buffers_1(other.cb_));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class>
|
template<class>
|
||||||
void
|
void
|
||||||
chunk_encode_delim::
|
chunk_header::
|
||||||
setup(std::size_t n)
|
prepare_impl(std::size_t n)
|
||||||
{
|
{
|
||||||
buf_[buf_.size() - 2] = '\r';
|
auto const end = &buf_[sizeof(buf_)];
|
||||||
buf_[buf_.size() - 1] = '\n';
|
auto it = to_hex(end, n);
|
||||||
auto it = to_hex(buf_.end() - 2, n);
|
|
||||||
cb_ = boost::asio::const_buffer{&*it,
|
cb_ = boost::asio::const_buffer{&*it,
|
||||||
static_cast<std::size_t>(
|
static_cast<std::size_t>(
|
||||||
std::distance(it, buf_.end()))};
|
std::distance(it, end))};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a buffer sequence holding a CRLF for chunk encoding
|
||||||
|
inline
|
||||||
|
boost::asio::const_buffers_1
|
||||||
|
chunk_crlf()
|
||||||
|
{
|
||||||
|
return {"\r\n", 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a buffer sequence holding a CRLF then final chunk
|
||||||
|
inline
|
||||||
|
boost::asio::const_buffers_1
|
||||||
|
chunk_crlf_final()
|
||||||
|
{
|
||||||
|
return {"\r\n0\r\n\r\n", 7};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a buffer sequence holding a final chunk header
|
||||||
|
inline
|
||||||
|
boost::asio::const_buffers_1
|
||||||
|
chunk_final()
|
||||||
|
{
|
||||||
|
return {"0\r\n", 3};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // detail
|
} // detail
|
||||||
|
39
include/beast/http/detail/type_traits.hpp
Normal file
39
include/beast/http/detail/type_traits.hpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP
|
||||||
|
#define BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP
|
||||||
|
|
||||||
|
#include <beast/core/detail/type_traits.hpp>
|
||||||
|
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
struct header;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class is_header_impl
|
||||||
|
{
|
||||||
|
template<bool b, class F>
|
||||||
|
static std::true_type check(
|
||||||
|
header<b, F> const*);
|
||||||
|
static std::false_type check(...);
|
||||||
|
public:
|
||||||
|
using type = decltype(check((T*)0));
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_header = typename is_header_impl<T>::type;
|
||||||
|
|
||||||
|
} // detail
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
|
||||||
|
#endif
|
@ -12,24 +12,26 @@
|
|||||||
#include <beast/core/error.hpp>
|
#include <beast/core/error.hpp>
|
||||||
#include <beast/core/multi_buffer.hpp>
|
#include <beast/core/multi_buffer.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace beast {
|
namespace beast {
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
/** A message body represented by a @b DynamicBuffer
|
/** An HTTP message body represented by a @b DynamicBuffer.
|
||||||
|
|
||||||
Meets the requirements of @b Body.
|
Meets the requirements of @b Body.
|
||||||
*/
|
*/
|
||||||
template<class DynamicBuffer>
|
template<class DynamicBuffer>
|
||||||
struct basic_dynamic_body
|
struct basic_dynamic_body
|
||||||
{
|
{
|
||||||
/// The type of the `message::body` member
|
/// The type of the body member when used in a message.
|
||||||
using value_type = DynamicBuffer;
|
using value_type = DynamicBuffer;
|
||||||
|
|
||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
private:
|
/// The algorithm used when parsing this body.
|
||||||
#endif
|
using reader = implementation_defined;
|
||||||
|
#else
|
||||||
class reader
|
class reader
|
||||||
{
|
{
|
||||||
value_type& body_;
|
value_type& body_;
|
||||||
@ -75,40 +77,48 @@ private:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
|
/// The algorithm used when serializing this body.
|
||||||
|
using writer = implementation_defined;
|
||||||
|
#else
|
||||||
class writer
|
class writer
|
||||||
{
|
{
|
||||||
DynamicBuffer const& body_;
|
DynamicBuffer const& body_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
typename DynamicBuffer::const_buffers_type;
|
||||||
|
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
explicit
|
explicit
|
||||||
writer(message<
|
writer(message<
|
||||||
isRequest, basic_dynamic_body, Fields> const& m) noexcept
|
isRequest, basic_dynamic_body, Fields> const& m)
|
||||||
: body_(m.body)
|
: body_(m.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
init(error_code& ec) noexcept
|
init(error_code&)
|
||||||
{
|
{
|
||||||
beast::detail::ignore_unused(ec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t
|
std::uint64_t
|
||||||
content_length() const noexcept
|
content_length() const
|
||||||
{
|
{
|
||||||
return body_.size();
|
return body_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class WriteFunction>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
bool
|
get(error_code& ec)
|
||||||
write(error_code&, WriteFunction&& wf) noexcept
|
|
||||||
{
|
{
|
||||||
wf(body_.data());
|
return {{body_.data(), false}};
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A dynamic message body represented by a @ref multi_buffer
|
/** A dynamic message body represented by a @ref multi_buffer
|
||||||
|
72
include/beast/http/empty_body.hpp
Normal file
72
include/beast/http/empty_body.hpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BEAST_HTTP_EMPTY_BODY_HPP
|
||||||
|
#define BEAST_HTTP_EMPTY_BODY_HPP
|
||||||
|
|
||||||
|
#include <beast/config.hpp>
|
||||||
|
#include <beast/http/message.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
/** An empty message body
|
||||||
|
|
||||||
|
Meets the requirements of @b Body.
|
||||||
|
|
||||||
|
@note This body type may only be written, not read.
|
||||||
|
*/
|
||||||
|
struct empty_body
|
||||||
|
{
|
||||||
|
/// The type of the body member when used in a message.
|
||||||
|
struct value_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
|
/// The algorithm used when serializing this body.
|
||||||
|
using writer = implementation_defined;
|
||||||
|
#else
|
||||||
|
struct writer
|
||||||
|
{
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::null_buffers;
|
||||||
|
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
explicit
|
||||||
|
writer(message<
|
||||||
|
isRequest, empty_body, Fields> const&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init(error_code&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t
|
||||||
|
content_length() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(error_code& ec)
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
|
||||||
|
#endif
|
@ -33,6 +33,17 @@ enum class error
|
|||||||
*/
|
*/
|
||||||
partial_message,
|
partial_message,
|
||||||
|
|
||||||
|
/** Additional buffers are required.
|
||||||
|
|
||||||
|
This error is generated during serialization of HTTP
|
||||||
|
messages using the @ref buffer_body representation.
|
||||||
|
It indicates to the caller that an additional buffer
|
||||||
|
sequence should be placed into the body, or that the
|
||||||
|
caller should indicate that there are no more bytes
|
||||||
|
remaining in the body to serialize.
|
||||||
|
*/
|
||||||
|
need_more,
|
||||||
|
|
||||||
/** Buffer maximum exceeded.
|
/** Buffer maximum exceeded.
|
||||||
|
|
||||||
This error is returned when reading HTTP content
|
This error is returned when reading HTTP content
|
||||||
|
@ -41,6 +41,7 @@ public:
|
|||||||
default:
|
default:
|
||||||
case error::end_of_stream: return "end of stream";
|
case error::end_of_stream: return "end of stream";
|
||||||
case error::partial_message: return "partial message";
|
case error::partial_message: return "partial message";
|
||||||
|
case error::need_more: return "need more";
|
||||||
case error::buffer_overflow: return "buffer overflow";
|
case error::buffer_overflow: return "buffer overflow";
|
||||||
case error::bad_line_ending: return "bad line ending";
|
case error::bad_line_ending: return "bad line ending";
|
||||||
case error::bad_method: return "bad method";
|
case error::bad_method: return "bad method";
|
||||||
|
@ -163,8 +163,7 @@ prepare(message<isRequest, Body, Fields>& msg,
|
|||||||
"Body requirements not met");
|
"Body requirements not met");
|
||||||
static_assert(has_writer<Body>::value,
|
static_assert(has_writer<Body>::value,
|
||||||
"Body has no writer");
|
"Body has no writer");
|
||||||
static_assert(is_Writer<typename Body::writer,
|
static_assert(is_Writer<Body>::value,
|
||||||
message<isRequest, Body, Fields>>::value,
|
|
||||||
"Writer requirements not met");
|
"Writer requirements not met");
|
||||||
detail::prepare_info pi;
|
detail::prepare_info pi;
|
||||||
detail::prepare_content_length(pi, msg,
|
detail::prepare_content_length(pi, msg,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -13,25 +13,27 @@
|
|||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
#include <beast/core/detail/type_traits.hpp>
|
#include <beast/core/detail/type_traits.hpp>
|
||||||
#include <boost/asio/buffer.hpp>
|
#include <boost/asio/buffer.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace beast {
|
namespace beast {
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
/** A Body represented by a std::string.
|
/** An HTTP message body represented by a `std::string`.
|
||||||
|
|
||||||
Meets the requirements of @b Body.
|
Meets the requirements of @b Body.
|
||||||
*/
|
*/
|
||||||
struct string_body
|
struct string_body
|
||||||
{
|
{
|
||||||
/// The type of the `message::body` member
|
/// The type of the body member when used in a message.
|
||||||
using value_type = std::string;
|
using value_type = std::string;
|
||||||
|
|
||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
private:
|
/// The algorithm used when parsing this body.
|
||||||
#endif
|
using reader = implementation_defined;
|
||||||
|
#else
|
||||||
class reader
|
class reader
|
||||||
{
|
{
|
||||||
value_type& body_;
|
value_type& body_;
|
||||||
@ -88,40 +90,50 @@ private:
|
|||||||
body_.resize(len_);
|
body_.resize(len_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
|
/// The algorithm used when serializing this body.
|
||||||
|
using writer = implementation_defined;
|
||||||
|
#else
|
||||||
class writer
|
class writer
|
||||||
{
|
{
|
||||||
value_type const& body_;
|
value_type const& body_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
explicit
|
explicit
|
||||||
writer(message<
|
writer(message<
|
||||||
isRequest, string_body, Fields> const& msg) noexcept
|
isRequest, string_body, Fields> const& msg)
|
||||||
: body_(msg.body)
|
: body_(msg.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
init(error_code& ec) noexcept
|
init(error_code& ec)
|
||||||
{
|
{
|
||||||
beast::detail::ignore_unused(ec);
|
beast::detail::ignore_unused(ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t
|
std::uint64_t
|
||||||
content_length() const noexcept
|
content_length() const
|
||||||
{
|
{
|
||||||
return body_.size();
|
return body_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class WriteFunction>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
bool
|
get(error_code& ec)
|
||||||
write(error_code&, WriteFunction&& wf) noexcept
|
|
||||||
{
|
{
|
||||||
wf(boost::asio::buffer(body_));
|
return {{const_buffers_type{
|
||||||
return true;
|
body_.data(), body_.size()}, false}};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // http
|
} // http
|
||||||
|
@ -9,127 +9,424 @@
|
|||||||
#define BEAST_HTTP_WRITE_HPP
|
#define BEAST_HTTP_WRITE_HPP
|
||||||
|
|
||||||
#include <beast/config.hpp>
|
#include <beast/config.hpp>
|
||||||
|
#include <beast/core/buffer_cat.hpp>
|
||||||
|
#include <beast/core/consuming_buffers.hpp>
|
||||||
|
#include <beast/core/multi_buffer.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
|
#include <beast/http/detail/chunk_encode.hpp>
|
||||||
#include <beast/core/error.hpp>
|
#include <beast/core/error.hpp>
|
||||||
#include <beast/core/async_result.hpp>
|
#include <beast/core/async_result.hpp>
|
||||||
|
#include <beast/core/string_view.hpp>
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace beast {
|
namespace beast {
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
/** Write a HTTP/1 header to a stream.
|
/** A chunk decorator which does nothing.
|
||||||
|
|
||||||
This function is used to synchronously write a header to
|
When selected as a chunk decorator, objects of this type
|
||||||
a stream. The call will block until one of the following
|
affect the output of messages specifying chunked
|
||||||
conditions is true:
|
transfer encodings as follows:
|
||||||
|
|
||||||
@li The entire header is written.
|
@li chunk headers will have empty chunk extensions, and
|
||||||
|
|
||||||
@li An error occurs.
|
@li final chunks will have an empty set of trailers.
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls
|
@see @ref serializer
|
||||||
to the stream's `write_some` function.
|
|
||||||
|
|
||||||
Regardless of the semantic meaning of the header (for example,
|
|
||||||
specifying "Content-Length: 0" and "Connection: close"),
|
|
||||||
this function will not return `boost::asio::error::eof`.
|
|
||||||
|
|
||||||
@param stream The stream to which the data is to be written.
|
|
||||||
The type must support the @b SyncWriteStream concept.
|
|
||||||
|
|
||||||
@param msg The header to write.
|
|
||||||
|
|
||||||
@throws system_error Thrown on failure.
|
|
||||||
*/
|
*/
|
||||||
template<class SyncWriteStream,
|
struct empty_decorator
|
||||||
bool isRequest, class Fields>
|
{
|
||||||
void
|
template<class ConstBufferSequence>
|
||||||
write(SyncWriteStream& stream,
|
string_view
|
||||||
header<isRequest, Fields> const& msg);
|
operator()(ConstBufferSequence const&) const
|
||||||
|
{
|
||||||
|
return {"\r\n"};
|
||||||
|
}
|
||||||
|
|
||||||
/** Write a HTTP/1 header to a stream.
|
string_view
|
||||||
|
operator()(boost::asio::null_buffers) const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
This function is used to synchronously write a header to
|
/** Provides stream-oriented HTTP message serialization functionality.
|
||||||
a stream. The call will block until one of the following
|
|
||||||
conditions is true:
|
|
||||||
|
|
||||||
@li The entire header is written.
|
Objects of this type may be used to perform synchronous or
|
||||||
|
asynchronous serialization of an HTTP message on a stream.
|
||||||
|
Unlike functions such as @ref write or @ref async_write,
|
||||||
|
the stream operations provided here guarantee that bounded
|
||||||
|
work will be performed. This is accomplished by making one
|
||||||
|
or more calls to the underlying stream's `write_some` or
|
||||||
|
`async_write_some` member functions. In order to fully
|
||||||
|
serialize the message, multiple calls are required.
|
||||||
|
|
||||||
@li An error occurs.
|
The ability to incrementally serialize a message, peforming
|
||||||
|
bounded work at each iteration is useful in many scenarios,
|
||||||
|
such as:
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls
|
@li Setting consistent, per-call timeouts
|
||||||
to the stream's `write_some` function.
|
|
||||||
|
|
||||||
Regardless of the semantic meaning of the header (for example,
|
@li Efficiently relaying body content from another stream
|
||||||
specifying "Content-Length: 0" and "Connection: close"),
|
|
||||||
this function will not return `boost::asio::error::eof`.
|
|
||||||
|
|
||||||
@param stream The stream to which the data is to be written.
|
@li Performing application-layer flow control
|
||||||
The type must support the @b SyncWriteStream concept.
|
|
||||||
|
|
||||||
@param msg The header to write.
|
To use this class, construct an instance with the message
|
||||||
|
to be sent. To make it easier to declare the type, the
|
||||||
|
helper function @ref make_write_stream is provided:
|
||||||
|
|
||||||
@param ec Set to the error, if any occurred.
|
The implementation will automatically perform chunk encoding
|
||||||
|
if the contents of the message indicate that chunk encoding
|
||||||
|
is required. If the semantics of the message indicate that
|
||||||
|
the connection should be closed after the message is sent,
|
||||||
|
the error delivered from stream operations will be
|
||||||
|
`boost::asio::error::eof`.
|
||||||
|
|
||||||
|
@code
|
||||||
|
template<class Stream>
|
||||||
|
void send(Stream& stream, request<string_body> const& msg)
|
||||||
|
{
|
||||||
|
serializer<true, string_body, fields> w{msg};
|
||||||
|
do
|
||||||
|
{
|
||||||
|
w.write_some();
|
||||||
|
}
|
||||||
|
while(! w.is_done());
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
Upon construction, an optional chunk decorator may be
|
||||||
|
specified. This decorator is a function object called with
|
||||||
|
each buffer sequence of the body when the chunked transfer
|
||||||
|
encoding is indicate in the message header. The decorator
|
||||||
|
will be called with an empty buffer sequence (actually
|
||||||
|
the type `boost::asio::null_buffers`) to indicate the
|
||||||
|
final chunk. The decorator may return a string which forms
|
||||||
|
the chunk extension for chunks, and the field trailers
|
||||||
|
for the final chunk.
|
||||||
|
|
||||||
|
In C++11 the decorator must be declared as a class or
|
||||||
|
struct with a templated operator() thusly:
|
||||||
|
|
||||||
|
@code
|
||||||
|
// The implementation guarantees that operator()
|
||||||
|
// will be called only after the view returned by
|
||||||
|
// any previous calls to operator() are no longer
|
||||||
|
// needed. The decorator instance is intended to
|
||||||
|
// manage the lifetime of the storage for all returned
|
||||||
|
// views.
|
||||||
|
//
|
||||||
|
struct decorator
|
||||||
|
{
|
||||||
|
// Returns the chunk-extension for each chunk.
|
||||||
|
// The buffer returned must include a trailing "\r\n",
|
||||||
|
// and the leading semicolon (";") if one or more
|
||||||
|
// chunk extensions are specified.
|
||||||
|
//
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
string_view
|
||||||
|
operator()(ConstBufferSequence const&) const;
|
||||||
|
|
||||||
|
// Returns a set of field trailers for the final chunk.
|
||||||
|
// Each field should be formatted according to rfc7230
|
||||||
|
// including the trailing "\r\n" for each field. If
|
||||||
|
// no trailers are indicated, an empty string is returned.
|
||||||
|
//
|
||||||
|
string_view
|
||||||
|
operator()(boost::asio::null_buffers) const;
|
||||||
|
};
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
@par Thread Safety
|
||||||
|
@e Distinct @e objects: Safe.@n
|
||||||
|
@e Shared @e objects: Unsafe.
|
||||||
|
|
||||||
|
@tparam isRequest `true` if the message is a request.
|
||||||
|
|
||||||
|
@tparam Body The body type of the message.
|
||||||
|
|
||||||
|
@tparam Fields The type of fields in the message.
|
||||||
|
|
||||||
|
@tparam Decorator The type of chunk decorator to use.
|
||||||
|
|
||||||
|
@tparam Allocator The type of allocator to use.
|
||||||
|
|
||||||
|
@see @ref make_write_stream
|
||||||
*/
|
*/
|
||||||
template<class SyncWriteStream,
|
template<
|
||||||
bool isRequest, class Fields>
|
bool isRequest, class Body, class Fields,
|
||||||
void
|
class Decorator = empty_decorator,
|
||||||
write(SyncWriteStream& stream,
|
class Allocator = std::allocator<char>
|
||||||
header<isRequest, Fields> const& msg,
|
>
|
||||||
error_code& ec);
|
class serializer
|
||||||
|
{
|
||||||
|
template<class Stream, class Handler>
|
||||||
|
class async_op;
|
||||||
|
|
||||||
/** Write a HTTP/1 header asynchronously to a stream.
|
enum
|
||||||
|
{
|
||||||
|
do_init = 0,
|
||||||
|
do_header_only = 10,
|
||||||
|
do_header = 20,
|
||||||
|
do_body = 40,
|
||||||
|
|
||||||
|
do_init_c = 50,
|
||||||
|
do_header_only_c = 60,
|
||||||
|
do_header_c = 70,
|
||||||
|
do_body_c = 90,
|
||||||
|
do_final_c = 100,
|
||||||
|
|
||||||
This function is used to asynchronously write a header to
|
do_complete = 110
|
||||||
a stream. The function call always returns immediately. The
|
};
|
||||||
asynchronous operation will continue until one of the following
|
|
||||||
conditions is true:
|
|
||||||
|
|
||||||
@li The entire header is written.
|
void split(bool, std::true_type) {}
|
||||||
|
void split(bool v, std::false_type) { split_ = v; }
|
||||||
|
|
||||||
@li An error occurs.
|
using buffer_type =
|
||||||
|
basic_multi_buffer<Allocator>;
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls to
|
using is_deferred =
|
||||||
the stream's `async_write_some` functions, and is known as a
|
typename Body::writer::is_deferred;
|
||||||
<em>composed operation</em>. The program must ensure that the
|
|
||||||
stream performs no other write operations until this operation
|
|
||||||
completes.
|
|
||||||
|
|
||||||
Regardless of the semantic meaning of the header (for example,
|
using cb0_t = consuming_buffers<buffers_view<
|
||||||
specifying "Content-Length: 0" and "Connection: close"),
|
typename buffer_type::const_buffers_type, // header
|
||||||
this function will not return `boost::asio::error::eof`.
|
typename Body::writer::const_buffers_type>>;// body
|
||||||
|
|
||||||
@param stream The stream to which the data is to be written.
|
using cb1_t = consuming_buffers<
|
||||||
The type must support the @b AsyncWriteStream concept.
|
typename Body::writer::const_buffers_type>; // body
|
||||||
|
|
||||||
@param msg The header to write. The object must remain valid
|
using ch0_t = consuming_buffers<buffers_view<
|
||||||
at least until the completion handler is called; ownership is
|
typename buffer_type::const_buffers_type, // header
|
||||||
not transferred.
|
detail::chunk_header, // chunk-header
|
||||||
|
boost::asio::const_buffers_1, // chunk-ext+\r\n
|
||||||
|
typename Body::writer::const_buffers_type, // body
|
||||||
|
boost::asio::const_buffers_1>>; // crlf
|
||||||
|
|
||||||
|
using ch1_t = consuming_buffers<buffers_view<
|
||||||
|
detail::chunk_header, // chunk-header
|
||||||
|
boost::asio::const_buffers_1, // chunk-ext+\r\n
|
||||||
|
typename Body::writer::const_buffers_type, // body
|
||||||
|
boost::asio::const_buffers_1>>; // crlf
|
||||||
|
|
||||||
@param handler The handler to be called when the operation
|
using ch2_t = consuming_buffers<buffers_view<
|
||||||
completes. Copies will be made of the handler as required.
|
boost::asio::const_buffers_1, // chunk-final
|
||||||
The equivalent function signature of the handler must be:
|
boost::asio::const_buffers_1, // trailers
|
||||||
@code void handler(
|
boost::asio::const_buffers_1>>; // crlf
|
||||||
error_code const& error // result of operation
|
|
||||||
); @endcode
|
message<isRequest, Body, Fields> const& m_;
|
||||||
Regardless of whether the asynchronous operation completes
|
Decorator d_;
|
||||||
immediately or not, the handler will not be invoked from within
|
std::size_t limit_ =
|
||||||
this function. Invocation of the handler will be performed in a
|
(std::numeric_limits<std::size_t>::max)();
|
||||||
manner equivalent to using `boost::asio::io_service::post`.
|
boost::optional<typename Body::writer> wr_;
|
||||||
*/
|
buffer_type b_;
|
||||||
template<class AsyncWriteStream,
|
boost::variant<boost::blank,
|
||||||
bool isRequest, class Fields,
|
cb0_t, cb1_t, ch0_t, ch1_t, ch2_t> v_;
|
||||||
class WriteHandler>
|
int s_;
|
||||||
|
bool split_ = is_deferred::value;
|
||||||
|
bool header_done_ = false;
|
||||||
|
bool chunked_;
|
||||||
|
bool close_;
|
||||||
|
bool more_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructor
|
||||||
|
|
||||||
|
@param msg The message to serialize. The message object
|
||||||
|
must remain valid for the lifetime of the write stream.
|
||||||
|
|
||||||
|
@param decorator An optional decorator to use.
|
||||||
|
|
||||||
|
@param alloc An optional allocator to use.
|
||||||
|
*/
|
||||||
|
explicit
|
||||||
|
serializer(message<isRequest, Body, Fields> const& msg,
|
||||||
|
Decorator const& decorator = Decorator{},
|
||||||
|
Allocator const& alloc = Allocator{});
|
||||||
|
|
||||||
|
/// Returns the maximum number of bytes that will be written in each operation
|
||||||
|
std::size_t
|
||||||
|
limit() const
|
||||||
|
{
|
||||||
|
return limit_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns `true` if we will pause after writing the header.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
split() const
|
||||||
|
{
|
||||||
|
return split_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set whether the header and body are written separately.
|
||||||
|
|
||||||
|
When the split feature is enabled, the implementation will
|
||||||
|
write only the octets corresponding to the serialized header
|
||||||
|
first. If the header has already been written, this function
|
||||||
|
will have no effect on output. This function should be called
|
||||||
|
before any writes take place, otherwise the behavior is
|
||||||
|
undefined.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
split(bool v)
|
||||||
|
{
|
||||||
|
split(v, is_deferred{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the maximum number of bytes that will be written in each operation.
|
||||||
|
|
||||||
|
By default, there is no limit on the size of writes.
|
||||||
|
|
||||||
|
@param n The byte limit. This must be greater than zero.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
limit(std::size_t n)
|
||||||
|
{
|
||||||
|
limit_ = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return `true` if serialization of the header is complete.
|
||||||
|
|
||||||
|
This function indicates whether or not all octets
|
||||||
|
corresponding to the serialized representation of the
|
||||||
|
header have been successfully delivered to the stream.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
is_header_done() const
|
||||||
|
{
|
||||||
|
return header_done_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return `true` if serialization is complete
|
||||||
|
|
||||||
|
The operation is complete when all octets corresponding
|
||||||
|
to the serialized representation of the message have been
|
||||||
|
successfully delivered to the stream.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
is_done() const
|
||||||
|
{
|
||||||
|
return s_ == do_complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write some serialized message data to the stream.
|
||||||
|
|
||||||
|
This function is used to write serialized message data to the
|
||||||
|
stream. The function call will block until one of the following
|
||||||
|
conditions is true:
|
||||||
|
|
||||||
|
@li One or more bytes have been transferred.
|
||||||
|
|
||||||
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
|
In order to completely serialize a message, this function
|
||||||
|
should be called until @ref is_done returns `true`. If the
|
||||||
|
semantics of the message indicate that the connection should
|
||||||
|
be closed after the message is sent, the error delivered from
|
||||||
|
this call will be `boost::asio::error::eof`.
|
||||||
|
|
||||||
|
@param stream The stream to write to. This type must
|
||||||
|
satisfy the requirements of @b SyncWriteStream.
|
||||||
|
|
||||||
|
@throws system_error Thrown on failure.
|
||||||
|
*/
|
||||||
|
template<class SyncWriteStream>
|
||||||
|
void
|
||||||
|
write_some(SyncWriteStream& stream);
|
||||||
|
|
||||||
|
/** Write some serialized message data to the stream.
|
||||||
|
|
||||||
|
This function is used to write serialized message data to the
|
||||||
|
stream. The function call will block until one of the following
|
||||||
|
conditions is true:
|
||||||
|
|
||||||
|
@li One or more bytes have been transferred.
|
||||||
|
|
||||||
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
|
In order to completely serialize a message, this function
|
||||||
|
should be called until @ref is_done returns `true`. If the
|
||||||
|
semantics of the message indicate that the connection should
|
||||||
|
be closed after the message is sent, the error delivered from
|
||||||
|
this call will be `boost::asio::error::eof`.
|
||||||
|
|
||||||
|
@param stream The stream to write to. This type must
|
||||||
|
satisfy the requirements of @b SyncWriteStream.
|
||||||
|
|
||||||
|
@param ec Set to indicate what error occurred, if any.
|
||||||
|
*/
|
||||||
|
template<class SyncWriteStream>
|
||||||
|
void
|
||||||
|
write_some(SyncWriteStream& stream, error_code &ec);
|
||||||
|
|
||||||
|
/** Start an asynchronous write of some serialized message data.
|
||||||
|
|
||||||
|
This function is used to asynchronously write serialized
|
||||||
|
message data to the stream. The function call always returns
|
||||||
|
immediately. The asynchronous operation will continue until
|
||||||
|
one of the following conditions is true:
|
||||||
|
|
||||||
|
@li One or more bytes have been transferred.
|
||||||
|
|
||||||
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
|
In order to completely serialize a message, this function
|
||||||
|
should be called until @ref is_done returns `true`. If the
|
||||||
|
semantics of the message indicate that the connection should
|
||||||
|
be closed after the message is sent, the error delivered from
|
||||||
|
this call will be `boost::asio::error::eof`.
|
||||||
|
|
||||||
|
@param stream The stream to write to. This type must
|
||||||
|
satisfy the requirements of @b SyncWriteStream.
|
||||||
|
|
||||||
|
@param handler The handler to be called when the request
|
||||||
|
completes. Copies will be made of the handler as required. The
|
||||||
|
equivalent function signature of the handler must be:
|
||||||
|
@code void handler(
|
||||||
|
error_code const& ec // Result of operation
|
||||||
|
); @endcode
|
||||||
|
Regardless of whether the asynchronous operation completes
|
||||||
|
immediately or not, the handler will not be invoked from within
|
||||||
|
this function. Invocation of the handler will be performed in a
|
||||||
|
manner equivalent to using `boost::asio::io_service::post`.
|
||||||
|
*/
|
||||||
|
template<class AsyncWriteStream, class WriteHandler>
|
||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
void_or_deduced
|
void_or_deduced
|
||||||
#else
|
#else
|
||||||
async_return_type<
|
async_return_type<WriteHandler, void(error_code)>
|
||||||
WriteHandler, void(error_code)>
|
|
||||||
#endif
|
#endif
|
||||||
async_write(AsyncWriteStream& stream,
|
async_write_some(AsyncWriteStream& stream,
|
||||||
header<isRequest, Fields> const& msg,
|
|
||||||
WriteHandler&& handler);
|
WriteHandler&& handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Return a stateful object to serialize an HTTP message.
|
||||||
|
|
||||||
|
This convenience function makes it easier to declare
|
||||||
|
the variable for a given message.
|
||||||
|
*/
|
||||||
|
template<
|
||||||
|
bool isRequest, class Body, class Fields,
|
||||||
|
class Decorator = empty_decorator,
|
||||||
|
class Allocator = std::allocator<char>>
|
||||||
|
inline
|
||||||
|
serializer<isRequest, Body, Fields,
|
||||||
|
typename std::decay<Decorator>::type,
|
||||||
|
typename std::decay<Allocator>::type>
|
||||||
|
make_write_stream(message<isRequest, Body, Fields> const& m,
|
||||||
|
Decorator const& decorator = Decorator{},
|
||||||
|
Allocator const& allocator = Allocator{})
|
||||||
|
{
|
||||||
|
return serializer<isRequest, Body, Fields,
|
||||||
|
typename std::decay<Decorator>::type,
|
||||||
|
typename std::decay<Allocator>::type>{
|
||||||
|
m, decorator, allocator};
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
|
#define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
|
||||||
|
|
||||||
#include <beast/websocket/detail/type_traits.hpp>
|
#include <beast/websocket/detail/type_traits.hpp>
|
||||||
|
#include <beast/http/empty_body.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
#include <beast/http/header_parser.hpp>
|
#include <beast/http/header_parser.hpp>
|
||||||
#include <beast/http/read.hpp>
|
#include <beast/http/read.hpp>
|
||||||
@ -39,7 +40,8 @@ class stream<NextLayer>::response_op
|
|||||||
{
|
{
|
||||||
bool cont;
|
bool cont;
|
||||||
stream<NextLayer>& ws;
|
stream<NextLayer>& ws;
|
||||||
http::header<false, http::fields> res;
|
http::message<false,
|
||||||
|
http::empty_body, http::fields> res;
|
||||||
int state = 0;
|
int state = 0;
|
||||||
|
|
||||||
template<class Fields, class Decorator>
|
template<class Fields, class Decorator>
|
||||||
@ -188,8 +190,8 @@ class stream<NextLayer>::accept_op
|
|||||||
|
|
||||||
template<class Buffers>
|
template<class Buffers>
|
||||||
data(Handler& handler, stream<NextLayer>& ws_,
|
data(Handler& handler, stream<NextLayer>& ws_,
|
||||||
Buffers const& buffers,
|
Buffers const& buffers,
|
||||||
Decorator const& decorator_)
|
Decorator const& decorator_)
|
||||||
: ws(ws_)
|
: ws(ws_)
|
||||||
, decorator(decorator_)
|
, decorator(decorator_)
|
||||||
{
|
{
|
||||||
@ -365,7 +367,8 @@ accept_ex(ResponseDecorator const& decorator, error_code& ec)
|
|||||||
|
|
||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
void
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
accept(ConstBufferSequence const& buffers)
|
accept(ConstBufferSequence const& buffers)
|
||||||
{
|
{
|
||||||
@ -383,7 +386,8 @@ accept(ConstBufferSequence const& buffers)
|
|||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<
|
template<
|
||||||
class ConstBufferSequence, class ResponseDecorator>
|
class ConstBufferSequence, class ResponseDecorator>
|
||||||
void
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
accept_ex(ConstBufferSequence const& buffers,
|
accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const &decorator)
|
ResponseDecorator const &decorator)
|
||||||
@ -404,7 +408,8 @@ accept_ex(ConstBufferSequence const& buffers,
|
|||||||
|
|
||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
void
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
accept(ConstBufferSequence const& buffers, error_code& ec)
|
accept(ConstBufferSequence const& buffers, error_code& ec)
|
||||||
{
|
{
|
||||||
@ -425,7 +430,8 @@ accept(ConstBufferSequence const& buffers, error_code& ec)
|
|||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<
|
template<
|
||||||
class ConstBufferSequence, class ResponseDecorator>
|
class ConstBufferSequence, class ResponseDecorator>
|
||||||
void
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
accept_ex(ConstBufferSequence const& buffers,
|
accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const& decorator, error_code& ec)
|
ResponseDecorator const& decorator, error_code& ec)
|
||||||
@ -641,8 +647,9 @@ async_accept_ex(ResponseDecorator const& decorator,
|
|||||||
|
|
||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<class ConstBufferSequence, class AcceptHandler>
|
template<class ConstBufferSequence, class AcceptHandler>
|
||||||
async_return_type<
|
typename std::enable_if<
|
||||||
AcceptHandler, void(error_code)>
|
! http::detail::is_header<ConstBufferSequence>::value,
|
||||||
|
async_return_type<AcceptHandler, void(error_code)>>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
async_accept(ConstBufferSequence const& buffers,
|
async_accept(ConstBufferSequence const& buffers,
|
||||||
AcceptHandler&& handler)
|
AcceptHandler&& handler)
|
||||||
@ -664,8 +671,9 @@ async_accept(ConstBufferSequence const& buffers,
|
|||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<class ConstBufferSequence,
|
template<class ConstBufferSequence,
|
||||||
class ResponseDecorator, class AcceptHandler>
|
class ResponseDecorator, class AcceptHandler>
|
||||||
async_return_type<
|
typename std::enable_if<
|
||||||
AcceptHandler, void(error_code)>
|
! http::detail::is_header<ConstBufferSequence>::value,
|
||||||
|
async_return_type<AcceptHandler, void(error_code)>>::type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
async_accept_ex(ConstBufferSequence const& buffers,
|
async_accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const& decorator,
|
ResponseDecorator const& decorator,
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
|
#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
|
||||||
|
|
||||||
#include <beast/websocket/detail/type_traits.hpp>
|
#include <beast/websocket/detail/type_traits.hpp>
|
||||||
|
#include <beast/http/empty_body.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
#include <beast/http/read.hpp>
|
#include <beast/http/read.hpp>
|
||||||
#include <beast/http/write.hpp>
|
#include <beast/http/write.hpp>
|
||||||
@ -38,7 +39,8 @@ class stream<NextLayer>::handshake_op
|
|||||||
stream<NextLayer>& ws;
|
stream<NextLayer>& ws;
|
||||||
response_type* res_p;
|
response_type* res_p;
|
||||||
detail::sec_ws_key_type key;
|
detail::sec_ws_key_type key;
|
||||||
request_type req;
|
http::message<true,
|
||||||
|
http::empty_body, http::fields> req;
|
||||||
response_type res;
|
response_type res;
|
||||||
int state = 0;
|
int state = 0;
|
||||||
|
|
||||||
|
@ -196,10 +196,10 @@ build_request(detail::sec_ws_key_type& key,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class NextLayer>
|
template<class NextLayer>
|
||||||
template<class Decorator>
|
template<class Fields, class Decorator>
|
||||||
response_type
|
response_type
|
||||||
stream<NextLayer>::
|
stream<NextLayer>::
|
||||||
build_response(request_type const& req,
|
build_response(http::header<true, Fields> const& req,
|
||||||
Decorator const& decorator)
|
Decorator const& decorator)
|
||||||
{
|
{
|
||||||
auto const decorate =
|
auto const decorate =
|
||||||
|
@ -12,8 +12,10 @@
|
|||||||
#include <beast/websocket/option.hpp>
|
#include <beast/websocket/option.hpp>
|
||||||
#include <beast/websocket/detail/hybi13.hpp>
|
#include <beast/websocket/detail/hybi13.hpp>
|
||||||
#include <beast/websocket/detail/stream_base.hpp>
|
#include <beast/websocket/detail/stream_base.hpp>
|
||||||
|
#include <beast/http/empty_body.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
#include <beast/http/string_body.hpp>
|
#include <beast/http/string_body.hpp>
|
||||||
|
#include <beast/http/detail/type_traits.hpp>
|
||||||
#include <beast/core/async_result.hpp>
|
#include <beast/core/async_result.hpp>
|
||||||
#include <beast/core/buffered_read_stream.hpp>
|
#include <beast/core/buffered_read_stream.hpp>
|
||||||
#include <beast/core/string_view.hpp>
|
#include <beast/core/string_view.hpp>
|
||||||
@ -28,12 +30,12 @@ namespace beast {
|
|||||||
namespace websocket {
|
namespace websocket {
|
||||||
|
|
||||||
/// The type of object holding HTTP Upgrade requests
|
/// The type of object holding HTTP Upgrade requests
|
||||||
using request_type = http::header<true, http::fields>;
|
using request_type =
|
||||||
|
http::message<true, http::empty_body, http::fields>;
|
||||||
|
|
||||||
/// The type of object holding HTTP Upgrade responses
|
/// The type of object holding HTTP Upgrade responses
|
||||||
using response_type =
|
using response_type =
|
||||||
//http::response_header;
|
http::message<false, http::string_body, http::fields>;
|
||||||
http::response<http::string_body, http::fields>;
|
|
||||||
|
|
||||||
/** Information about a WebSocket frame.
|
/** Information about a WebSocket frame.
|
||||||
|
|
||||||
@ -473,7 +475,12 @@ public:
|
|||||||
@throws system_error Thrown on failure.
|
@throws system_error Thrown on failure.
|
||||||
*/
|
*/
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
void
|
void
|
||||||
|
#else
|
||||||
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
|
#endif
|
||||||
accept(ConstBufferSequence const& buffers);
|
accept(ConstBufferSequence const& buffers);
|
||||||
|
|
||||||
/** Read and respond to a WebSocket HTTP Upgrade request.
|
/** Read and respond to a WebSocket HTTP Upgrade request.
|
||||||
@ -516,7 +523,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<class ConstBufferSequence,
|
template<class ConstBufferSequence,
|
||||||
class ResponseDecorator>
|
class ResponseDecorator>
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
void
|
void
|
||||||
|
#else
|
||||||
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
|
#endif
|
||||||
accept_ex(ConstBufferSequence const& buffers,
|
accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const& decorator);
|
ResponseDecorator const& decorator);
|
||||||
|
|
||||||
@ -550,7 +562,12 @@ public:
|
|||||||
@param ec Set to indicate what error occurred, if any.
|
@param ec Set to indicate what error occurred, if any.
|
||||||
*/
|
*/
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
void
|
void
|
||||||
|
#else
|
||||||
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
|
#endif
|
||||||
accept(ConstBufferSequence const& buffers, error_code& ec);
|
accept(ConstBufferSequence const& buffers, error_code& ec);
|
||||||
|
|
||||||
/** Read and respond to a WebSocket HTTP Upgrade request.
|
/** Read and respond to a WebSocket HTTP Upgrade request.
|
||||||
@ -593,7 +610,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<class ConstBufferSequence,
|
template<class ConstBufferSequence,
|
||||||
class ResponseDecorator>
|
class ResponseDecorator>
|
||||||
|
#if BEAST_DOXYGEN
|
||||||
void
|
void
|
||||||
|
#else
|
||||||
|
typename std::enable_if<! http::detail::is_header<
|
||||||
|
ConstBufferSequence>::value>::type
|
||||||
|
#endif
|
||||||
accept_ex(ConstBufferSequence const& buffers,
|
accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const& decorator,
|
ResponseDecorator const& decorator,
|
||||||
error_code& ec);
|
error_code& ec);
|
||||||
@ -1085,8 +1107,9 @@ public:
|
|||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
void_or_deduced
|
void_or_deduced
|
||||||
#else
|
#else
|
||||||
async_return_type<
|
typename std::enable_if<
|
||||||
AcceptHandler, void(error_code)>
|
! http::detail::is_header<ConstBufferSequence>::value,
|
||||||
|
async_return_type<AcceptHandler, void(error_code)>>::type
|
||||||
#endif
|
#endif
|
||||||
async_accept(ConstBufferSequence const& buffers,
|
async_accept(ConstBufferSequence const& buffers,
|
||||||
AcceptHandler&& handler);
|
AcceptHandler&& handler);
|
||||||
@ -1153,8 +1176,9 @@ public:
|
|||||||
#if BEAST_DOXYGEN
|
#if BEAST_DOXYGEN
|
||||||
void_or_deduced
|
void_or_deduced
|
||||||
#else
|
#else
|
||||||
async_return_type<
|
typename std::enable_if<
|
||||||
AcceptHandler, void(error_code)>
|
! http::detail::is_header<ConstBufferSequence>::value,
|
||||||
|
async_return_type<AcceptHandler, void(error_code)>>::type
|
||||||
#endif
|
#endif
|
||||||
async_accept_ex(ConstBufferSequence const& buffers,
|
async_accept_ex(ConstBufferSequence const& buffers,
|
||||||
ResponseDecorator const& decorator,
|
ResponseDecorator const& decorator,
|
||||||
@ -2975,9 +2999,9 @@ private:
|
|||||||
string_view const& target,
|
string_view const& target,
|
||||||
Decorator const& decorator);
|
Decorator const& decorator);
|
||||||
|
|
||||||
template<class Decorator>
|
template<class Fields, class Decorator>
|
||||||
response_type
|
response_type
|
||||||
build_response(request_type const& req,
|
build_response(http::header<true, Fields> const& req,
|
||||||
Decorator const& decorator);
|
Decorator const& decorator);
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -42,6 +42,7 @@ unit-test core-tests :
|
|||||||
unit-test http-tests :
|
unit-test http-tests :
|
||||||
../extras/beast/unit_test/main.cpp
|
../extras/beast/unit_test/main.cpp
|
||||||
http/basic_parser.cpp
|
http/basic_parser.cpp
|
||||||
|
http/buffer_body.cpp
|
||||||
http/concepts.cpp
|
http/concepts.cpp
|
||||||
http/design.cpp
|
http/design.cpp
|
||||||
http/dynamic_body.cpp
|
http/dynamic_body.cpp
|
||||||
@ -54,7 +55,6 @@ unit-test http-tests :
|
|||||||
http/rfc7230.cpp
|
http/rfc7230.cpp
|
||||||
http/string_body.cpp
|
http/string_body.cpp
|
||||||
http/write.cpp
|
http/write.cpp
|
||||||
http/chunk_encode.cpp
|
|
||||||
;
|
;
|
||||||
|
|
||||||
unit-test http-bench :
|
unit-test http-bench :
|
||||||
|
@ -11,9 +11,11 @@ add_executable (http-tests
|
|||||||
test_parser.hpp
|
test_parser.hpp
|
||||||
../../extras/beast/unit_test/main.cpp
|
../../extras/beast/unit_test/main.cpp
|
||||||
basic_parser.cpp
|
basic_parser.cpp
|
||||||
|
buffer_body.cpp
|
||||||
concepts.cpp
|
concepts.cpp
|
||||||
design.cpp
|
design.cpp
|
||||||
dynamic_body.cpp
|
dynamic_body.cpp
|
||||||
|
empty_body.cpp
|
||||||
error.cpp
|
error.cpp
|
||||||
fields.cpp
|
fields.cpp
|
||||||
header_parser.cpp
|
header_parser.cpp
|
||||||
@ -23,7 +25,6 @@ add_executable (http-tests
|
|||||||
rfc7230.cpp
|
rfc7230.cpp
|
||||||
string_body.cpp
|
string_body.cpp
|
||||||
write.cpp
|
write.cpp
|
||||||
chunk_encode.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <beast/core/buffer_cat.hpp>
|
#include <beast/core/buffer_cat.hpp>
|
||||||
#include <beast/core/consuming_buffers.hpp>
|
#include <beast/core/consuming_buffers.hpp>
|
||||||
#include <beast/core/multi_buffer.hpp>
|
#include <beast/core/multi_buffer.hpp>
|
||||||
|
#include <beast/core/ostream.hpp>
|
||||||
#include <beast/unit_test/suite.hpp>
|
#include <beast/unit_test/suite.hpp>
|
||||||
|
|
||||||
namespace beast {
|
namespace beast {
|
||||||
@ -903,7 +904,7 @@ public:
|
|||||||
{
|
{
|
||||||
#if 0
|
#if 0
|
||||||
multi_buffer b;
|
multi_buffer b;
|
||||||
b <<
|
ostream(b) <<
|
||||||
"POST / HTTP/1.1\r\n"
|
"POST / HTTP/1.1\r\n"
|
||||||
"Content-Length: 5\r\n"
|
"Content-Length: 5\r\n"
|
||||||
"\r\n"
|
"\r\n"
|
||||||
|
9
test/http/buffer_body.cpp
Normal file
9
test/http/buffer_body.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test that header file is self-contained.
|
||||||
|
#include <beast/http/buffer_body.hpp>
|
@ -1,146 +0,0 @@
|
|||||||
//
|
|
||||||
// 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)
|
|
||||||
//
|
|
||||||
|
|
||||||
// Test that header file is self-contained.
|
|
||||||
#include <beast/http/chunk_encode.hpp>
|
|
||||||
|
|
||||||
#include <beast/core/ostream.hpp>
|
|
||||||
#include <beast/unit_test/suite.hpp>
|
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
|
|
||||||
namespace beast {
|
|
||||||
namespace http {
|
|
||||||
|
|
||||||
class chunk_encode_test : public beast::unit_test::suite
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct final_chunk
|
|
||||||
{
|
|
||||||
std::string s;
|
|
||||||
|
|
||||||
final_chunk() = default;
|
|
||||||
|
|
||||||
explicit
|
|
||||||
final_chunk(std::string s_)
|
|
||||||
: s(std::move(s_))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
static
|
|
||||||
std::string
|
|
||||||
to_string(ConstBufferSequence const& bs)
|
|
||||||
{
|
|
||||||
return boost::lexical_cast<
|
|
||||||
std::string>(buffers(bs));
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
void
|
|
||||||
encode1(std::string& s, final_chunk const& fc)
|
|
||||||
{
|
|
||||||
using boost::asio::buffer;
|
|
||||||
if(! fc.s.empty())
|
|
||||||
s.append(to_string(chunk_encode(
|
|
||||||
false, buffer(fc.s.data(), fc.s.size()))));
|
|
||||||
s.append(to_string(chunk_encode_final()));
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
void
|
|
||||||
encode1(std::string& s, std::string const& piece)
|
|
||||||
{
|
|
||||||
using boost::asio::buffer;
|
|
||||||
s.append(to_string(chunk_encode(
|
|
||||||
false, buffer(piece.data(), piece.size()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
inline
|
|
||||||
void
|
|
||||||
encode(std::string&)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Arg, class... Args>
|
|
||||||
static
|
|
||||||
void
|
|
||||||
encode(std::string& s, Arg const& arg, Args const&... args)
|
|
||||||
{
|
|
||||||
encode1(s, arg);
|
|
||||||
encode(s, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class... Args>
|
|
||||||
void
|
|
||||||
check(std::string const& answer, Args const&... args)
|
|
||||||
{
|
|
||||||
std::string s;
|
|
||||||
encode(s, args...);
|
|
||||||
BEAST_EXPECT(s == answer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
check(
|
|
||||||
"0\r\n\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
"", final_chunk{});
|
|
||||||
|
|
||||||
check(
|
|
||||||
"1\r\n"
|
|
||||||
"*\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
final_chunk("*"));
|
|
||||||
|
|
||||||
check(
|
|
||||||
"2\r\n"
|
|
||||||
"**\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
final_chunk("**"));
|
|
||||||
|
|
||||||
check(
|
|
||||||
"1\r\n"
|
|
||||||
"*\r\n"
|
|
||||||
"1\r\n"
|
|
||||||
"*\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
"*", final_chunk("*"));
|
|
||||||
|
|
||||||
check(
|
|
||||||
"5\r\n"
|
|
||||||
"*****\r\n"
|
|
||||||
"7\r\n"
|
|
||||||
"*******\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
"*****", final_chunk("*******"));
|
|
||||||
|
|
||||||
check(
|
|
||||||
"1\r\n"
|
|
||||||
"*\r\n"
|
|
||||||
"1\r\n"
|
|
||||||
"*\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
"*", "*", final_chunk{});
|
|
||||||
|
|
||||||
check(
|
|
||||||
"4\r\n"
|
|
||||||
"****\r\n"
|
|
||||||
"0\r\n\r\n",
|
|
||||||
"****", final_chunk{});
|
|
||||||
|
|
||||||
BEAST_EXPECT(to_string(chunk_encode(true,
|
|
||||||
boost::asio::buffer("****", 4))) ==
|
|
||||||
"4\r\n****\r\n0\r\n\r\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast);
|
|
||||||
|
|
||||||
} // http
|
|
||||||
} // beast
|
|
@ -7,3 +7,15 @@
|
|||||||
|
|
||||||
// Test that header file is self-contained.
|
// Test that header file is self-contained.
|
||||||
#include <beast/http/concepts.hpp>
|
#include <beast/http/concepts.hpp>
|
||||||
|
|
||||||
|
#include <beast/http/empty_body.hpp>
|
||||||
|
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
BOOST_STATIC_ASSERT(! is_Writer<int>::value);
|
||||||
|
|
||||||
|
BOOST_STATIC_ASSERT(is_Writer<empty_body>::value);
|
||||||
|
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
@ -5,13 +5,10 @@
|
|||||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <beast/core/buffer_prefix.hpp>
|
#include <beast/core.hpp>
|
||||||
#include <beast/core/flat_buffer.hpp>
|
#include <beast/http.hpp>
|
||||||
#include <beast/http/chunk_encode.hpp>
|
|
||||||
#include <beast/http/read.hpp>
|
|
||||||
#include <beast/http/write.hpp>
|
|
||||||
#include <beast/http/string_body.hpp>
|
|
||||||
#include <beast/core/detail/clamp.hpp>
|
#include <beast/core/detail/clamp.hpp>
|
||||||
|
#include <beast/test/pipe_stream.hpp>
|
||||||
#include <beast/test/string_istream.hpp>
|
#include <beast/test/string_istream.hpp>
|
||||||
#include <beast/test/string_ostream.hpp>
|
#include <beast/test/string_ostream.hpp>
|
||||||
#include <beast/test/yield_to.hpp>
|
#include <beast/test/yield_to.hpp>
|
||||||
@ -27,6 +24,177 @@ class design_test
|
|||||||
, public beast::test::enable_yield_to
|
, public beast::test::enable_yield_to
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
Expect: 100-continue
|
||||||
|
|
||||||
|
1. Client sends a header with Expect: 100-continue
|
||||||
|
|
||||||
|
2. Server reads the request header:
|
||||||
|
If "Expect: 100-continue" is present, send 100 status response
|
||||||
|
|
||||||
|
3. Client reads a 100 status response and delivers the body
|
||||||
|
|
||||||
|
4. Server reads the request body
|
||||||
|
*/
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<class Stream>
|
||||||
|
void
|
||||||
|
serverExpect100Continue(Stream& stream, yield_context yield)
|
||||||
|
{
|
||||||
|
flat_buffer buffer;
|
||||||
|
message_parser<true, string_body, fields> parser;
|
||||||
|
// read the header
|
||||||
|
async_read_some(stream, buffer, parser, yield);
|
||||||
|
if(parser.get().fields["Expect"] ==
|
||||||
|
"100-continue")
|
||||||
|
{
|
||||||
|
// send 100 response
|
||||||
|
message<false, empty_body, fields> res;
|
||||||
|
res.version = 11;
|
||||||
|
res.status = 100;
|
||||||
|
res.reason("Continue");
|
||||||
|
res.fields.insert("Server", "test");
|
||||||
|
async_write(stream, res, yield);
|
||||||
|
}
|
||||||
|
// read the rest of the message
|
||||||
|
while(! parser.is_complete())
|
||||||
|
async_read_some(stream, buffer, parser,yield);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Stream>
|
||||||
|
void
|
||||||
|
clientExpect100Continue(Stream& stream, yield_context yield)
|
||||||
|
{
|
||||||
|
flat_buffer buffer;
|
||||||
|
message<true, string_body, fields> req;
|
||||||
|
req.version = 11;
|
||||||
|
req.method("POST");
|
||||||
|
req.target("/");
|
||||||
|
req.fields.insert("User-Agent", "test");
|
||||||
|
req.fields.insert("Expect", "100-continue");
|
||||||
|
req.body = "Hello, world!";
|
||||||
|
|
||||||
|
// send header
|
||||||
|
auto ws = make_write_stream(req);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
ws.async_write_some(stream, yield);
|
||||||
|
if(ws.is_header_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read response
|
||||||
|
{
|
||||||
|
message<false, string_body, fields> res;
|
||||||
|
async_read(stream, buffer, res, yield);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send body
|
||||||
|
while(! ws.is_done())
|
||||||
|
ws.async_write_some(stream, yield);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doExpect100Continue()
|
||||||
|
{
|
||||||
|
test::pipe p{ios_};
|
||||||
|
yield_to(
|
||||||
|
[&](yield_context yield)
|
||||||
|
{
|
||||||
|
serverExpect100Continue(p.server, yield);
|
||||||
|
},
|
||||||
|
[&](yield_context yield)
|
||||||
|
{
|
||||||
|
clientExpect100Continue(p.client, yield);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Deferred Body type commitment
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void
|
||||||
|
doDeferredBody()
|
||||||
|
{
|
||||||
|
test::pipe p{ios_};
|
||||||
|
ostream(p.server.buffer) <<
|
||||||
|
"POST / HTTP/1.1\r\n"
|
||||||
|
"User-Agent: test\r\n"
|
||||||
|
"Content-Length: 13\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Hello, world!";
|
||||||
|
|
||||||
|
flat_buffer buffer;
|
||||||
|
header_parser<true, fields> parser;
|
||||||
|
auto bytes_used =
|
||||||
|
read_some(p.server, buffer, parser);
|
||||||
|
buffer.consume(bytes_used);
|
||||||
|
|
||||||
|
message_parser<true, string_body, fields> parser2(
|
||||||
|
std::move(parser));
|
||||||
|
|
||||||
|
while(! parser2.is_complete())
|
||||||
|
{
|
||||||
|
bytes_used = read_some(p.server, buffer, parser2);
|
||||||
|
buffer.consume(bytes_used);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Write using caller provided buffers
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void
|
||||||
|
doBufferBody()
|
||||||
|
{
|
||||||
|
test::pipe p{ios_};
|
||||||
|
message<true, buffer_body<false,
|
||||||
|
boost::asio::const_buffers_1>, fields> m;
|
||||||
|
std::string s = "Hello, world!";
|
||||||
|
m.version = 11;
|
||||||
|
m.method("POST");
|
||||||
|
m.target("/");
|
||||||
|
m.fields.insert("User-Agent", "test");
|
||||||
|
m.fields.insert("Content-Length", s.size());
|
||||||
|
auto ws = make_write_stream(m);
|
||||||
|
error_code ec;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
m.body.first.emplace(s.data(),
|
||||||
|
std::min<std::size_t>(3, s.size()));
|
||||||
|
m.body.second = s.size() > 3;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
ws.write_some(p.client, ec);
|
||||||
|
if(ec == error::need_more)
|
||||||
|
{
|
||||||
|
ec = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
|
return;
|
||||||
|
if(ws.is_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s.erase(s.begin(), s.begin() +
|
||||||
|
boost::asio::buffer_size(*m.body.first));
|
||||||
|
if(ws.is_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(p.server.str() ==
|
||||||
|
"POST / HTTP/1.1\r\n"
|
||||||
|
"User-Agent: test\r\n"
|
||||||
|
"Content-Length: 13\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
Read a message with a direct Reader Body.
|
Read a message with a direct Reader Body.
|
||||||
@ -321,6 +489,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
#if 0
|
||||||
|
// VFALCO This is broken
|
||||||
/*
|
/*
|
||||||
Efficiently relay a message from one stream to another
|
Efficiently relay a message from one stream to another
|
||||||
*/
|
*/
|
||||||
@ -337,6 +507,9 @@ public:
|
|||||||
{
|
{
|
||||||
flat_buffer buffer{4096}; // 4K limit
|
flat_buffer buffer{4096}; // 4K limit
|
||||||
header_parser<isRequest, fields> parser;
|
header_parser<isRequest, fields> parser;
|
||||||
|
serializer<isRequest, buffer_body<
|
||||||
|
typename flat_buffer::const_buffers_type>,
|
||||||
|
fields> ws{parser.get()};
|
||||||
error_code ec;
|
error_code ec;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@ -349,7 +522,17 @@ public:
|
|||||||
case parse_state::header:
|
case parse_state::header:
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(parser.got_header());
|
BEAST_EXPECT(parser.got_header());
|
||||||
write(out, parser.get());
|
for(;;)
|
||||||
|
{
|
||||||
|
ws.write_some(out, ec);
|
||||||
|
if(ec == http::error::need_more)
|
||||||
|
{
|
||||||
|
ec = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(ec)
|
||||||
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +614,7 @@ public:
|
|||||||
relay<true>(os, b, is);
|
relay<true>(os, b, is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
@ -519,11 +703,15 @@ public:
|
|||||||
void
|
void
|
||||||
run()
|
run()
|
||||||
{
|
{
|
||||||
|
doExpect100Continue();
|
||||||
|
doDeferredBody();
|
||||||
|
doBufferBody();
|
||||||
|
|
||||||
testDirectBody();
|
testDirectBody();
|
||||||
testIndirectBody();
|
testIndirectBody();
|
||||||
testManualBody();
|
testManualBody();
|
||||||
testExpect100Continue();
|
testExpect100Continue();
|
||||||
testRelay();
|
//testRelay(); // VFALCO Broken with serializer changes
|
||||||
testFixedRead();
|
testFixedRead();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
9
test/http/empty_body.cpp
Normal file
9
test/http/empty_body.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test that header file is self-contained.
|
||||||
|
#include <beast/http/empty_body.hpp>
|
@ -38,6 +38,7 @@ public:
|
|||||||
{
|
{
|
||||||
check("http", error::end_of_stream);
|
check("http", error::end_of_stream);
|
||||||
check("http", error::partial_message);
|
check("http", error::partial_message);
|
||||||
|
check("http", error::need_more);
|
||||||
check("http", error::buffer_overflow);
|
check("http", error::buffer_overflow);
|
||||||
check("http", error::bad_line_ending);
|
check("http", error::bad_line_ending);
|
||||||
check("http", error::bad_method);
|
check("http", error::bad_method);
|
||||||
|
@ -8,13 +8,16 @@
|
|||||||
// Test that header file is self-contained.
|
// Test that header file is self-contained.
|
||||||
#include <beast/http/write.hpp>
|
#include <beast/http/write.hpp>
|
||||||
|
|
||||||
|
#include <beast/http/buffer_body.hpp>
|
||||||
#include <beast/http/fields.hpp>
|
#include <beast/http/fields.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
|
#include <beast/http/read.hpp>
|
||||||
#include <beast/http/string_body.hpp>
|
#include <beast/http/string_body.hpp>
|
||||||
#include <beast/http/write.hpp>
|
|
||||||
#include <beast/core/error.hpp>
|
#include <beast/core/error.hpp>
|
||||||
#include <beast/core/multi_buffer.hpp>
|
#include <beast/core/multi_buffer.hpp>
|
||||||
#include <beast/test/fail_stream.hpp>
|
#include <beast/test/fail_stream.hpp>
|
||||||
|
#include <beast/test/pipe_stream.hpp>
|
||||||
|
#include <beast/test/string_istream.hpp>
|
||||||
#include <beast/test/string_ostream.hpp>
|
#include <beast/test/string_ostream.hpp>
|
||||||
#include <beast/test/yield_to.hpp>
|
#include <beast/test/yield_to.hpp>
|
||||||
#include <beast/unit_test/suite.hpp>
|
#include <beast/unit_test/suite.hpp>
|
||||||
@ -39,25 +42,154 @@ public:
|
|||||||
value_type const& body_;
|
value_type const& body_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
template<bool isRequest, class Allocator>
|
template<bool isRequest, class Allocator>
|
||||||
explicit
|
explicit
|
||||||
writer(message<isRequest, unsized_body, Allocator> const& msg) noexcept
|
writer(message<isRequest, unsized_body, Allocator> const& msg)
|
||||||
: body_(msg.body)
|
: body_(msg.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
init(error_code& ec) noexcept
|
init(error_code& ec)
|
||||||
{
|
{
|
||||||
beast::detail::ignore_unused(ec);
|
beast::detail::ignore_unused(ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class WriteFunction>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
bool
|
get(error_code&)
|
||||||
write(error_code&, WriteFunction&& wf) noexcept
|
|
||||||
{
|
{
|
||||||
wf(boost::asio::buffer(body_));
|
return {{const_buffers_type{
|
||||||
return true;
|
body_.data(), body_.size()}, false}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<
|
||||||
|
bool isDeferred,
|
||||||
|
bool isSplit,
|
||||||
|
bool isFinalEmpty
|
||||||
|
>
|
||||||
|
struct test_body
|
||||||
|
{
|
||||||
|
struct value_type
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
bool mutable read = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class writer
|
||||||
|
{
|
||||||
|
int step_ = 0;
|
||||||
|
value_type const& body_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using is_deferred =
|
||||||
|
std::integral_constant<bool, isDeferred>;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
explicit
|
||||||
|
writer(message<isRequest, test_body,
|
||||||
|
Fields> const& msg)
|
||||||
|
: body_(msg.body)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
init(error_code&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(error_code&)
|
||||||
|
{
|
||||||
|
body_.read = true;
|
||||||
|
return get(
|
||||||
|
std::integral_constant<bool, isSplit>{},
|
||||||
|
std::integral_constant<bool, isFinalEmpty>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(
|
||||||
|
std::false_type, // isSplit
|
||||||
|
std::false_type) // isFinalEmpty
|
||||||
|
{
|
||||||
|
using boost::asio::buffer;
|
||||||
|
if(body_.s.empty())
|
||||||
|
return boost::none;
|
||||||
|
return {{buffer(body_.s.data(), body_.s.size()), false}};
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(
|
||||||
|
std::false_type, // isSplit
|
||||||
|
std::true_type) // isFinalEmpty
|
||||||
|
{
|
||||||
|
using boost::asio::buffer;
|
||||||
|
if(body_.s.empty())
|
||||||
|
return boost::none;
|
||||||
|
switch(step_)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
step_ = 1;
|
||||||
|
return {{buffer(
|
||||||
|
body_.s.data(), body_.s.size()), true}};
|
||||||
|
default:
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(
|
||||||
|
std::true_type, // isSplit
|
||||||
|
std::false_type) // isFinalEmpty
|
||||||
|
{
|
||||||
|
using boost::asio::buffer;
|
||||||
|
auto const n = (body_.s.size() + 1) / 2;
|
||||||
|
switch(step_)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if(n == 0)
|
||||||
|
return boost::none;
|
||||||
|
step_ = 1;
|
||||||
|
return {{buffer(body_.s.data(), n),
|
||||||
|
body_.s.size() > 1}};
|
||||||
|
default:
|
||||||
|
return {{buffer(body_.s.data() + n,
|
||||||
|
body_.s.size() - n), false}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(
|
||||||
|
std::true_type, // isSplit
|
||||||
|
std::true_type) // isFinalEmpty
|
||||||
|
{
|
||||||
|
using boost::asio::buffer;
|
||||||
|
auto const n = (body_.s.size() + 1) / 2;
|
||||||
|
switch(step_)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if(n == 0)
|
||||||
|
return boost::none;
|
||||||
|
step_ = body_.s.size() > 1 ? 1 : 2;
|
||||||
|
return {{buffer(body_.s.data(), n), true}};
|
||||||
|
case 1:
|
||||||
|
BOOST_ASSERT(body_.s.size() > 1);
|
||||||
|
step_ = 2;
|
||||||
|
return {{buffer(body_.s.data() + n,
|
||||||
|
body_.s.size() - n), true}};
|
||||||
|
default:
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -72,22 +204,14 @@ public:
|
|||||||
|
|
||||||
std::string s_;
|
std::string s_;
|
||||||
test::fail_counter& fc_;
|
test::fail_counter& fc_;
|
||||||
boost::asio::io_service& ios_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
value_type(test::fail_counter& fc,
|
explicit
|
||||||
boost::asio::io_service& ios)
|
value_type(test::fail_counter& fc)
|
||||||
: fc_(fc)
|
: fc_(fc)
|
||||||
, ios_(ios)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::io_service&
|
|
||||||
get_io_service() const
|
|
||||||
{
|
|
||||||
return ios_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type&
|
value_type&
|
||||||
operator=(std::string s)
|
operator=(std::string s)
|
||||||
{
|
{
|
||||||
@ -102,34 +226,57 @@ public:
|
|||||||
value_type const& body_;
|
value_type const& body_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using is_deferred = std::false_type;
|
||||||
|
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
template<bool isRequest, class Allocator>
|
template<bool isRequest, class Allocator>
|
||||||
explicit
|
explicit
|
||||||
writer(message<isRequest, fail_body, Allocator> const& msg) noexcept
|
writer(message<isRequest, fail_body, Allocator> const& msg)
|
||||||
: body_(msg.body)
|
: body_(msg.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
init(error_code& ec) noexcept
|
init(error_code& ec)
|
||||||
{
|
{
|
||||||
body_.fc_.fail(ec);
|
body_.fc_.fail(ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class WriteFunction>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
bool
|
get(error_code& ec)
|
||||||
write(error_code& ec, WriteFunction&& wf) noexcept
|
|
||||||
{
|
{
|
||||||
if(body_.fc_.fail(ec))
|
if(body_.fc_.fail(ec))
|
||||||
return false;
|
return boost::none;
|
||||||
if(n_ >= body_.s_.size())
|
if(n_ >= body_.s_.size())
|
||||||
return true;
|
return boost::none;
|
||||||
wf(boost::asio::buffer(body_.s_.data() + n_, 1));
|
return {{const_buffers_type{
|
||||||
++n_;
|
body_.s_.data() + n_++, 1}, true}};
|
||||||
return n_ == body_.s_.size();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<bool isRequest>
|
||||||
|
bool
|
||||||
|
equal_body(string_view sv, string_view body)
|
||||||
|
{
|
||||||
|
test::string_istream si{
|
||||||
|
get_io_service(), sv.to_string()};
|
||||||
|
message<isRequest, string_body, fields> m;
|
||||||
|
multi_buffer b;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
read(si, b, m);
|
||||||
|
return m.body == body;
|
||||||
|
}
|
||||||
|
catch(std::exception const& e)
|
||||||
|
{
|
||||||
|
log << "equal_body: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<bool isRequest, class Body, class Fields>
|
template<bool isRequest, class Body, class Fields>
|
||||||
std::string
|
std::string
|
||||||
str(message<isRequest, Body, Fields> const& m)
|
str(message<isRequest, Body, Fields> const& m)
|
||||||
@ -139,43 +286,6 @@ public:
|
|||||||
return ss.str;
|
return ss.str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
testAsyncWriteHeaders(yield_context do_yield)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
header<true, fields> m;
|
|
||||||
m.version = 11;
|
|
||||||
m.method("GET");
|
|
||||||
m.target("/");
|
|
||||||
m.fields.insert("User-Agent", "test");
|
|
||||||
error_code ec;
|
|
||||||
test::string_ostream ss{ios_};
|
|
||||||
async_write(ss, m, do_yield[ec]);
|
|
||||||
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
BEAST_EXPECT(ss.str ==
|
|
||||||
"GET / HTTP/1.1\r\n"
|
|
||||||
"User-Agent: test\r\n"
|
|
||||||
"\r\n");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
header<false, fields> m;
|
|
||||||
m.version = 10;
|
|
||||||
m.status = 200;
|
|
||||||
m.reason("OK");
|
|
||||||
m.fields.insert("Server", "test");
|
|
||||||
m.fields.insert("Content-Length", "5");
|
|
||||||
error_code ec;
|
|
||||||
test::string_ostream ss{ios_};
|
|
||||||
async_write(ss, m, do_yield[ec]);
|
|
||||||
if(BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
BEAST_EXPECT(ss.str ==
|
|
||||||
"HTTP/1.0 200 OK\r\n"
|
|
||||||
"Server: test\r\n"
|
|
||||||
"Content-Length: 5\r\n"
|
|
||||||
"\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
testAsyncWrite(yield_context do_yield)
|
testAsyncWrite(yield_context do_yield)
|
||||||
{
|
{
|
||||||
@ -232,9 +342,7 @@ public:
|
|||||||
test::fail_counter fc(n);
|
test::fail_counter fc(n);
|
||||||
test::fail_stream<
|
test::fail_stream<
|
||||||
test::string_ostream> fs(fc, ios_);
|
test::string_ostream> fs(fc, ios_);
|
||||||
message<true, fail_body, fields> m(
|
message<true, fail_body, fields> m{fc};
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(fc, ios_));
|
|
||||||
m.method("GET");
|
m.method("GET");
|
||||||
m.target("/");
|
m.target("/");
|
||||||
m.version = 10;
|
m.version = 10;
|
||||||
@ -265,9 +373,7 @@ public:
|
|||||||
test::fail_counter fc(n);
|
test::fail_counter fc(n);
|
||||||
test::fail_stream<
|
test::fail_stream<
|
||||||
test::string_ostream> fs(fc, ios_);
|
test::string_ostream> fs(fc, ios_);
|
||||||
message<true, fail_body, fields> m(
|
message<true, fail_body, fields> m{fc};
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(fc, ios_));
|
|
||||||
m.method("GET");
|
m.method("GET");
|
||||||
m.target("/");
|
m.target("/");
|
||||||
m.version = 10;
|
m.version = 10;
|
||||||
@ -300,9 +406,7 @@ public:
|
|||||||
test::fail_counter fc(n);
|
test::fail_counter fc(n);
|
||||||
test::fail_stream<
|
test::fail_stream<
|
||||||
test::string_ostream> fs(fc, ios_);
|
test::string_ostream> fs(fc, ios_);
|
||||||
message<true, fail_body, fields> m(
|
message<true, fail_body, fields> m{fc};
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(fc, ios_));
|
|
||||||
m.method("GET");
|
m.method("GET");
|
||||||
m.target("/");
|
m.target("/");
|
||||||
m.version = 10;
|
m.version = 10;
|
||||||
@ -335,9 +439,7 @@ public:
|
|||||||
test::fail_counter fc(n);
|
test::fail_counter fc(n);
|
||||||
test::fail_stream<
|
test::fail_stream<
|
||||||
test::string_ostream> fs(fc, ios_);
|
test::string_ostream> fs(fc, ios_);
|
||||||
message<true, fail_body, fields> m(
|
message<true, fail_body, fields> m{fc};
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(fc, ios_));
|
|
||||||
m.method("GET");
|
m.method("GET");
|
||||||
m.target("/");
|
m.target("/");
|
||||||
m.version = 10;
|
m.version = 10;
|
||||||
@ -365,9 +467,7 @@ public:
|
|||||||
test::fail_counter fc(n);
|
test::fail_counter fc(n);
|
||||||
test::fail_stream<
|
test::fail_stream<
|
||||||
test::string_ostream> fs(fc, ios_);
|
test::string_ostream> fs(fc, ios_);
|
||||||
message<true, fail_body, fields> m(
|
message<true, fail_body, fields> m{fc};
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(fc, ios_));
|
|
||||||
m.method("GET");
|
m.method("GET");
|
||||||
m.target("/");
|
m.target("/");
|
||||||
m.version = 10;
|
m.version = 10;
|
||||||
@ -558,22 +658,15 @@ public:
|
|||||||
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
|
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
|
||||||
BEAST_EXPECT(boost::lexical_cast<std::string>(m.base()) ==
|
BEAST_EXPECT(boost::lexical_cast<std::string>(m.base()) ==
|
||||||
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n");
|
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n");
|
||||||
// Cause exceptions in operator<<
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
// header
|
||||||
|
ss << m.base();
|
||||||
|
|
||||||
|
// Cause exception in operator<<
|
||||||
ss.setstate(ss.rdstate() |
|
ss.setstate(ss.rdstate() |
|
||||||
std::stringstream::failbit);
|
std::stringstream::failbit);
|
||||||
try
|
try
|
||||||
{
|
|
||||||
// header
|
|
||||||
ss << m.base();
|
|
||||||
fail("", __FILE__, __LINE__);
|
|
||||||
}
|
|
||||||
catch(std::exception const&)
|
|
||||||
{
|
|
||||||
pass();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// message
|
// message
|
||||||
ss << m;
|
ss << m;
|
||||||
@ -664,10 +757,287 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Stream,
|
||||||
|
bool isRequest, class Body, class Fields,
|
||||||
|
class Decorator = empty_decorator>
|
||||||
|
void
|
||||||
|
do_write(Stream& stream, message<
|
||||||
|
isRequest, Body, Fields> const& m, error_code& ec,
|
||||||
|
Decorator const& decorator = Decorator{})
|
||||||
|
{
|
||||||
|
auto ws = make_write_stream(m, decorator);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
stream.nwrite = 0;
|
||||||
|
ws.write_some(stream, ec);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
BEAST_EXPECT(stream.nwrite <= 1);
|
||||||
|
if(ws.is_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Stream,
|
||||||
|
bool isRequest, class Body, class Fields,
|
||||||
|
class Decorator = empty_decorator>
|
||||||
|
void
|
||||||
|
do_async_write(Stream& stream,
|
||||||
|
message<isRequest, Body, Fields> const& m,
|
||||||
|
error_code& ec, yield_context yield,
|
||||||
|
Decorator const& decorator = Decorator{})
|
||||||
|
{
|
||||||
|
auto ws = make_write_stream(m);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
stream.nwrite = 0;
|
||||||
|
ws.async_write_some(stream, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
BEAST_EXPECT(stream.nwrite <= 1);
|
||||||
|
if(ws.is_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct test_decorator
|
||||||
|
{
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
string_view
|
||||||
|
operator()(ConstBufferSequence const&) const
|
||||||
|
{
|
||||||
|
return {";x\r\n"};
|
||||||
|
}
|
||||||
|
|
||||||
|
string_view
|
||||||
|
operator()(boost::asio::null_buffers) const
|
||||||
|
{
|
||||||
|
return {"F: v\r\n"};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Body>
|
||||||
|
void
|
||||||
|
testWriteStream(boost::asio::yield_context yield)
|
||||||
|
{
|
||||||
|
test::pipe p{ios_};
|
||||||
|
p.client.write_size(3);
|
||||||
|
|
||||||
|
message<false, Body, fields> m0;
|
||||||
|
m0.version = 11;
|
||||||
|
m0.status = 200;
|
||||||
|
m0.reason("OK");
|
||||||
|
m0.fields.insert("Server", "test");
|
||||||
|
m0.body.s = "Hello, world!\n";
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string const result =
|
||||||
|
"HTTP/1.1 200 OK\r\n"
|
||||||
|
"Server: test\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Hello, world!\n";
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_write(p.client, m, ec);
|
||||||
|
BEAST_EXPECT(p.server.str() == result);
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_async_write(p.client, m, ec, yield);
|
||||||
|
BEAST_EXPECT(p.server.str() == result);
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
serializer<false, Body, fields> w{m};
|
||||||
|
w.split(true);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.write_some(p.client);
|
||||||
|
if(w.is_header_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(! m.body.read);
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
serializer<false, Body, fields> w{m};
|
||||||
|
w.split(true);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.async_write_some(p.client, yield);
|
||||||
|
if(w.is_header_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(! m.body.read);
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
m0.fields.insert("Transfer-Encoding", "chunked");
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_write(p.client, m, ec);
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_write(p.client, m, ec, test_decorator{});
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_async_write(p.client, m, ec, yield);
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
do_async_write(p.client, m, ec, yield, test_decorator{});
|
||||||
|
BEAST_EXPECT(equal_body<false>(
|
||||||
|
p.server.str(), m.body.s));
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
test::string_ostream so{get_io_service(), 3};
|
||||||
|
serializer<false, Body, fields> w{m};
|
||||||
|
w.split(true);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.write_some(p.client);
|
||||||
|
if(w.is_header_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(! m.body.read);
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto m = m0;
|
||||||
|
error_code ec;
|
||||||
|
serializer<false, Body, fields> w{m};
|
||||||
|
w.split(true);
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.async_write_some(p.client, yield);
|
||||||
|
if(w.is_header_done())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BEAST_EXPECT(! m.body.read);
|
||||||
|
p.server.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Execute a child process and return the output as an HTTP response.
|
||||||
|
|
||||||
|
@param input A stream to read the child process output from.
|
||||||
|
|
||||||
|
@param output A stream to write the HTTP response to.
|
||||||
|
*/
|
||||||
|
template<class SyncReadStream, class SyncWriteStream>
|
||||||
|
void
|
||||||
|
cgi_process(SyncReadStream& input, SyncWriteStream& output, error_code& ec)
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
message<false, buffer_body<true,
|
||||||
|
typename multi_buffer::const_buffers_type>, fields> m;
|
||||||
|
m.status = 200;
|
||||||
|
m.version = 11;
|
||||||
|
m.fields.insert("Server", "cgi-process");
|
||||||
|
m.fields.insert("Transfer-Encoding", "chunked");
|
||||||
|
m.body.first = boost::none;
|
||||||
|
m.body.second = true;
|
||||||
|
|
||||||
|
auto w = make_write_stream(m);
|
||||||
|
|
||||||
|
// send the header first, so the
|
||||||
|
// other end gets it right away
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.write_some(output, ec);
|
||||||
|
if(ec == error::need_more)
|
||||||
|
{
|
||||||
|
ec = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the body
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
// read from input
|
||||||
|
auto bytes_transferred =
|
||||||
|
input.read_some(b.prepare(1024), ec);
|
||||||
|
if(ec == boost::asio::error::eof)
|
||||||
|
{
|
||||||
|
BOOST_ASSERT(bytes_transferred == 0);
|
||||||
|
ec = {};
|
||||||
|
m.body = {boost::none, false};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
b.commit(bytes_transferred);
|
||||||
|
m.body = {b.data(), true};
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to output
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
w.write_some(output, ec);
|
||||||
|
if(ec == error::need_more)
|
||||||
|
{
|
||||||
|
ec = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
if(w.is_done())
|
||||||
|
goto is_done;
|
||||||
|
}
|
||||||
|
b.consume(b.size());
|
||||||
|
}
|
||||||
|
is_done:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testCgiRelay()
|
||||||
|
{
|
||||||
|
error_code ec;
|
||||||
|
std::string const body = "Hello, world!\n";
|
||||||
|
test::string_ostream so{get_io_service(), 3};
|
||||||
|
test::string_istream si{get_io_service(), body, 6};
|
||||||
|
cgi_process(si, so, ec);
|
||||||
|
BEAST_EXPECT(equal_body<false>(so.str, body));
|
||||||
|
}
|
||||||
|
|
||||||
void run() override
|
void run() override
|
||||||
{
|
{
|
||||||
yield_to([&](yield_context yield){
|
|
||||||
testAsyncWriteHeaders(yield); });
|
|
||||||
yield_to([&](yield_context yield){
|
yield_to([&](yield_context yield){
|
||||||
testAsyncWrite(yield); });
|
testAsyncWrite(yield); });
|
||||||
yield_to([&](yield_context yield){
|
yield_to([&](yield_context yield){
|
||||||
@ -676,6 +1046,19 @@ public:
|
|||||||
test_std_ostream();
|
test_std_ostream();
|
||||||
testOstream();
|
testOstream();
|
||||||
testIoService();
|
testIoService();
|
||||||
|
testCgiRelay();
|
||||||
|
yield_to(
|
||||||
|
[&](yield_context yield)
|
||||||
|
{
|
||||||
|
testWriteStream<test_body<false, false, false>>(yield);
|
||||||
|
testWriteStream<test_body<false, false, true>>(yield);
|
||||||
|
testWriteStream<test_body<false, true, false>>(yield);
|
||||||
|
testWriteStream<test_body<false, true, true>>(yield);
|
||||||
|
testWriteStream<test_body< true, false, false>>(yield);
|
||||||
|
testWriteStream<test_body< true, false, true>>(yield);
|
||||||
|
testWriteStream<test_body< true, true, false>>(yield);
|
||||||
|
testWriteStream<test_body< true, true, true>>(yield);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,7 +130,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class NextLayer, class Buffers>
|
template<class NextLayer, class Buffers>
|
||||||
void
|
typename std::enable_if<
|
||||||
|
! http::detail::is_header<Buffers>::value>::type
|
||||||
accept(stream<NextLayer>& ws,
|
accept(stream<NextLayer>& ws,
|
||||||
Buffers const& buffers) const
|
Buffers const& buffers) const
|
||||||
{
|
{
|
||||||
@ -165,7 +166,8 @@ public:
|
|||||||
|
|
||||||
template<class NextLayer,
|
template<class NextLayer,
|
||||||
class Buffers, class Decorator>
|
class Buffers, class Decorator>
|
||||||
void
|
typename std::enable_if<
|
||||||
|
! http::detail::is_header<Buffers>::value>::type
|
||||||
accept_ex(stream<NextLayer>& ws,
|
accept_ex(stream<NextLayer>& ws,
|
||||||
Buffers const& buffers,
|
Buffers const& buffers,
|
||||||
Decorator const& d) const
|
Decorator const& d) const
|
||||||
@ -319,7 +321,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class NextLayer, class Buffers>
|
template<class NextLayer, class Buffers>
|
||||||
void
|
typename std::enable_if<
|
||||||
|
! http::detail::is_header<Buffers>::value>::type
|
||||||
accept(stream<NextLayer>& ws,
|
accept(stream<NextLayer>& ws,
|
||||||
Buffers const& buffers) const
|
Buffers const& buffers) const
|
||||||
{
|
{
|
||||||
@ -367,7 +370,8 @@ public:
|
|||||||
|
|
||||||
template<class NextLayer,
|
template<class NextLayer,
|
||||||
class Buffers, class Decorator>
|
class Buffers, class Decorator>
|
||||||
void
|
typename std::enable_if<
|
||||||
|
! http::detail::is_header<Buffers>::value>::type
|
||||||
accept_ex(stream<NextLayer>& ws,
|
accept_ex(stream<NextLayer>& ws,
|
||||||
Buffers const& buffers,
|
Buffers const& buffers,
|
||||||
Decorator const& d) const
|
Decorator const& d) const
|
||||||
|
Reference in New Issue
Block a user