From c49cde844b3e3c93891b1a2a71be672162be1fd6 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 7 May 2016 15:18:22 -0400 Subject: [PATCH] New constructors for message: The message class now behaves like a pair with respect to the construction of the body and headers. Additional constructors allow construction of just the body portion from a tuple, leaving the headers default constructed. Previous constructors are removed as they were a notational convenience for assembling HTTP/1 requests and responses. They are not necessary as this library aims at library writers and not end users. --- README.md | 5 +- doc/beast.qbk | 5 +- examples/http_async_server.hpp | 12 +- examples/http_crawl.cpp | 5 +- examples/http_example.cpp | 5 +- examples/http_sync_server.hpp | 12 +- include/beast/http/impl/message_v1.ipp | 22 ---- include/beast/http/message.hpp | 76 ++++++++++++ include/beast/http/message_v1.hpp | 33 ++--- include/beast/websocket/impl/stream.ipp | 54 +++++---- test/http/message.cpp | 154 +++++++++++++++++++++--- test/http/message_v1.cpp | 41 +++---- test/http/write.cpp | 54 ++++++--- 13 files changed, 334 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 550e617e..f5eaaeaa 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,10 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request_v1 req({"GET", "/", 11}); + beast::http::request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); req.headers.replace("User-Agent", "Beast"); beast::http::prepare(req); diff --git a/doc/beast.qbk b/doc/beast.qbk index 90941935..c32b50f6 100644 --- a/doc/beast.qbk +++ b/doc/beast.qbk @@ -113,7 +113,10 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request_v1 req({"GET", "/", 11}); + beast::http::request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); req.headers.replace("User-Agent", "Beast"); beast::http::prepare(req); diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp index 2a6108f2..9abeae9a 100644 --- a/examples/http_async_server.hpp +++ b/examples/http_async_server.hpp @@ -127,8 +127,10 @@ private: path = root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp( - {404, "Not Found", req_.version}); + response_v1 resp; + resp.status = 404; + resp.reason = "Not Found"; + resp.version = req_.version; resp.headers.replace("Server", "http_async_server"); resp.body = "The file '" + path + "' was not found"; prepare(resp); @@ -137,8 +139,10 @@ private: asio::placeholders::error)); return; } - resp_type resp( - {200, "OK", req_.version}); + resp_type resp; + resp.status = 200; + resp.reason = "OK"; + resp.version = req_.version; resp.headers.replace("Server", "http_async_server"); resp.headers.replace("Content-Type", "text/html"); resp.body = path; diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp index 5eae1627..2eb46aad 100644 --- a/examples/http_crawl.cpp +++ b/examples/http_crawl.cpp @@ -46,7 +46,10 @@ int main(int, char const*[]) stream hs(ios); connect(hs.lowest_layer(), it); auto ep = hs.lowest_layer().remote_endpoint(); - request_v1 req({"GET", "/", 11}); + request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; req.headers.insert("Host", host + std::string(":") + std::to_string(ep.port())); req.headers.insert("User-Agent", "beast/http"); diff --git a/examples/http_example.cpp b/examples/http_example.cpp index 7186ef4d..a7869c6c 100644 --- a/examples/http_example.cpp +++ b/examples/http_example.cpp @@ -21,7 +21,10 @@ int main() r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"})); // Send HTTP request using beast - beast::http::request_v1 req({"GET", "/", 11}); + beast::http::request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); req.headers.replace("User-Agent", "Beast"); beast::http::prepare(req); diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp index dbfdc52e..d07c0d0c 100644 --- a/examples/http_sync_server.hpp +++ b/examples/http_sync_server.hpp @@ -155,8 +155,10 @@ public: path = root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp( - {404, "Not Found", req.version}); + response_v1 resp; + resp.status = 404; + resp.reason = "Not Found"; + resp.version = req.version; resp.headers.replace("Server", "http_sync_server"); resp.body = "The file '" + path + "' was not found"; prepare(resp); @@ -164,8 +166,10 @@ public: if(ec) break; } - resp_type resp( - {200, "OK", req.version}); + resp_type resp; + resp.status = 200; + resp.reason = "OK"; + resp.version = req.version; resp.headers.replace("Server", "http_sync_server"); resp.headers.replace("Content-Type", "text/html"); resp.body = path; diff --git a/include/beast/http/impl/message_v1.ipp b/include/beast/http/impl/message_v1.ipp index 805eddd1..11f36414 100644 --- a/include/beast/http/impl/message_v1.ipp +++ b/include/beast/http/impl/message_v1.ipp @@ -16,28 +16,6 @@ namespace beast { namespace http { -template -message_v1:: -message_v1(request_params params) -{ - static_assert(isRequest, "message is not a request"); - this->method = params.method; - this->url = std::move(params.url); - version = params.version; -} - -template -message_v1:: -message_v1(response_params params) -{ - static_assert(! isRequest, "message is not a response"); - this->status = params.status; - this->reason = std::move(params.reason); - version = params.version; -} - -//------------------------------------------------------------------------------ - template bool is_keep_alive(message_v1 const& msg) diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 1f417d95..6b06816a 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -9,8 +9,11 @@ #define BEAST_HTTP_MESSAGE_HPP #include +#include #include #include +#include +#include namespace beast { namespace http { @@ -69,6 +72,79 @@ struct message /// A container representing the body. typename Body::value_type body; + + /// Default constructor + message() = default; + + /** Construct a message. + + @param u An argument forwarded to the body constructor. + */ + template + explicit + message(U&& u) + : body(std::forward(u)) + { + } + + /** Construct a message. + + @param u An argument forwarded to the body constructor. + @param v An argument forwarded to the headers constructor. + */ + template + explicit + message(U&& u, V&& v) + : headers(std::forward(v)) + , body(std::forward(u)) + { + } + + /** Construct a message. + + @param un A tuple forwarded as a parameter pack to the body constructor. + */ + template + message(std::piecewise_construct_t, std::tuple un) + : message(std::piecewise_construct, un, + beast::detail::make_index_sequence{}) + { + + } + + /** Construct a message. + + @param un A tuple forwarded as a parameter pack to the body constructor. + @param vn A tuple forwarded as a parameter pack to the headers constructor. + */ + template + explicit + message(std::piecewise_construct_t, + std::tuple&& un, std::tuple&& vn) + : message(std::piecewise_construct, un, vn, + beast::detail::make_index_sequence{}, + beast::detail::make_index_sequence{}) + { + } + +private: + template + message(std::piecewise_construct_t, + std::tuple& tu, beast::detail::index_sequence) + : body(std::forward(std::get(tu))...) + { + } + + template + message(std::piecewise_construct_t, + std::tuple& tu, std::tuple& tv, + beast::detail::index_sequence, + beast::detail::index_sequence) + : headers(std::forward(std::get(tv))...) + , body(std::forward(std::get(tu))...) + { + } }; #if ! GENERATING_DOCS diff --git a/include/beast/http/message_v1.hpp b/include/beast/http/message_v1.hpp index b5b4fe17..cb25ac2f 100644 --- a/include/beast/http/message_v1.hpp +++ b/include/beast/http/message_v1.hpp @@ -15,24 +15,6 @@ namespace beast { namespace http { -#if ! GENERATING_DOCS - -struct request_params -{ - std::string method; - std::string url; - int version; -}; - -struct response_params -{ - int status; - std::string reason; - int version; -}; - -#endif - /** A HTTP/1 message. A message can be a request or response, depending on the `isRequest` @@ -54,15 +36,18 @@ struct message_v1 : message /// HTTP/1 version (10 or 11) int version; + /// Default constructor message_v1() = default; - /// Construct a HTTP/1 request. + /// Constructor + template explicit - message_v1(request_params params); - - /// Construct a HTTP/1 response. - explicit - message_v1(response_params params); + message_v1(Arg1& arg1, Argn&&... argn) + : message( + std::forward(arg1), + std::forward(argn)...) + { + } }; #if ! GENERATING_DOCS diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index aa154fc3..7b733033 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -291,9 +291,9 @@ accept(http::request_v1 const& req, { static_assert(is_SyncStream::value, "SyncStream requirements not met"); - auto const resp = build_response(req); - http::write(stream_, resp, ec); - if(resp.status != 101) + auto const res = build_response(req); + http::write(stream_, res, ec); + if(res.status != 101) { ec = error::handshake_failed; // VFALCO TODO Respect keep alive setting, perform @@ -349,11 +349,11 @@ handshake(boost::string_ref const& host, build_request(host, resource, key), ec); if(ec) return; - http::response_v1 resp; - http::read(next_layer(), stream_.buffer(), resp, ec); + http::response_v1 res; + http::read(next_layer(), stream_.buffer(), res, ec); if(ec) return; - do_response(resp, key, ec); + do_response(res, key, ec); } template @@ -855,11 +855,13 @@ build_response(http::request_v1 const& req) auto err = [&](std::string const& text) { - http::response_v1 resp( - {400, http::reason_string(400), req.version}); - resp.body = text; + http::response_v1 res; + res.status = 400; + res.reason = http::reason_string(res.status); + res.version = req.version; + res.body = text; // VFALCO TODO respect keep-alive here - return resp; + return res; }; if(req.version < 11) return err("HTTP version 1.1 required"); @@ -882,41 +884,43 @@ build_response(http::request_v1 const& req) if(! rfc2616::token_in_list( req.headers["Upgrade"], "websocket")) return err("Missing websocket Upgrade token"); - http::response_v1 resp( - {101, http::reason_string(101), req.version}); - resp.headers.insert("Upgrade", "websocket"); + http::response_v1 res; + res.status = 101; + res.reason = http::reason_string(res.status); + res.version = req.version; + res.headers.insert("Upgrade", "websocket"); { auto const key = req.headers["Sec-WebSocket-Key"]; - resp.headers.insert("Sec-WebSocket-Key", key); - resp.headers.insert("Sec-WebSocket-Accept", + res.headers.insert("Sec-WebSocket-Key", key); + res.headers.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } - resp.headers.replace("Server", "Beast.WSProto"); - (*d_)(resp); - http::prepare(resp, http::connection::upgrade); - return resp; + res.headers.replace("Server", "Beast.WSProto"); + (*d_)(res); + http::prepare(res, http::connection::upgrade); + return res; } template template void stream:: -do_response(http::response_v1 const& resp, +do_response(http::response_v1 const& res, boost::string_ref const& key, error_code& ec) { // VFALCO Review these error codes auto fail = [&]{ ec = error::response_failed; }; - if(resp.status != 101) + if(res.status != 101) return fail(); - if(! is_upgrade(resp)) + if(! is_upgrade(res)) return fail(); if(! rfc2616::ci_equal( - resp.headers["Upgrade"], "websocket")) + res.headers["Upgrade"], "websocket")) return fail(); - if(! resp.headers.exists("Sec-WebSocket-Accept")) + if(! res.headers.exists("Sec-WebSocket-Accept")) return fail(); - if(resp.headers["Sec-WebSocket-Accept"] != + if(res.headers["Sec-WebSocket-Accept"] != detail::make_sec_ws_accept(key)) return fail(); role_ = role_type::client; diff --git a/test/http/message.cpp b/test/http/message.cpp index c9ebd0de..9152bfa7 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -1,21 +1,139 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - 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. -*/ -//============================================================================== +// +// Copyright (c) 2013-2016 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) +// // Test that header file is self-contained. #include + +#include +#include +#include + +namespace beast { +namespace http { + +class message_test : public beast::unit_test::suite +{ +public: + struct Arg1 + { + bool moved = false; + + Arg1() = default; + + Arg1(Arg1&& other) + { + other.moved = true; + } + }; + + struct Arg2 { }; + struct Arg3 { }; + + // default constructible Body + struct default_body + { + using value_type = std::string; + }; + + // 1-arg constructible Body + struct one_arg_body + { + struct value_type + { + explicit + value_type(Arg1 const&) + { + } + + explicit + value_type(Arg1&& arg) + { + Arg1 arg_(std::move(arg)); + } + }; + }; + + // 2-arg constructible Body + struct two_arg_body + { + struct value_type + { + value_type(Arg1 const&, Arg2 const&) + { + } + }; + }; + + void testConstruction() + { + static_assert(std::is_constructible< + message>::value, ""); + + static_assert(std::is_constructible< + message, Arg1>::value, ""); + + static_assert(std::is_constructible< + message, Arg1 const>::value, ""); + + static_assert(std::is_constructible< + message, Arg1 const&>::value, ""); + + static_assert(std::is_constructible< + message, Arg1&&>::value, ""); + + static_assert(! std::is_constructible< + message>::value, ""); + + static_assert(std::is_constructible< + message, + Arg1, headers::allocator_type>::value, ""); + + static_assert(std::is_constructible< + message, std::piecewise_construct_t, + std::tuple>::value, ""); + + static_assert(std::is_constructible< + message, std::piecewise_construct_t, + std::tuple>::value, ""); + + static_assert(std::is_constructible< + message, std::piecewise_construct_t, + std::tuple, std::tuple>::value, ""); + + { + Arg1 arg1; + message{std::move(arg1)}; + expect(arg1.moved); + } + + { + headers h; + h.insert("User-Agent", "test"); + message m{Arg1{}, h}; + expect(h["User-Agent"] == "test"); + expect(m.headers["User-Agent"] == "test"); + } + { + headers h; + h.insert("User-Agent", "test"); + message m{Arg1{}, std::move(h)}; + expect(! h.exists("User-Agent")); + expect(m.headers["User-Agent"] == "test"); + } + } + + void run() override + { + testConstruction(); + } +}; + +BEAST_DEFINE_TESTSUITE(message,http,beast); + +} // http +} // beast + diff --git a/test/http/message_v1.cpp b/test/http/message_v1.cpp index a1aa6659..f9f922f4 100644 --- a/test/http/message_v1.cpp +++ b/test/http/message_v1.cpp @@ -1,21 +1,9 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - 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. -*/ -//============================================================================== +// +// Copyright (c) 2013-2016 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) +// // Test that header file is self-contained. #include @@ -29,7 +17,6 @@ namespace beast { namespace http { -namespace test { class sync_echo_http_server { @@ -122,8 +109,10 @@ private: read(sock, rb, req, ec); if(ec) break; - response_v1 resp( - {100, "OK", req.version}); + response_v1 resp; + resp.status = 100; + resp.reason = "OK"; + resp.version = req.version; resp.body = "Completed successfully."; write(sock, resp, ec); if(ec) @@ -132,7 +121,7 @@ private: } }; -class message_test : public beast::unit_test::suite +class message_v1_test : public beast::unit_test::suite { public: using endpoint_type = boost::asio::ip::tcp::endpoint; @@ -184,7 +173,10 @@ public: streambuf rb; { - request_v1 req({"GET", "/", 11}); + request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; req.body = "Beast.HTTP"; req.headers.replace("Host", ep.address().to_string() + ":" + @@ -214,8 +206,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(message,http,beast); +BEAST_DEFINE_TESTSUITE(message_v1,http,beast); -} // test } // http } // beast diff --git a/test/http/write.cpp b/test/http/write.cpp index 3ad8a597..00a47f1b 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -135,8 +135,10 @@ public: { // auto content-length HTTP/1.0 { - message_v1 m{{ - "GET", "/", 10}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 10; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m); @@ -150,8 +152,10 @@ public: } // keep-alive HTTP/1.0 { - message_v1 m{{ - "GET", "/", 10}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 10; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m, connection::keep_alive); @@ -166,8 +170,10 @@ public: } // upgrade HTTP/1.0 { - message_v1 m{{ - "GET", "/", 10}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 10; m.headers.insert("User-Agent", "test"); m.body = "*"; try @@ -182,8 +188,10 @@ public: } // no content-length HTTP/1.0 { - message_v1 m{{ - "GET", "/", 10}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 10; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m); @@ -200,8 +208,10 @@ public: } // auto content-length HTTP/1.1 { - message_v1 m{{ - "GET", "/", 11}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 11; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m); @@ -215,8 +225,10 @@ public: } // close HTTP/1.1 { - message_v1 m{{ - "GET", "/", 11}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 11; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m, connection::close); @@ -235,8 +247,10 @@ public: } // upgrade HTTP/1.1 { - message_v1 m{{ - "GET", "/", 11}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 11; m.headers.insert("User-Agent", "test"); prepare(m, connection::upgrade); expect(str(m) == @@ -248,8 +262,10 @@ public: } // no content-length HTTP/1.1 { - message_v1 m{{ - "GET", "/", 11}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 11; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m); @@ -270,8 +286,10 @@ public: void testConvert() { - message_v1 m{{ - "GET", "/", 11}}; + message_v1 m; + m.method = "GET"; + m.url = "/"; + m.version = 11; m.headers.insert("User-Agent", "test"); m.body = "*"; prepare(m);