Distinguish HTTP/1 messages from general HTTP messages:

The version field is moved into message_v1, all public interfaces
are reworked to identify HTTP/1 wire format operations (suffix "_v1")
versus general HTTP.
This commit is contained in:
Vinnie Falco
2017-07-20 08:12:05 -07:00
parent cdde9b5ef1
commit c309f53ed2
42 changed files with 724 additions and 697 deletions
+2 -1
View File
@@ -33,11 +33,12 @@ add_executable (http-tests
${BEAST_INCLUDES}
main.cpp
http/basic_headers.cpp
http/basic_parser.cpp
http/basic_parser_v1.cpp
http/empty_body.cpp
http/error.cpp
http/headers.cpp
http/message.cpp
http/message_v1.cpp
http/parse_error.cpp
http/parser.cpp
http/read.cpp
+2 -1
View File
@@ -31,11 +31,12 @@ unit-test core-tests :
unit-test http-tests :
main.cpp
http/basic_headers.cpp
http/basic_parser.cpp
http/basic_parser_v1.cpp
http/empty_body.cpp
http/error.cpp
http/headers.cpp
http/message.cpp
http/message_v1.cpp
http/parse_error.cpp
http/parser.cpp
http/read.cpp
@@ -6,7 +6,7 @@
//
// Test that header file is self-contained.
#include <beast/http/basic_parser.hpp>
#include <beast/http/basic_parser_v1.hpp>
#include "message_fuzz.hpp"
@@ -27,7 +27,7 @@
namespace beast {
namespace http {
class basic_parser_test : public beast::detail::unit_test::suite
class basic_parser_v1_test : public beast::detail::unit_test::suite
{
std::mt19937 rng_;
@@ -47,9 +47,9 @@ public:
template<bool isRequest>
struct cb_checker
: public basic_parser<isRequest, cb_checker<isRequest>>
: public basic_parser_v1<isRequest, cb_checker<isRequest>>
, std::conditional<isRequest,
cb_req_checker, cb_res_checker>::type
cb_req_checker, cb_res_checker>::type
{
bool field = false;
@@ -59,7 +59,7 @@ public:
bool complete = false;
private:
friend class basic_parser<isRequest, cb_checker<isRequest>>;
friend class basic_parser_v1<isRequest, cb_checker<isRequest>>;
void on_method(boost::string_ref const&, error_code&)
{
@@ -129,13 +129,13 @@ public:
}
template<bool isRequest>
struct null_parser : basic_parser<isRequest, null_parser<isRequest>>
struct null_parser : basic_parser_v1<isRequest, null_parser<isRequest>>
{
};
template<bool isRequest>
class test_parser :
public basic_parser<isRequest, test_parser<isRequest>>
public basic_parser_v1<isRequest, test_parser<isRequest>>
{
std::string field_;
std::string value_;
@@ -635,7 +635,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(basic_parser,http,beast);
BEAST_DEFINE_TESTSUITE(basic_parser_v1,http,beast);
} // http
} // beast
-165
View File
@@ -19,168 +19,3 @@
// Test that header file is self-contained.
#include <beast/http/message.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <beast/detail/unit_test/thread.hpp>
#include <beast/placeholders.hpp>
#include <beast/streambuf.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
namespace beast {
namespace http {
namespace test {
class sync_echo_http_server
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
private:
beast::detail::unit_test::suite& suite_;
boost::asio::io_service ios_;
socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_;
beast::detail::unit_test::thread thread_;
public:
sync_echo_http_server(
endpoint_type ep, beast::detail::unit_test::suite& suite)
: suite_(suite)
, sock_(ios_)
, acceptor_(ios_)
{
error_code ec;
acceptor_.open(ep.protocol(), ec);
maybe_throw(ec, "open");
acceptor_.bind(ep, ec);
maybe_throw(ec, "bind");
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
maybe_throw(ec, "listen");
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
thread_ = beast::detail::unit_test::thread(suite_,
[&]
{
ios_.run();
});
}
~sync_echo_http_server()
{
error_code ec;
ios_.dispatch(
[&]{ acceptor_.close(ec); });
thread_.join();
}
private:
void
fail(error_code ec, std::string what)
{
suite_.log <<
what << ": " << ec.message();
}
void
maybe_throw(error_code ec, std::string what)
{
if(ec &&
ec != boost::asio::error::operation_aborted)
{
fail(ec, what);
throw ec;
}
}
void
on_accept(error_code ec)
{
if(ec == boost::asio::error::operation_aborted)
return;
maybe_throw(ec, "accept");
std::thread{&sync_echo_http_server::do_client, this,
std::move(sock_), boost::asio::io_service::work{
sock_.get_io_service()}}.detach();
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
}
void
do_client(socket_type sock, boost::asio::io_service::work)
{
error_code ec;
streambuf rb;
for(;;)
{
request<string_body> req;
read(sock, rb, req, ec);
if(ec)
break;
response<string_body> resp(
{100, "OK", req.version});
resp.body = "Completed successfully.";
write(sock, resp, ec);
if(ec)
break;
}
}
};
class message_test : public beast::detail::unit_test::suite
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
void
syncEcho(endpoint_type ep)
{
boost::asio::io_service ios;
socket_type sock(ios);
sock.connect(ep);
streambuf rb;
{
request<string_body> req({"GET", "/", 11});
req.body = "Beast.HTTP";
req.headers.replace("Host",
ep.address().to_string() + ":" +
std::to_string(ep.port()));
write(sock, req);
}
{
response<string_body> m;
read(sock, rb, m);
}
}
void
testAsio()
{
endpoint_type ep{
address_type::from_string("127.0.0.1"), 6000};
sync_echo_http_server s(ep, *this);
syncEcho(ep);
}
void run() override
{
testAsio();
pass();
}
};
BEAST_DEFINE_TESTSUITE(message,http,beast);
} // test
} // http
} // beast
+1 -1
View File
@@ -8,7 +8,7 @@
#ifndef BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP
#define BEAST_HTTP_TEST_MESSAGE_FUZZ_HPP
#include <beast/http/detail/basic_parser.hpp>
#include <beast/http/detail/basic_parser_v1.hpp>
#include <beast/write_streambuf.hpp>
#include <cstdint>
#include <random>
+186
View File
@@ -0,0 +1,186 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
// Test that header file is self-contained.
#include <beast/http/message_v1.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <beast/detail/unit_test/thread.hpp>
#include <beast/placeholders.hpp>
#include <beast/streambuf.hpp>
#include <beast/http.hpp>
#include <boost/asio.hpp>
namespace beast {
namespace http {
namespace test {
class sync_echo_http_server
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
private:
beast::detail::unit_test::suite& suite_;
boost::asio::io_service ios_;
socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_;
beast::detail::unit_test::thread thread_;
public:
sync_echo_http_server(
endpoint_type ep, beast::detail::unit_test::suite& suite)
: suite_(suite)
, sock_(ios_)
, acceptor_(ios_)
{
error_code ec;
acceptor_.open(ep.protocol(), ec);
maybe_throw(ec, "open");
acceptor_.bind(ep, ec);
maybe_throw(ec, "bind");
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
maybe_throw(ec, "listen");
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
thread_ = beast::detail::unit_test::thread(suite_,
[&]
{
ios_.run();
});
}
~sync_echo_http_server()
{
error_code ec;
ios_.dispatch(
[&]{ acceptor_.close(ec); });
thread_.join();
}
private:
void
fail(error_code ec, std::string what)
{
suite_.log <<
what << ": " << ec.message();
}
void
maybe_throw(error_code ec, std::string what)
{
if(ec &&
ec != boost::asio::error::operation_aborted)
{
fail(ec, what);
throw ec;
}
}
void
on_accept(error_code ec)
{
if(ec == boost::asio::error::operation_aborted)
return;
maybe_throw(ec, "accept");
std::thread{&sync_echo_http_server::do_client, this,
std::move(sock_), boost::asio::io_service::work{
sock_.get_io_service()}}.detach();
acceptor_.async_accept(sock_,
std::bind(&sync_echo_http_server::on_accept, this,
beast::asio::placeholders::error));
}
void
do_client(socket_type sock, boost::asio::io_service::work)
{
error_code ec;
streambuf rb;
for(;;)
{
request_v1<string_body> req;
read(sock, rb, req, ec);
if(ec)
break;
response_v1<string_body> resp(
{100, "OK", req.version});
resp.body = "Completed successfully.";
write(sock, resp, ec);
if(ec)
break;
}
}
};
class message_test : public beast::detail::unit_test::suite
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
void
syncEcho(endpoint_type ep)
{
boost::asio::io_service ios;
socket_type sock(ios);
sock.connect(ep);
streambuf rb;
{
request_v1<string_body> req({"GET", "/", 11});
req.body = "Beast.HTTP";
req.headers.replace("Host",
ep.address().to_string() + ":" +
std::to_string(ep.port()));
write(sock, req);
}
{
response_v1<string_body> m;
read(sock, rb, m);
}
}
void
testAsio()
{
endpoint_type ep{
address_type::from_string("127.0.0.1"), 6000};
sync_echo_http_server s(ep, *this);
syncEcho(ep);
}
void run() override
{
testAsio();
pass();
}
};
BEAST_DEFINE_TESTSUITE(message,http,beast);
} // test
} // http
} // beast
+4 -15
View File
@@ -10,7 +10,8 @@
#include "nodejs-parser/http_parser.h"
#include <beast/http/basic_parser.hpp>
#include <beast/http/error.hpp>
#include <beast/http/message_v1.hpp>
#include <beast/http/rfc2616.hpp>
#include <beast/type_check.hpp>
#include <boost/asio/buffer.hpp>
@@ -18,6 +19,7 @@
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
@@ -730,19 +732,6 @@ nodejs_basic_parser<Derived>::cb_chunk_complete(http_parser*)
return 0;
}
} // http
} // beast
#include <beast/http/error.hpp>
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
/** A HTTP parser.
The parser may only be used once.
@@ -752,7 +741,7 @@ class nodejs_parser
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Headers>>
{
using message_type =
message<isRequest, Body, Headers>;
message_v1<isRequest, Body, Headers>;
message_type m_;
typename message_type::body_type::reader r_;
+3 -3
View File
@@ -6,7 +6,7 @@
//
// Test that header file is self-contained.
#include <beast/http/parser.hpp>
#include <beast/http/parser_v1.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/string_body.hpp>
@@ -23,7 +23,7 @@ public:
using boost::asio::buffer;
{
error_code ec;
parser<true, string_body,
parser_v1<true, string_body,
basic_headers<std::allocator<char>>> p;
std::string const s =
"GET / HTTP/1.1\r\n"
@@ -43,7 +43,7 @@ public:
}
{
error_code ec;
parser<false, string_body,
parser_v1<false, string_body,
basic_headers<std::allocator<char>>> p;
std::string const s =
"HTTP/1.1 200 OK\r\n"
+6 -6
View File
@@ -97,7 +97,7 @@ public:
}
template<bool isRequest>
struct null_parser : basic_parser<isRequest, null_parser<isRequest>>
struct null_parser : basic_parser_v1<isRequest, null_parser<isRequest>>
{
};
@@ -108,10 +108,10 @@ public:
static std::size_t constexpr Repeat = 50;
log << "sizeof(request parser) == " <<
sizeof(basic_parser<true, null_parser<true>>);
sizeof(basic_parser_v1<true, null_parser<true>>);
log << "sizeof(response parser) == " <<
sizeof(basic_parser<false, null_parser<true>>);
sizeof(basic_parser_v1<false, null_parser<true>>);
testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " <<
@@ -127,13 +127,13 @@ public:
false, streambuf_body, headers>>(
Repeat, cres_);
});
timedTest(Trials, "http::basic_parser",
timedTest(Trials, "http::basic_parser_v1",
[&]
{
testParser<parser<
testParser<parser_v1<
true, streambuf_body, headers>>(
Repeat, creq_);
testParser<parser<
testParser<parser_v1<
false, streambuf_body, headers>>(
Repeat, cres_);
});
+10 -10
View File
@@ -89,7 +89,7 @@ public:
template<bool isRequest, class Body, class Headers>
std::string
str(message<isRequest, Body, Headers> const& m)
str(message_v1<isRequest, Body, Headers> const& m)
{
string_SyncStream ss;
write(ss, m);
@@ -101,7 +101,7 @@ public:
{
// auto content-length HTTP/1.0
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 10}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -116,7 +116,7 @@ public:
}
// keep-alive HTTP/1.0
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 10}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -132,7 +132,7 @@ public:
}
// upgrade HTTP/1.0
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 10}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -148,7 +148,7 @@ public:
}
// no content-length HTTP/1.0
{
message<true, test_Body, headers> m{{
message_v1<true, test_Body, headers> m{{
"GET", "/", 10}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -166,7 +166,7 @@ public:
}
// auto content-length HTTP/1.1
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 11}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -181,7 +181,7 @@ public:
}
// close HTTP/1.1
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 11}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -201,7 +201,7 @@ public:
}
// upgrade HTTP/1.1
{
message<true, empty_body, headers> m{{
message_v1<true, empty_body, headers> m{{
"GET", "/", 11}};
m.headers.insert("User-Agent", "test");
prepare(m, connection::upgrade);
@@ -214,7 +214,7 @@ public:
}
// no content-length HTTP/1.1
{
message<true, test_Body, headers> m{{
message_v1<true, test_Body, headers> m{{
"GET", "/", 11}};
m.headers.insert("User-Agent", "test");
m.body = "*";
@@ -236,7 +236,7 @@ public:
void testConvert()
{
message<true, string_body, headers> m{{
message_v1<true, string_body, headers> m{{
"GET", "/", 11}};
m.headers.insert("User-Agent", "test");
m.body = "*";
+1 -1
View File
@@ -22,7 +22,7 @@
#include <mutex>
#include <thread>
#include <beast/http/parser.hpp>
#include <beast/http/parser_v1.hpp>
namespace beast {
namespace websocket {