Skip to content

Commit 0a5126c

Browse files
committed
Add parser::read(stream)
1 parent 1ab0b6f commit 0a5126c

6 files changed

Lines changed: 257 additions & 12 deletions

File tree

CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ set(BOOST_HTTP_DEPENDENCIES
6060
Boost::core
6161
Boost::json
6262
Boost::mp11
63-
Boost::static_assert
6463
Boost::system
6564
Boost::throw_exception
6665
Boost::type_traits

CMakePresets.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

include/boost/http/parser.hpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,31 @@ class parser
340340
capy::io_task<>
341341
read_header(Stream& stream);
342342

343+
/** Asynchronously read a complete HTTP message.
344+
345+
Reads from the stream until the message is fully
346+
parsed or an error occurs. The body is accumulated
347+
in the parser's internal buffer and can be retrieved
348+
via @ref body after completion.
349+
350+
If the parser's internal buffer fills before the
351+
message is complete, the operation completes with
352+
@ref error::in_place_overflow.
353+
354+
@par Preconditions
355+
@li @ref reset has been called
356+
@li @ref start has been called
357+
358+
@param stream The stream to read from.
359+
360+
@return An awaitable yielding `(error_code)`.
361+
362+
@see @ref body, @ref read_header.
363+
*/
364+
template<capy::ReadStream Stream>
365+
capy::io_task<>
366+
read(Stream& stream);
367+
343368
/** Asynchronously read body data into buffers.
344369
345370
Reads from the stream and copies body data into
@@ -517,6 +542,37 @@ read_header(Stream& stream)
517542
}
518543
}
519544

545+
template<capy::ReadStream Stream>
546+
capy::io_task<>
547+
parser::
548+
read(Stream& stream)
549+
{
550+
system::error_code ec;
551+
for(;;)
552+
{
553+
parse(ec);
554+
555+
if(is_complete())
556+
co_return {};
557+
558+
if(ec && ec != condition::need_more_input)
559+
co_return {ec};
560+
561+
if(ec == condition::need_more_input)
562+
{
563+
auto mbs = prepare();
564+
565+
auto [read_ec, n] = co_await stream.read_some(mbs);
566+
if(read_ec == capy::cond::eof)
567+
commit_eof();
568+
else if(!read_ec)
569+
commit(n);
570+
else
571+
co_return {read_ec};
572+
}
573+
}
574+
}
575+
520576
template<capy::ReadStream Stream, capy::MutableBufferSequence MB>
521577
capy::io_task<std::size_t>
522578
parser::

src/error.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ message(
4343
case error::expect_100_continue: return "expect 100 continue";
4444
case error::end_of_message: return "end of message";
4545
case error::end_of_stream: return "end of stream";
46-
case error::in_place_overflow: return "in place overflow";
46+
case error::in_place_overflow: return "in-place overflow";
4747
case error::need_data: return "need data";
4848

4949
case error::bad_connection: return "bad Connection";

src/parser.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,10 @@ class parser::impl
12731273
(is_plain() ? cb0_ : cb1_).data(),
12741274
body_avail_);
12751275
return detail::make_span(cbp_);
1276+
case state::reset:
1277+
if(got_header_)
1278+
return {};
1279+
BOOST_FALLTHROUGH;
12761280
default:
12771281
detail::throw_logic_error();
12781282
}
@@ -1291,6 +1295,10 @@ class parser::impl
12911295
(is_plain() ? cb0_ : cb1_).consume(n);
12921296
body_avail_ -= n;
12931297
return;
1298+
case state::reset:
1299+
if(got_header_)
1300+
return;
1301+
BOOST_FALLTHROUGH;
12941302
default:
12951303
detail::throw_logic_error();
12961304
}

test/unit/parser.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,192 @@ struct parser_coro_test
19741974
BOOST_TEST(r.success);
19751975
}
19761976

1977+
void
1978+
testSourceForLargeBody()
1979+
{
1980+
capy::test::fuse f;
1981+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
1982+
{
1983+
std::string const expected(40000, 'x');
1984+
1985+
capy::test::read_stream rs(f);
1986+
rs.provide(
1987+
"HTTP/1.1 200 OK\r\n"
1988+
"Content-Length: 40000\r\n"
1989+
"\r\n");
1990+
rs.provide(expected);
1991+
1992+
response_parser pr(res_cfg_);
1993+
pr.reset();
1994+
pr.start();
1995+
1996+
auto source = pr.source_for(rs);
1997+
1998+
std::string body;
1999+
capy::const_buffer arr[16];
2000+
2001+
for(;;)
2002+
{
2003+
auto [ec, bufs] = co_await source.pull(arr);
2004+
if(ec == capy::cond::eof)
2005+
break;
2006+
BOOST_TEST(ec != error::in_place_overflow);
2007+
if(ec)
2008+
co_return;
2009+
std::size_t n = 0;
2010+
for(auto const& buf : bufs)
2011+
{
2012+
body.append(
2013+
static_cast<char const*>(buf.data()),
2014+
buf.size());
2015+
n += buf.size();
2016+
}
2017+
source.consume(n);
2018+
}
2019+
2020+
BOOST_TEST(body == expected);
2021+
BOOST_TEST(pr.is_complete());
2022+
});
2023+
BOOST_TEST(r.success);
2024+
}
2025+
2026+
void
2027+
testSourceForBodyTooLarge()
2028+
{
2029+
capy::test::fuse f;
2030+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
2031+
{
2032+
parser_config cfg{false};
2033+
cfg.body_limit = 5;
2034+
auto small_cfg = make_parser_config(cfg);
2035+
2036+
capy::test::read_stream rs(f, 1);
2037+
rs.provide(
2038+
"HTTP/1.1 200 OK\r\n"
2039+
"Content-Length: 100\r\n"
2040+
"\r\n"
2041+
"this body is way over the configured limit");
2042+
2043+
response_parser pr(small_cfg);
2044+
pr.reset();
2045+
pr.start();
2046+
2047+
auto source = pr.source_for(rs);
2048+
2049+
capy::const_buffer arr[16];
2050+
auto [ec, bufs] = co_await source.pull(arr);
2051+
2052+
BOOST_TEST(static_cast<bool>(ec));
2053+
BOOST_TEST(ec != capy::cond::eof);
2054+
BOOST_TEST(capy::buffer_size(bufs) == 0);
2055+
BOOST_TEST(! pr.is_complete());
2056+
});
2057+
BOOST_TEST(r.success);
2058+
}
2059+
2060+
void
2061+
testRead()
2062+
{
2063+
capy::test::fuse f;
2064+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
2065+
{
2066+
capy::test::read_stream rs(f, 1);
2067+
rs.provide(
2068+
"HTTP/1.1 200 OK\r\n"
2069+
"Content-Length: 13\r\n"
2070+
"\r\n"
2071+
"Hello, World!");
2072+
2073+
response_parser pr(res_cfg_);
2074+
pr.reset();
2075+
pr.start();
2076+
2077+
auto [ec] = co_await pr.read(rs);
2078+
if(ec)
2079+
co_return;
2080+
2081+
BOOST_TEST(pr.is_complete());
2082+
BOOST_TEST(pr.body() == "Hello, World!");
2083+
});
2084+
BOOST_TEST(r.success);
2085+
}
2086+
2087+
void
2088+
testReadChunked()
2089+
{
2090+
capy::test::fuse f;
2091+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
2092+
{
2093+
capy::test::read_stream rs(f, 1);
2094+
rs.provide(
2095+
"HTTP/1.1 200 OK\r\n"
2096+
"Transfer-Encoding: chunked\r\n"
2097+
"\r\n"
2098+
"5\r\nHello\r\n"
2099+
"7\r\n, World\r\n"
2100+
"0\r\n\r\n");
2101+
2102+
response_parser pr(res_cfg_);
2103+
pr.reset();
2104+
pr.start();
2105+
2106+
auto [ec] = co_await pr.read(rs);
2107+
if(ec)
2108+
co_return;
2109+
2110+
BOOST_TEST(pr.is_complete());
2111+
BOOST_TEST(pr.body() == "Hello, World");
2112+
});
2113+
BOOST_TEST(r.success);
2114+
}
2115+
2116+
void
2117+
testReadEof()
2118+
{
2119+
capy::test::fuse f;
2120+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
2121+
{
2122+
capy::test::read_stream rs(f, 1);
2123+
2124+
response_parser pr(res_cfg_);
2125+
pr.reset();
2126+
pr.start();
2127+
2128+
auto [ec] = co_await pr.read(rs);
2129+
2130+
BOOST_TEST(static_cast<bool>(ec));
2131+
BOOST_TEST(! pr.is_complete());
2132+
});
2133+
BOOST_TEST(r.success);
2134+
}
2135+
2136+
void
2137+
testReadOverflow()
2138+
{
2139+
capy::test::fuse f;
2140+
auto r = f.armed([&](capy::test::fuse&) -> capy::task<>
2141+
{
2142+
std::string const big(40000, 'x');
2143+
2144+
capy::test::read_stream rs(f);
2145+
rs.provide(
2146+
"HTTP/1.1 200 OK\r\n"
2147+
"Content-Length: 40000\r\n"
2148+
"\r\n");
2149+
rs.provide(big);
2150+
2151+
response_parser pr(res_cfg_);
2152+
pr.reset();
2153+
pr.start();
2154+
2155+
auto [ec] = co_await pr.read(rs);
2156+
2157+
BOOST_TEST(static_cast<bool>(ec));
2158+
BOOST_TEST(! pr.is_complete());
2159+
});
2160+
BOOST_TEST(r.success);
2161+
}
2162+
19772163
void
19782164
run()
19792165
{
@@ -1982,6 +2168,12 @@ struct parser_coro_test
19822168
testReadWriteSinkChunked();
19832169
testSourceFor();
19842170
testSourceForChunked();
2171+
testSourceForLargeBody();
2172+
testSourceForBodyTooLarge();
2173+
testRead();
2174+
testReadChunked();
2175+
testReadEof();
2176+
testReadOverflow();
19852177
}
19862178
};
19872179

0 commit comments

Comments
 (0)