forked from boostorg/beast
serializer interface is changed to be buffer-only, no streams, and placed in its own header file. Operations on serializers are moved to free functions as part of the HTTP write family of synchronous and asynchronous algorithms.
408 lines
9.8 KiB
C++
408 lines
9.8 KiB
C++
//
|
|
// 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_IMPL_SERIALIZER_IPP
|
|
#define BEAST_HTTP_IMPL_SERIALIZER_IPP
|
|
|
|
#include <beast/http/error.hpp>
|
|
#include <boost/assert.hpp>
|
|
#include <ostream>
|
|
|
|
namespace beast {
|
|
namespace http {
|
|
namespace detail {
|
|
|
|
template<class Fields>
|
|
void
|
|
write_start_line(std::ostream& os,
|
|
header<true, Fields> const& msg)
|
|
{
|
|
BOOST_ASSERT(msg.version == 10 || msg.version == 11);
|
|
os << msg.method() << " " << msg.target();
|
|
switch(msg.version)
|
|
{
|
|
case 10: os << " HTTP/1.0\r\n"; break;
|
|
case 11: os << " HTTP/1.1\r\n"; break;
|
|
}
|
|
}
|
|
|
|
template<class Fields>
|
|
void
|
|
write_start_line(std::ostream& os,
|
|
header<false, Fields> const& msg)
|
|
{
|
|
BOOST_ASSERT(msg.version == 10 || msg.version == 11);
|
|
switch(msg.version)
|
|
{
|
|
case 10: os << "HTTP/1.0 "; break;
|
|
case 11: os << "HTTP/1.1 "; break;
|
|
}
|
|
os << msg.status << " " << msg.reason() << "\r\n";
|
|
}
|
|
|
|
template<class FieldSequence>
|
|
void
|
|
write_fields(std::ostream& os,
|
|
FieldSequence const& fields)
|
|
{
|
|
//static_assert(is_FieldSequence<FieldSequence>::value,
|
|
// "FieldSequence requirements not met");
|
|
for(auto const& field : fields)
|
|
{
|
|
auto const name = field.name();
|
|
BOOST_ASSERT(! name.empty());
|
|
if(name[0] == ':')
|
|
continue;
|
|
os << field.name() << ": " << field.value() << "\r\n";
|
|
}
|
|
}
|
|
|
|
} // detail
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template<bool isRequest, class Body, class Fields,
|
|
class Decorator, class Allocator>
|
|
serializer<isRequest, Body, Fields,
|
|
Decorator, Allocator>::
|
|
serializer(message<isRequest, Body, Fields> const& m,
|
|
Decorator const& d, Allocator const& alloc)
|
|
: m_(m)
|
|
, d_(d)
|
|
, b_(1024, alloc)
|
|
, chunked_(token_list{
|
|
m.fields["Transfer-Encoding"]}.exists("chunked"))
|
|
, close_(token_list{
|
|
m.fields["Connection"]}.exists("close") ||
|
|
(m.version < 11 && ! m.fields.exists(
|
|
"Content-Length")))
|
|
{
|
|
s_ = chunked_ ? do_init_c : do_init;
|
|
// VFALCO Move this stuff to the switch?
|
|
auto os = ostream(b_);
|
|
detail::write_start_line(os, m_);
|
|
detail::write_fields(os, m_.fields);
|
|
os << "\r\n";
|
|
}
|
|
|
|
template<bool isRequest, class Body, class Fields,
|
|
class Decorator, class Allocator>
|
|
template<class Visit>
|
|
void
|
|
serializer<isRequest, Body, Fields,
|
|
Decorator, Allocator>::
|
|
get(error_code& ec, Visit&& visit)
|
|
{
|
|
using boost::asio::buffer_size;
|
|
switch(s_)
|
|
{
|
|
case do_init:
|
|
{
|
|
if(split_)
|
|
goto go_header_only;
|
|
rd_.emplace(m_);
|
|
rd_->init(ec);
|
|
if(ec)
|
|
return;
|
|
auto result = rd_->get(ec);
|
|
if(ec)
|
|
{
|
|
// Can't use need_more when ! is_deferred
|
|
BOOST_ASSERT(ec != error::need_more);
|
|
return;
|
|
}
|
|
if(! result)
|
|
goto go_header_only;
|
|
more_ = result->second;
|
|
v_ = cb0_t{
|
|
boost::in_place_init,
|
|
b_.data(),
|
|
result->first};
|
|
s_ = do_header;
|
|
// [[fallthrough]]
|
|
}
|
|
|
|
case do_header:
|
|
visit(ec, boost::get<cb0_t>(v_));
|
|
break;
|
|
|
|
go_header_only:
|
|
s_ = do_header_only;
|
|
case do_header_only:
|
|
visit(ec, b_.data());
|
|
break;
|
|
|
|
case do_body:
|
|
BOOST_ASSERT(! rd_);
|
|
rd_.emplace(m_);
|
|
rd_->init(ec);
|
|
if(ec)
|
|
return;
|
|
s_ = do_body + 1;
|
|
// [[fallthrough]]
|
|
|
|
case do_body + 1:
|
|
{
|
|
auto result = rd_->get(ec);
|
|
if(ec)
|
|
return;
|
|
if(! result)
|
|
goto go_complete;
|
|
more_ = result->second;
|
|
v_ = cb1_t{result->first};
|
|
s_ = do_body + 2;
|
|
// [[fallthrough]]
|
|
}
|
|
|
|
case do_body + 2:
|
|
visit(ec, boost::get<cb1_t>(v_));
|
|
break;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
case do_init_c:
|
|
{
|
|
if(split_)
|
|
goto go_header_only_c;
|
|
rd_.emplace(m_);
|
|
rd_->init(ec);
|
|
if(ec)
|
|
return;
|
|
auto result = rd_->get(ec);
|
|
if(ec)
|
|
{
|
|
// Can't use need_more when ! is_deferred
|
|
BOOST_ASSERT(ec != error::need_more);
|
|
return;
|
|
}
|
|
if(! result)
|
|
goto go_header_only_c;
|
|
more_ = result->second;
|
|
v_ = ch0_t{
|
|
boost::in_place_init,
|
|
b_.data(),
|
|
detail::chunk_header{
|
|
buffer_size(result->first)},
|
|
[&]()
|
|
{
|
|
auto sv = d_(result->first);
|
|
return boost::asio::const_buffers_1{
|
|
sv.data(), sv.size()};
|
|
|
|
}(),
|
|
result->first,
|
|
detail::chunk_crlf()};
|
|
s_ = do_header_c;
|
|
// [[fallthrough]]
|
|
}
|
|
|
|
case do_header_c:
|
|
visit(ec, boost::get<ch0_t>(v_));
|
|
break;
|
|
|
|
go_header_only_c:
|
|
s_ = do_header_only_c;
|
|
case do_header_only_c:
|
|
visit(ec, b_.data());
|
|
break;
|
|
|
|
case do_body_c:
|
|
BOOST_ASSERT(! rd_);
|
|
rd_.emplace(m_);
|
|
rd_->init(ec);
|
|
if(ec)
|
|
return;
|
|
s_ = do_body_c + 1;
|
|
// [[fallthrough]]
|
|
|
|
case do_body_c + 1:
|
|
{
|
|
auto result = rd_->get(ec);
|
|
if(ec)
|
|
return;
|
|
if(! result)
|
|
goto go_final_c;
|
|
more_ = result->second;
|
|
v_ = ch1_t{
|
|
boost::in_place_init,
|
|
detail::chunk_header{
|
|
buffer_size(result->first)},
|
|
[&]()
|
|
{
|
|
auto sv = d_(result->first);
|
|
return boost::asio::const_buffers_1{
|
|
sv.data(), sv.size()};
|
|
|
|
}(),
|
|
result->first,
|
|
detail::chunk_crlf()};
|
|
s_ = do_body_c + 2;
|
|
// [[fallthrough]]
|
|
}
|
|
|
|
case do_body_c + 2:
|
|
visit(ec, boost::get<ch1_t>(v_));
|
|
break;
|
|
|
|
go_final_c:
|
|
case do_final_c:
|
|
v_ = ch2_t{
|
|
boost::in_place_init,
|
|
detail::chunk_final(),
|
|
[&]()
|
|
{
|
|
auto sv = d_(
|
|
boost::asio::null_buffers{});
|
|
return boost::asio::const_buffers_1{
|
|
sv.data(), sv.size()};
|
|
|
|
}(),
|
|
detail::chunk_crlf()};
|
|
s_ = do_final_c + 1;
|
|
// [[fallthrough]]
|
|
|
|
case do_final_c + 1:
|
|
visit(ec, boost::get<ch2_t>(v_));
|
|
break;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
default:
|
|
case do_complete:
|
|
BOOST_ASSERT(false);
|
|
break;
|
|
|
|
go_complete:
|
|
s_ = do_complete;
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<bool isRequest, class Body, class Fields,
|
|
class Decorator, class Allocator>
|
|
void
|
|
serializer<isRequest, Body, Fields,
|
|
Decorator, Allocator>::
|
|
consume(std::size_t n)
|
|
{
|
|
using boost::asio::buffer_size;
|
|
switch(s_)
|
|
{
|
|
case do_header:
|
|
BOOST_ASSERT(n <= buffer_size(
|
|
boost::get<cb0_t>(v_)));
|
|
boost::get<cb0_t>(v_).consume(n);
|
|
if(buffer_size(boost::get<cb0_t>(v_)) > 0)
|
|
break;
|
|
header_done_ = true;
|
|
v_ = boost::blank{};
|
|
b_.consume(b_.size()); // VFALCO delete b_?
|
|
if(! more_)
|
|
goto go_complete;
|
|
s_ = do_body + 1;
|
|
break;
|
|
|
|
case do_header_only:
|
|
BOOST_ASSERT(n <= b_.size());
|
|
b_.consume(n);
|
|
if(buffer_size(b_.data()) > 0)
|
|
break;
|
|
// VFALCO delete b_?
|
|
header_done_ = true;
|
|
if(! is_deferred::value)
|
|
goto go_complete;
|
|
s_ = do_body;
|
|
break;
|
|
|
|
case do_body + 2:
|
|
{
|
|
BOOST_ASSERT(n <= buffer_size(
|
|
boost::get<cb1_t>(v_)));
|
|
boost::get<cb1_t>(v_).consume(n);
|
|
if(buffer_size(boost::get<cb1_t>(v_)) > 0)
|
|
break;
|
|
v_ = boost::blank{};
|
|
if(! more_)
|
|
goto go_complete;
|
|
s_ = do_body + 1;
|
|
break;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
case do_header_c:
|
|
BOOST_ASSERT(n <= buffer_size(
|
|
boost::get<ch0_t>(v_)));
|
|
boost::get<ch0_t>(v_).consume(n);
|
|
if(buffer_size(boost::get<ch0_t>(v_)) > 0)
|
|
break;
|
|
header_done_ = true;
|
|
v_ = boost::blank{};
|
|
b_.consume(b_.size()); // VFALCO delete b_?
|
|
if(more_)
|
|
s_ = do_body_c + 1;
|
|
else
|
|
s_ = do_final_c;
|
|
break;
|
|
|
|
case do_header_only_c:
|
|
{
|
|
BOOST_ASSERT(n <= buffer_size(b_.data()));
|
|
b_.consume(n);
|
|
if(buffer_size(b_.data()) > 0)
|
|
break;
|
|
// VFALCO delete b_?
|
|
header_done_ = true;
|
|
if(! is_deferred::value)
|
|
{
|
|
s_ = do_final_c;
|
|
break;
|
|
}
|
|
s_ = do_body_c;
|
|
break;
|
|
}
|
|
|
|
case do_body_c + 2:
|
|
BOOST_ASSERT(n <= buffer_size(
|
|
boost::get<ch1_t>(v_)));
|
|
boost::get<ch1_t>(v_).consume(n);
|
|
if(buffer_size(boost::get<ch1_t>(v_)) > 0)
|
|
break;
|
|
v_ = boost::blank{};
|
|
if(more_)
|
|
s_ = do_body_c + 1;
|
|
else
|
|
s_ = do_final_c;
|
|
break;
|
|
|
|
case do_final_c + 1:
|
|
BOOST_ASSERT(buffer_size(
|
|
boost::get<ch2_t>(v_)));
|
|
boost::get<ch2_t>(v_).consume(n);
|
|
if(buffer_size(boost::get<ch2_t>(v_)) > 0)
|
|
break;
|
|
v_ = boost::blank{};
|
|
goto go_complete;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
default:
|
|
BOOST_ASSERT(false);
|
|
case do_complete:
|
|
break;
|
|
|
|
go_complete:
|
|
s_ = do_complete;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // http
|
|
} // beast
|
|
|
|
#endif
|