Skip to content

Commit

Permalink
stream prepare has nullary overload
Browse files Browse the repository at this point in the history
  • Loading branch information
cmazakas committed Apr 8, 2024
1 parent b5cbccc commit c3e0dfb
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 60 deletions.
20 changes: 17 additions & 3 deletions include/boost/http_proto/serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,25 +247,35 @@ class BOOST_SYMBOL_VISIBLE
// trailer-section
// CRLF

static
constexpr
std::size_t
crlf_len_ = 2;

// chunk = chunk-size [ chunk-ext ] CRLF
// chunk-data CRLF
static
constexpr
std::size_t
chunk_header_len_ = 16 + 2; // chunk-size + CRLF
chunk_header_len_ =
16 + // 16 hex digits => 64 bit number
crlf_len_;

// last-chunk = 1*("0") [ chunk-ext ] CRLF
static
constexpr
std::size_t
last_chunk_len_ = 1 + 2 + 2; // chunked-body termination requires an extra CRLF
last_chunk_len_ =
1 + // "0"
crlf_len_ +
crlf_len_; // chunked-body termination requires an extra CRLF

static
constexpr
std::size_t
chunked_overhead_ =
chunk_header_len_ +
2 + // CRLF
crlf_len_ + // closing chunk data
last_chunk_len_;

detail::workspace ws_;
Expand Down Expand Up @@ -313,6 +323,10 @@ struct serializer::stream
std::size_t
size() const;

BOOST_HTTP_PROTO_DECL
buffers_type
prepare() const;

BOOST_HTTP_PROTO_DECL
buffers_type
prepare(std::size_t n) const;
Expand Down
31 changes: 27 additions & 4 deletions src/serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,18 @@ size() const
return sr_->tmp0_.size();
}

auto
serializer::
stream::
prepare() const ->
buffers_type
{
auto n = sr_->tmp0_.capacity();
if( sr_->is_chunked_ )
n -= chunked_overhead_;
return prepare(n);
}

auto
serializer::
stream::
Expand All @@ -565,10 +577,21 @@ prepare(
buffers_type
{
if( sr_->is_chunked_ )
{
// for chunked encoding, we want to unconditionally
// reserve space for the complete chunk and the
// last-chunk
// this enables users to call
// stream.commit(n); stream.close();
// without needing to worry about draining the
// serializer via `consume()` calls
if( sr_->tmp0_.capacity() < n + chunked_overhead_ )
detail::throw_length_error();

return buffers::sans_prefix(
sr_->tmp0_.prepare(n + chunk_header_len_),
sr_->tmp0_.prepare(chunk_header_len_ + n),
chunk_header_len_);

}
return sr_->tmp0_.prepare(n);
}

Expand Down Expand Up @@ -603,10 +626,10 @@ stream::
close() const
{
// Precondition violation
if(! sr_->more_)
if(! sr_->more_ )
detail::throw_logic_error();

if (sr_->is_chunked_)
if( sr_->is_chunked_ )
write_last_chunk(sr_->tmp0_);

sr_->more_ = false;
Expand Down
222 changes: 169 additions & 53 deletions test/unit/serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,26 +255,49 @@ struct serializer_test
f(s);
};

struct check_stream_opts
{
std::size_t chunk_size = 100;
std::size_t sr_capacity = 1024;
bool use_full_stream_cap = false;
};

template <class F>
void
check_stream(
core::string_view headers,
core::string_view body,
check_stream_opts const& opts,
F f)
{
auto sr_capacity = opts.sr_capacity;

response res(headers);

serializer sr(1024);
serializer sr(sr_capacity);
auto stream = sr.start_stream(res);

std::vector<char> s; // stores complete output
std::size_t const chunk_data_size = 100;

auto chunk_size = opts.chunk_size;
auto use_full_stream_cap = opts.use_full_stream_cap;

auto prepare_chunk = [&]
{
auto mbs = stream.prepare(chunk_data_size);
auto mbs =
use_full_stream_cap ?
stream.prepare() :
stream.prepare(chunk_size);

auto bs = buffers::buffer_size(mbs);
BOOST_TEST_EQ(bs, chunk_data_size);
if (use_full_stream_cap) {
BOOST_TEST_NO_THROW(stream.prepare(bs));
BOOST_TEST_THROWS(
stream.prepare(bs + 1),
std::length_error);
} else {
BOOST_TEST_EQ(bs, chunk_size);
}

if( bs > body.size() )
bs = body.size();
Expand All @@ -300,8 +323,7 @@ struct serializer_test
while( buf.size() > 0 )
{
auto num_copied =
buffers::buffer_copy(
out_buf, buf);
buffers::buffer_copy(out_buf, buf);

buf += num_copied;

Expand Down Expand Up @@ -454,55 +476,149 @@ struct serializer_test
check_chunked_body(s, "");
});

check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n",
std::string(0, '*'),
[](core::string_view s){
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
BOOST_TEST_EQ(s, std::string(0, '*'));
});
// empty stream
{
check_stream_opts opts;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n",
std::string(0, '*'),
opts,
[](core::string_view s)
{
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
BOOST_TEST(s.empty());
});
}

check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 2048\r\n"
"\r\n",
std::string(2048, '*'),
[](core::string_view s){
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 2048\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
BOOST_TEST_EQ(s, std::string(2048, '*'));
});
// empty stream, chunked
{
check_stream_opts opts;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n",
std::string(0, '*'),
opts,
[](core::string_view s)
{
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
check_chunked_body(s, "");
});
}

check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n",
std::string(2048, '*'),
[](core::string_view s){
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
check_chunked_body(
s, std::string(2048, '*'));
});
// stream, fixed size
{
check_stream_opts opts;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 2048\r\n"
"\r\n",
std::string(2048, '*'),
opts,
[](core::string_view s){
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 2048\r\n"
"\r\n";
BOOST_TEST(s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
BOOST_TEST_EQ(s, std::string(2048, '*'));
});
}

// stream, all available capacity
{
check_stream_opts opts;
opts.use_full_stream_cap = true;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 13370\r\n"
"\r\n",
std::string(13370, '*'),
opts,
[](core::string_view s){
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Content-Length: 13370\r\n"
"\r\n";

BOOST_TEST(
s.starts_with(expected_header));

s.remove_prefix(expected_header.size());
BOOST_TEST_EQ(
s, std::string(13370, '*'));
});
}

// stream, chunked, fixed size
{
check_stream_opts opts;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n",
std::string(2048, '*'),
opts,
[](core::string_view s)
{
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n";
BOOST_TEST(
s.starts_with(expected_header));
s.remove_prefix(expected_header.size());
check_chunked_body(
s, std::string(2048, '*'));
});
}

// stream, chunked, all available capacity
{
check_stream_opts opts;
opts.use_full_stream_cap = true;
check_stream(
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n",
std::string(13370, '*'),
opts,
[](core::string_view s)
{
core::string_view expected_header =
"HTTP/1.1 200 OK\r\n"
"Server: test\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n";
BOOST_TEST(s.starts_with(
expected_header));
s.remove_prefix(expected_header.size());
check_chunked_body(
s, std::string(13370, '*'));
});
}
}

void
Expand Down

0 comments on commit c3e0dfb

Please sign in to comment.