如何从 std::string 构造一个 boost::beast::http::message?

How to construct a boost::beast::http::message from std::string?

是否可以从 std::stringstd::string_view 或其他原始缓冲区构造一个 boost::beast::http::message(特别是我必须构造一个 boost::beast::http::response<bb_http::string_body>)?

也许有某种解析器?根据我在 Boost.Beast 样本中看到的情况,我们可以:

  1. 收到来自 boost::beast::read* 函数的响应。在这种情况下,第一个参数应该是 SyncReadStream,它必须符合来自 boost/beast/core/type_traits.hpp:
  2. SyncReadStream 的合同
struct is_sync_read_stream<T, detail::void_t<decltype(
    std::declval<std::size_t&>() = std::declval<T>().read_some(
        std::declval<detail::MutableBufferSequence>()),
    std::declval<std::size_t&>() = std::declval<T>().read_some(
        std::declval<detail::MutableBufferSequence>(),
        std::declval<boost::system::error_code&>()),
            (void)0)>> : std::true_type {};
  1. 或者像http::request<http::string_body> req{http::verb::get, target, version};
  2. 那样手工构造它

您可以手动调用解析器,例如使用这个简单的骨架功能:

http::response<http::string_body> do_parse(std::string_view input)
{
    beast::error_code ec;
    http::response_parser<http::string_body> p;

    // read headers
    auto buf = boost::asio::buffer(sample);
    auto n = p.put(buf, ec);
    assert(p.is_header_done());

    // read body
    if (!ec) {
        buf += n;
        n = p.put(buf, ec);
        p.put_eof(ec);
    }
    if (ec)
        throw boost::system::system_error(ec);
    assert(p.is_done());

    return p.release();
}

这假设输入是一个完整的请求。

现场演示

Live On Coliru

#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <string_view>
#include <iostream>
#include <iomanip>
namespace beast = boost::beast;
namespace http = beast::http;

http::response<http::string_body> do_parse(std::string_view input)
{
    beast::error_code ec;
    http::response_parser<http::string_body> p;

    // read headers
    auto buf = boost::asio::buffer(input);
    auto n   = p.put(buf, ec);
    assert(p.is_header_done());

    // read body
    if (!ec) {
        buf += n;
        n = p.put(buf, ec);
        p.put_eof(ec);
    }
    if (ec)
        throw boost::system::system_error(ec);
    assert(p.is_done());

    return p.release();
}

int main() {
    auto res = do_parse(
        "HTTP/1.1 200 OK\r\n"
        "Date: Sun, 10 Oct 2010 23:26:07 GMT\r\n"
        "Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g\r\n"
        "Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT\r\n"
        "ETag: 45b6-834-49130cc1182c0\r\n"
        "Accept-Ranges: bytes\r\n"
        "Content-Length: 12\r\n"
        "Connection: close\r\n"
        "Content-Type: text/html\r\n"
        "\r\n"
        "Hello world!");
    std::cout << res << '\n';
    std::cout << "====== body:\n" << std::quoted(res.body()) << "\n";
}

版画

HTTP/1.1 200 OK
Date: Sun, 10 Oct 2010 23:26:07 GMT
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT
ETag: 45b6-834-49130cc1182c0
Accept-Ranges: bytes
Content-Length: 12
Connection: close
Content-Type: text/html
Hello world!
====== body:
"Hello world!"

原来已经有一个示例片段可以从 std::istream 读取 boost::beast::message。 https://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/buffer_oriented_parsing.html

/** Read a message from a `std::istream`.

    This function attempts to parse a complete HTTP/1 message from the stream.

    @param is The `std::istream` to read from.

    @param buffer The buffer to use.

    @param msg The message to store the result.

    @param ec Set to the error, if any occurred.
*/
template<
    class Allocator,
    bool isRequest,
    class Body>
void
read_istream(
    std::istream& is,
    basic_flat_buffer<Allocator>& buffer,
    message<isRequest, Body, fields>& msg,
    error_code& ec)
{
    // Create the message parser
    //
    // Arguments passed to the parser's constructor are
    // forwarded to the message constructor. Here, we use
    // a move construction in case the caller has constructed
    // their message in a non-default way.
    //
    parser<isRequest, Body> p{std::move(msg)};

    do
    {
        // Extract whatever characters are presently available in the istream
        if(is.rdbuf()->in_avail() > 0)
        {
            // Get a mutable buffer sequence for writing
            auto const b = buffer.prepare(
                static_cast<std::size_t>(is.rdbuf()->in_avail()));

            // Now get everything we can from the istream
            buffer.commit(static_cast<std::size_t>(is.readsome(
                reinterpret_cast<char*>(b.data()), b.size())));
        }
        else if(buffer.size() == 0)
        {
            // Our buffer is empty and we need more characters, 
            // see if we've reached the end of file on the istream
            if(! is.eof())
            {
                // Get a mutable buffer sequence for writing
                auto const b = buffer.prepare(1024);

                // Try to get more from the istream. This might block.
                is.read(reinterpret_cast<char*>(b.data()), b.size());

                // If an error occurs on the istream then return it to the caller.
                if(is.fail() && ! is.eof())
                {
                    // We'll just re-use io_error since std::istream has no error_code interface.
                    ec = make_error_code(errc::io_error);
                    return;
                }

                // Commit the characters we got to the buffer.
                buffer.commit(static_cast<std::size_t>(is.gcount()));
            }
            else
            {
                // Inform the parser that we've reached the end of the istream.
                p.put_eof(ec);
                if(ec)
                    return;
                break;
            }
        }

        // Write the data to the parser
        auto const bytes_used = p.put(buffer.data(), ec);

        // This error means that the parser needs additional octets.
        if(ec == error::need_more)
            ec = {};
        if(ec)
            return;

        // Consume the buffer octets that were actually parsed.
        buffer.consume(bytes_used);
    }
    while(! p.is_done());

    // Transfer ownership of the message container in the parser to the caller.
    msg = p.release();
}