From a0b04bdff27ead48d5c43d6e83ec05eb3e9397db Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 29 Apr 2016 06:04:40 -0400 Subject: [PATCH] Refactor beast core, http, tests, and examples: * Fix warnings * Port cmake scripts to linux * Add command line options for running test suites * Add examples to CMakeLists * Return std::uint64_t from writer::content_length * basic_parser::write takes asio::const_buffer instead of pointer and size * Turn message test back on now that it passes * Rename to http::headers, use std::allocator, remove http_headers * http::message::method is now a string * Refactor to_string for ConstBufferSequence * Remove chunk_encode from the public interface * Initialize members for default constructed iterators * Disallow default construction for dependent buffer sequences Refactor http::message serialization: * Serialization no longer creates a copy of the headers and modifies them * New function prepare(), sets Connection, Transfer-Encoding, Content-Length based on the body attributes and caller options. Callers can use prepare() to have the fields set automatically, or they can set the fields manually. * Use write for operator<< * Tests for serialization --- CMakeLists.txt | 15 +- TODO.txt | 18 +- doc/beast.dox | 2 +- doc/beast.qbk | 8 +- doc/quickref.xml | 3 +- doc/types.qbk | 2 +- examples/CMakeLists.txt | 32 +- examples/{file_body.h => file_body.hpp} | 12 +- ...p_async_server.h => http_async_server.hpp} | 6 +- examples/http_crawl.cpp | 7 +- examples/http_example.cpp | 3 +- examples/http_server.cpp | 6 +- examples/{http_stream.h => http_stream.hpp} | 0 ...ttp_sync_server.h => http_sync_server.hpp} | 6 +- examples/{sig_wait.h => sig_wait.hpp} | 0 examples/urls_large_data.cpp | 2 +- ...{urls_large_data.h => urls_large_data.hpp} | 0 ...o_peer.h => websocket_async_echo_peer.hpp} | 0 examples/websocket_echo.cpp | 6 +- examples/websocket_example.cpp | 5 +- ...ho_peer.h => websocket_sync_echo_peer.hpp} | 0 include/beast/buffers_debug.hpp | 44 --- include/beast/consuming_buffers.hpp | 22 +- include/beast/detail/write_streambuf.hpp | 15 +- include/beast/http.hpp | 1 - include/beast/http/basic_parser.hpp | 19 +- .../beast/http/{ => detail}/chunk_encode.hpp | 10 +- .../beast/http/detail/write_preparation.hpp | 56 +--- include/beast/http/empty_body.hpp | 2 +- include/beast/http/headers.hpp | 5 +- include/beast/http/impl/basic_parser.ipp | 46 +-- include/beast/http/impl/message.ipp | 292 +++++++++++------- include/beast/http/impl/write.ipp | 80 ++++- include/beast/http/message.hpp | 42 ++- include/beast/http/method.hpp | 179 ----------- include/beast/http/parser.hpp | 86 +----- include/beast/http/status.hpp | 71 +++++ include/beast/http/streambuf_body.hpp | 2 +- include/beast/http/string_body.hpp | 2 +- include/beast/http/type_check.hpp | 62 ++++ include/beast/http/write.hpp | 16 + include/beast/impl/basic_streambuf.ipp | 8 +- include/beast/impl/buffers_adapter.ipp | 8 +- include/beast/impl/consuming_buffers.ipp | 7 +- include/beast/impl/prepare_buffers.ipp | 2 +- include/beast/impl/static_streambuf.ipp | 12 +- include/beast/to_string.hpp | 51 +++ include/beast/websocket/detail/endian.hpp | 11 + include/beast/websocket/detail/frame.hpp | 4 +- include/beast/websocket/impl/close_op.ipp | 1 - .../beast/websocket/impl/read_frame_op.ipp | 12 +- include/beast/websocket/impl/stream.ipp | 17 +- include/beast/write_streambuf.hpp | 4 +- test/CMakeLists.txt | 46 +-- test/Jamfile | 5 +- test/basic_streambuf.cpp | 77 +++-- test/buffers_adapter.cpp | 76 +++-- test/http/basic_parser.cpp | 33 +- test/http/chunk_encode.cpp | 154 --------- test/http/message.cpp | 6 +- test/http/nodejs_parser.hpp | 96 +++--- test/http/parser.cpp | 7 +- test/http/parser_bench.cpp | 13 +- test/http/{method.cpp => status.cpp} | 2 +- test/http/type_check.cpp | 9 + test/http/write.cpp | 249 +++++++++++++++ test/main.cpp | 94 +++++- test/static_streambuf.cpp | 77 +++-- test/{buffers_debug.cpp => to_string.cpp} | 2 +- 69 files changed, 1315 insertions(+), 953 deletions(-) rename examples/{file_body.h => file_body.hpp} (90%) rename examples/{http_async_server.h => http_async_server.hpp} (98%) rename examples/{http_stream.h => http_stream.hpp} (100%) rename examples/{http_sync_server.h => http_sync_server.hpp} (97%) rename examples/{sig_wait.h => sig_wait.hpp} (100%) rename examples/{urls_large_data.h => urls_large_data.hpp} (100%) rename examples/{websocket_async_echo_peer.h => websocket_async_echo_peer.hpp} (100%) rename examples/{websocket_sync_echo_peer.h => websocket_sync_echo_peer.hpp} (100%) delete mode 100644 include/beast/buffers_debug.hpp rename include/beast/http/{ => detail}/chunk_encode.hpp (98%) delete mode 100644 include/beast/http/method.hpp create mode 100644 include/beast/http/status.hpp create mode 100644 include/beast/http/type_check.hpp create mode 100644 include/beast/to_string.hpp delete mode 100644 test/http/chunk_encode.cpp rename test/http/{method.cpp => status.cpp} (89%) create mode 100644 test/http/type_check.cpp rename test/{buffers_debug.cpp => to_string.cpp} (88%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1600df9..463b1929 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,25 @@ # Part of Beast -cmake_minimum_required (VERSION 3.5) +cmake_minimum_required (VERSION 3.2) project (Beast) set_property (GLOBAL PROPERTY USE_FOLDERS ON) if (WIN32) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /wd4100 /D_SCL_SECURE_NO_WARNINGS=1 /D_CRT_SECURE_NO_WARNINGS=1") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /W4 /wd4100 /D_SCL_SECURE_NO_WARNINGS=1 /D_CRT_SECURE_NO_WARNINGS=1") else() + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_MULTITHREADED ON) + find_package(Boost REQUIRED COMPONENTS filesystem program_options system) + include_directories(${Boost_INCLUDE_DIRS}) + link_directories(${Boost_LIBRARY_DIR}) + + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads) + + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wall /Wextra /Wpedantic /Wconversion -Wno-unused-variable") endif() message ("cxx Flags: " ${CMAKE_CXX_FLAGS}) diff --git a/TODO.txt b/TODO.txt index 2de8add5..32600262 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,24 +1,15 @@ -* Change build options to C++11 only * Replace Jamroot with Jamfile -* Fix failing test/message.cpp * Complete allocator testing in basic_streambuf, basic_headers * Tidy up type_checks - Derive from std::integral_constant * Check DOXYGEN, GENERATIC_DOCS directives in source - See if we can include them now that xsl is fixed -* Go over each header and split header material into detail and impl files * Make buffers_debug a detail -* Roll header-only http parser * Define Parser concept in HTTP * melpon sandbox? * invokable unit test * trim public interface of rfc2616.h to essentials only -* Use new http routines in JSONRPCClient -* Remove or change http::headers alias -* Do something about the methods.hpp and fields.hpp type headers * Fix index in docs -* Fix integer warning in file_body.hpp -* Use make_error_code in websocket to set the category right * Figure out why namespace rfc2616 is included in docs (currently disabled via GENERATING_DOCS macro) * Include Example program listings in the docs @@ -26,7 +17,12 @@ * HTTP parser size limit with test (configurable?) * HTTP parser trailers with test * URL parser, strong URL checking in HTTP parser -* Fix method, use string instead of enum * More fine grained parser errors -* Fix all the warnings in all projects/build configs * Fix bidirectional buffers iterators operator->() +* http type_check, e.g. is_WritableBody +* add bool should_close(message_v1 const&) to replace the use + of eof return value from write and async_write + +Boost.Http +* Use enum instead of bool in isRequest +* move version to a subclass of message diff --git a/doc/beast.dox b/doc/beast.dox index 007fc36e..976ff2c1 100644 --- a/doc/beast.dox +++ b/doc/beast.dox @@ -108,7 +108,6 @@ INPUT = \ ../include/beast/bind_handler.hpp \ ../include/beast/buffer_cat.hpp \ ../include/beast/buffers_adapter.hpp \ - ../include/beast/buffers_debug.hpp \ ../include/beast/consuming_buffers.hpp \ ../include/beast/handler_alloc.hpp \ ../include/beast/http.hpp \ @@ -117,6 +116,7 @@ INPUT = \ ../include/beast/static_streambuf.hpp \ ../include/beast/streambuf.hpp \ ../include/beast/streambuf_readstream.hpp \ + ../include/beast/to_string.hpp \ ../include/beast/type_check.hpp \ ../include/beast/websocket.hpp \ ../include/beast/write_streambuf.hpp \ diff --git a/doc/beast.qbk b/doc/beast.qbk index 47a26c6d..d64a4024 100644 --- a/doc/beast.qbk +++ b/doc/beast.qbk @@ -115,9 +115,10 @@ int main() using namespace beast::http; // Send HTTP request using beast - request req({method_t::http_get, "/", 11}); + request req({"GET", "/", 11}); req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); req.headers.replace("User-Agent", "Beast"); + prepare(req); write(sock, req); // Receive and print HTTP response using beast @@ -130,8 +131,8 @@ int main() Establish a WebSocket connection, send a message and receive the reply: ``` +#include #include -#include #include #include #include @@ -158,8 +159,7 @@ int main() opcode op; ws.read(op, sb); ws.close(close_code::normal); - std::cout << - beast::debug::buffers_to_string(sb.data()) << "\n"; + std::cout << to_string(sb.data()) << "\n"; } ``` diff --git a/doc/quickref.xml b/doc/quickref.xml index 460e6d3c..849864eb 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -34,6 +34,7 @@ basic_streambuf_body empty_body error_code + headers message resume_context streambuf_body @@ -49,8 +50,6 @@ async_read async_write - chunk_encode - chunk_encode_final read write diff --git a/doc/types.qbk b/doc/types.qbk index 535a4b49..d55d7c51 100644 --- a/doc/types.qbk +++ b/doc/types.qbk @@ -252,7 +252,7 @@ In this table: ] [ [`a.content_length()`] - [`std::size_t`] + [`std::uint64_t`] [ If this member is present, it is called after initialization and before calls to provide buffers. The serialized message will diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5b193b44..86cc4af9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -5,26 +5,56 @@ GroupSources(examples) add_executable (http-crawl ${BEAST_INCLUDES} - http_crawl.cpp + urls_large_data.hpp urls_large_data.cpp + http_crawl.cpp ) +if (NOT WIN32) + target_link_libraries(http-crawl ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endif() + add_executable (http-server ${BEAST_INCLUDES} + file_body.hpp + http_async_server.hpp + http_stream.hpp + http_stream.ipp + http_sync_server.hpp + sig_wait.hpp http_server.cpp ) +if (NOT WIN32) + target_link_libraries(http-server ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endif() + add_executable (http-example ${BEAST_INCLUDES} http_example.cpp ) +if (NOT WIN32) + target_link_libraries(http-example ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endif() + add_executable (websocket-echo ${BEAST_INCLUDES} + sig_wait.hpp + websocket_async_echo_peer.hpp + websocket_sync_echo_peer.hpp websocket_echo.cpp ) +if (NOT WIN32) + target_link_libraries(websocket-echo ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endif() + add_executable (websocket-example ${BEAST_INCLUDES} websocket_example.cpp ) + +if (NOT WIN32) + target_link_libraries(websocket-example ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endif() diff --git a/examples/file_body.h b/examples/file_body.hpp similarity index 90% rename from examples/file_body.h rename to examples/file_body.hpp index d6719d5a..0a3eed17 100644 --- a/examples/file_body.h +++ b/examples/file_body.hpp @@ -36,8 +36,8 @@ struct file_body class writer { - std::size_t size_; - std::size_t offset_ = 0; + std::uint64_t size_; + std::uint64_t offset_ = 0; std::string const& path_; FILE* file_ = nullptr; char buf_[4096]; @@ -69,7 +69,7 @@ struct file_body size_ = boost::filesystem::file_size(path_); } - std::size_t + std::uint64_t content_length() const { return size_; @@ -79,7 +79,11 @@ struct file_body boost::tribool operator()(resume_context&&, error_code&, Write&& write) { - buf_len_ = std::min(size_ - offset_, sizeof(buf_)); + if(size_ - offset_ < sizeof(buf_)) + buf_len_ = static_cast( + size_ - offset_); + else + buf_len_ = sizeof(buf_); auto const nread = fread(buf_, 1, sizeof(buf_), file_); (void)nread; offset_ += buf_len_; diff --git a/examples/http_async_server.h b/examples/http_async_server.hpp similarity index 98% rename from examples/http_async_server.h rename to examples/http_async_server.hpp index 5438ab6e..b409a1eb 100644 --- a/examples/http_async_server.h +++ b/examples/http_async_server.hpp @@ -20,8 +20,8 @@ #ifndef BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED #define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED -#include "file_body.h" -#include "http_stream.h" +#include "file_body.hpp" +#include "http_stream.hpp" #include #include @@ -131,6 +131,7 @@ private: {404, "Not Found", req_.version}); resp.headers.replace("Server", "http_async_server"); resp.body = "The file '" + path + "' was not found"; + prepare(resp); stream_.async_write(std::move(resp), std::bind(&peer::on_write, shared_from_this(), asio::placeholders::error)); @@ -141,6 +142,7 @@ private: resp.headers.replace("Server", "http_async_server"); resp.headers.replace("Content-Type", "text/html"); resp.body = path; + prepare(resp); stream_.async_write(std::move(resp), std::bind(&peer::on_write, shared_from_this(), asio::placeholders::error)); diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp index dd4779a1..876b9af7 100644 --- a/examples/http_crawl.cpp +++ b/examples/http_crawl.cpp @@ -17,8 +17,8 @@ */ //============================================================================== -#include "http_stream.h" -#include "urls_large_data.h" +#include "http_stream.hpp" +#include "urls_large_data.hpp" #include #include @@ -46,10 +46,11 @@ int main(int, char const*[]) stream hs(ios); connect(hs.lowest_layer(), it); auto ep = hs.lowest_layer().remote_endpoint(); - request req({method_t::http_get, "/", 11}); + request req({"GET", "/", 11}); req.headers.insert("Host", host + std::string(":") + std::to_string(ep.port())); req.headers.insert("User-Agent", "beast/http"); + prepare(req); hs.write(req); response resp; hs.read(resp); diff --git a/examples/http_example.cpp b/examples/http_example.cpp index b7fd3929..3e4fb2c9 100644 --- a/examples/http_example.cpp +++ b/examples/http_example.cpp @@ -23,9 +23,10 @@ int main() using namespace beast::http; // Send HTTP request using beast - request req({method_t::http_get, "/", 11}); + request req({"GET", "/", 11}); req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); req.headers.replace("User-Agent", "Beast"); + prepare(req); write(sock, req); // Receive and print HTTP response using beast diff --git a/examples/http_server.cpp b/examples/http_server.cpp index 8e310239..ddc16a71 100644 --- a/examples/http_server.cpp +++ b/examples/http_server.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include "http_async_server.h" -#include "http_sync_server.h" -#include "sig_wait.h" +#include "http_async_server.hpp" +#include "http_sync_server.hpp" +#include "sig_wait.hpp" #include diff --git a/examples/http_stream.h b/examples/http_stream.hpp similarity index 100% rename from examples/http_stream.h rename to examples/http_stream.hpp diff --git a/examples/http_sync_server.h b/examples/http_sync_server.hpp similarity index 97% rename from examples/http_sync_server.h rename to examples/http_sync_server.hpp index df6e4239..6343e36e 100644 --- a/examples/http_sync_server.h +++ b/examples/http_sync_server.hpp @@ -20,8 +20,8 @@ #ifndef BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED #define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED -#include "file_body.h" -#include "http_stream.h" +#include "file_body.hpp" +#include "http_stream.hpp" #include #include @@ -159,6 +159,7 @@ public: {404, "Not Found", req.version}); resp.headers.replace("Server", "http_sync_server"); resp.body = "The file '" + path + "' was not found"; + prepare(resp); hs.write(resp, ec); if(ec) break; @@ -168,6 +169,7 @@ public: resp.headers.replace("Server", "http_sync_server"); resp.headers.replace("Content-Type", "text/html"); resp.body = path; + prepare(resp); hs.write(resp, ec); if(ec) break; diff --git a/examples/sig_wait.h b/examples/sig_wait.hpp similarity index 100% rename from examples/sig_wait.h rename to examples/sig_wait.hpp diff --git a/examples/urls_large_data.cpp b/examples/urls_large_data.cpp index 782d0ca2..885754a7 100644 --- a/examples/urls_large_data.cpp +++ b/examples/urls_large_data.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include "urls_large_data.h" +#include "urls_large_data.hpp" // Data from Alexa top 1 million sites // http://s3.amazonaws.com/alexa-static/top-1m.csv.zip diff --git a/examples/urls_large_data.h b/examples/urls_large_data.hpp similarity index 100% rename from examples/urls_large_data.h rename to examples/urls_large_data.hpp diff --git a/examples/websocket_async_echo_peer.h b/examples/websocket_async_echo_peer.hpp similarity index 100% rename from examples/websocket_async_echo_peer.h rename to examples/websocket_async_echo_peer.hpp diff --git a/examples/websocket_echo.cpp b/examples/websocket_echo.cpp index deb1ef8b..6b65c4dd 100644 --- a/examples/websocket_echo.cpp +++ b/examples/websocket_echo.cpp @@ -17,9 +17,9 @@ */ //============================================================================== -#include "websocket_async_echo_peer.h" -#include "websocket_sync_echo_peer.h" -#include "sig_wait.h" +#include "websocket_async_echo_peer.hpp" +#include "websocket_sync_echo_peer.hpp" +#include "sig_wait.hpp" int main() { diff --git a/examples/websocket_example.cpp b/examples/websocket_example.cpp index 59f75a1c..f07b1e2f 100644 --- a/examples/websocket_example.cpp +++ b/examples/websocket_example.cpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include #include -#include #include #include #include @@ -33,6 +33,5 @@ int main() opcode op; ws.read(op, sb); ws.close(close_code::normal); - std::cout << - beast::debug::buffers_to_string(sb.data()) << "\n"; + std::cout << to_string(sb.data()) << "\n"; } diff --git a/examples/websocket_sync_echo_peer.h b/examples/websocket_sync_echo_peer.hpp similarity index 100% rename from examples/websocket_sync_echo_peer.h rename to examples/websocket_sync_echo_peer.hpp diff --git a/include/beast/buffers_debug.hpp b/include/beast/buffers_debug.hpp deleted file mode 100644 index 61566863..00000000 --- a/include/beast/buffers_debug.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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) -// - -#ifndef BEAST_BUFFERS_DEBUG_HPP -#define BEAST_BUFFERS_DEBUG_HPP - -#include -#include - -namespace beast { -namespace debug { - -/** Diagnostic utility to convert a `ConstBufferSequence` to a string. - - @note Carriage returns and linefeeds will have additional escape - representations printed for visibility. -*/ -template -std::string -buffers_to_string(Buffers const& bs) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(auto const& b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - for(auto i = s.size(); i-- > 0;) - if(s[i] == '\r') - s.replace(i, 1, "\\r"); - else if(s[i] == '\n') - s.replace(i, 1, "\\n\n"); - return s; -} - -} // debug -} // beast - -#endif diff --git a/include/beast/consuming_buffers.hpp b/include/beast/consuming_buffers.hpp index 752c3dbe..1c158606 100644 --- a/include/beast/consuming_buffers.hpp +++ b/include/beast/consuming_buffers.hpp @@ -110,10 +110,24 @@ public: consume(std::size_t n); }; -/// Returns a consumed buffer -template -consuming_buffers -consumed_buffers(Buffers const& bs, std::size_t n); +/** Returns a new, consumed buffer sequence. + + This function returns a new buffer sequence which when iterated, + efficiently represents the portion of the original buffer sequence + with `n` bytes removed from the beginning. + + Copies will be made of the buffer sequence passed, but ownership + of the underlying memory is not transferred. + + @param buffers The buffer sequence to consume. + + @param n The number of bytes to remove from the front. If this is + larger than the size of the buffer sequence, an empty buffer sequence + is returned. +*/ +template +consuming_buffers +consumed_buffers(BufferSequence const& buffers, std::size_t n); } // beast diff --git a/include/beast/detail/write_streambuf.hpp b/include/beast/detail/write_streambuf.hpp index ccac4b4c..710ebb06 100644 --- a/include/beast/detail/write_streambuf.hpp +++ b/include/beast/detail/write_streambuf.hpp @@ -41,13 +41,6 @@ public: ! is_string_literal::value; }; -template -inline -void -write_streambuf(Streambuf&) -{ -} - template void write_streambuf(Streambuf& streambuf, @@ -133,11 +126,11 @@ write_streambuf(Streambuf& streambuf, T const& t) template void -write_streambuf(Streambuf& streambuf, T0&& t0, T1&& t1, TN... tn) +write_streambuf(Streambuf& streambuf, + T0 const& t0, T1 const& t1, TN const&... tn) { - write_streambuf(streambuf, std::forward(t0)); - write_streambuf(streambuf, std::forward(t1)); - write_streambuf(streambuf, std::forward(tn)...); + write_streambuf(streambuf, t0); + write_streambuf(streambuf, t1, tn...); } } // detail diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 104f409d..7fb9c3fd 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include diff --git a/include/beast/http/basic_parser.hpp b/include/beast/http/basic_parser.hpp index 027f3a8a..734da661 100644 --- a/include/beast/http/basic_parser.hpp +++ b/include/beast/http/basic_parser.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -345,7 +345,7 @@ public: return s_ == s_restart; } - /** Write data to the parser. + /** Write a sequence of buffers to the parser. @param buffers An object meeting the requirements of ConstBufferSequence that represents the input sequence. @@ -354,20 +354,23 @@ public: @return The number of bytes consumed in the input sequence. */ - template + template::value>::type + > std::size_t write(ConstBufferSequence const& buffers, error_code& ec); - /** Write data to the parser. + /** Write a single buffer of data to the parser. - @param data A pointer to a buffer representing the input sequence. - @param size The number of bytes in the buffer pointed to by data. + @param buffer The buffer to write. @param ec Set to the error, if any error occurred. - @return The number of bytes consumed in the input sequence. + @return The number of bytes consumed in the buffer. */ std::size_t - write(void const* data, std::size_t size, error_code& ec); + write(boost::asio::const_buffer const& buffer, error_code& ec); /** Called to indicate the end of file. diff --git a/include/beast/http/chunk_encode.hpp b/include/beast/http/detail/chunk_encode.hpp similarity index 98% rename from include/beast/http/chunk_encode.hpp rename to include/beast/http/detail/chunk_encode.hpp index 13944fbd..07106822 100644 --- a/include/beast/http/chunk_encode.hpp +++ b/include/beast/http/detail/chunk_encode.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_HTTP_CHUNK_ENCODE_HPP -#define BEAST_HTTP_CHUNK_ENCODE_HPP +#ifndef BEAST_HTTP_DETAIL_CHUNK_ENCODE_HPP +#define BEAST_HTTP_DETAIL_CHUNK_ENCODE_HPP #include #include @@ -18,7 +18,6 @@ namespace beast { namespace http { - namespace detail { template @@ -237,9 +236,7 @@ chunk_encoded_buffers::const_iterator::const_iterator( { } -} // detail - -/** Returns a chunk-encoded BufferSequence. +/* Returns a chunk-encoded BufferSequence. See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 @@ -276,6 +273,7 @@ chunk_encode_final() "0\r\n\r\n", 5); } +} // detail } // http } // beast diff --git a/include/beast/http/detail/write_preparation.hpp b/include/beast/http/detail/write_preparation.hpp index 13dd6f13..e957cba6 100644 --- a/include/beast/http/detail/write_preparation.hpp +++ b/include/beast/http/detail/write_preparation.hpp @@ -9,6 +9,7 @@ #define BEAST_HTTP_DETAIL_WRITE_PREPARATION_HPP #include +#include #include #include @@ -54,6 +55,12 @@ struct write_preparation message const& msg_) : msg(msg_) , w(msg) + , chunked(rfc2616::token_in_list( + msg.headers["Transfer-Encoding"], "chunked")) + , close(rfc2616::token_in_list( + msg.headers["Connection"], "close") || + (msg.version < 11 && ! msg.headers.exists( + "Content-Length"))) { } @@ -63,57 +70,10 @@ struct write_preparation w.init(ec); if(ec) return; - // VFALCO TODO This implementation requires making a - // copy of the headers, we can do better. - // VFALCO Should we be using handler_alloc? - headers_type h(msg.headers.begin(), msg.headers.end()); - set_content_length(h, has_content_length< - typename Body::writer>{}); - - // VFALCO TODO Keep-Alive - - if(close) - { - if(msg.version >= 11) - h.insert("Connection", "close"); - } - else - { - if(msg.version < 11) - h.insert("Connection", "keep-alive"); - } - msg.write_firstline(sb); - write_fields(sb, h); + write_fields(sb, msg.headers); beast::write(sb, "\r\n"); } - -private: - void - set_content_length(headers_type& h, - std::true_type) - { - close = false; - chunked = false; - h.insert("Content-Length", w.content_length()); - } - - void - set_content_length(headers_type& h, - std::false_type) - { - if(msg.version >= 11) - { - close = false; - chunked = true; - h.insert("Transfer-Encoding", "chunked"); - } - else - { - close = true; - chunked = false; - } - } }; } // detail diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp index 11d2ff91..72ba9099 100644 --- a/include/beast/http/empty_body.hpp +++ b/include/beast/http/empty_body.hpp @@ -60,7 +60,7 @@ private: { } - std::size_t + std::uint64_t content_length() const { return 0; diff --git a/include/beast/http/headers.hpp b/include/beast/http/headers.hpp index 9b919071..3268fa50 100644 --- a/include/beast/http/headers.hpp +++ b/include/beast/http/headers.hpp @@ -14,10 +14,7 @@ namespace beast { namespace http { -template -using headers = basic_headers; - -using http_headers = +using headers = basic_headers>; } // http diff --git a/include/beast/http/impl/basic_parser.ipp b/include/beast/http/impl/basic_parser.ipp index c63549f9..b8395578 100644 --- a/include/beast/http/impl/basic_parser.ipp +++ b/include/beast/http/impl/basic_parser.ipp @@ -32,9 +32,27 @@ keep_alive() const // Implementation inspired by nodejs/http-parser template +template std::size_t basic_parser:: -write(void const* data, std::size_t size, error_code& ec) +write(ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(is_ConstBufferSequence::value, + "ConstBufferSequence requirements not met"); + std::size_t used = 0; + for(auto const& buffer : buffers) + { + used += write(buffer, ec); + if(ec) + break; + } + return used; +} + +template +std::size_t +basic_parser:: +write(boost::asio::const_buffer const& buffer, error_code& ec) { using beast::http::detail::is_digit; using beast::http::detail::is_token; @@ -42,7 +60,12 @@ write(void const* data, std::size_t size, error_code& ec) using beast::http::detail::to_field_char; using beast::http::detail::to_value_char; using beast::http::detail::unhex; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto const data = buffer_cast(buffer); + auto const size = buffer_size(buffer); + if(size == 0 && s_ != s_closed) return 0; @@ -997,27 +1020,6 @@ write(void const* data, std::size_t size, error_code& ec) return used(); } -template -template -std::size_t -basic_parser:: -write(ConstBufferSequence const& buffers, error_code& ec) -{ - static_assert(is_ConstBufferSequence::value, - "ConstBufferSequence requirements not met"); - std::size_t used = 0; - for(auto const& buffer : buffers) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - used += write(buffer_cast(buffer), - buffer_size(buffer), ec); - if(ec) - break; - } - return used; -} - template void basic_parser:: diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index 0f89cc4a..31635d3b 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -8,7 +8,6 @@ #ifndef BEAST_HTTP_IMPL_MESSAGE_IPP #define BEAST_HTTP_IMPL_MESSAGE_IPP -#include #include #include #include @@ -16,8 +15,10 @@ #include #include #include +#include #include #include +#include namespace beast { namespace http { @@ -55,7 +56,7 @@ message:: write_firstline(Streambuf& streambuf, std::true_type) const { - write(streambuf, to_string(this->method)); + write(streambuf, this->method); write(streambuf, " "); write(streambuf, this->url); switch(version) @@ -105,122 +106,6 @@ write_firstline(Streambuf& streambuf, write(streambuf, "\r\n"); } -namespace detail { - -template -std::string -buffers_to_string(ConstBufferSequence const& buffers) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(buffers)); - for(auto const& b : buffers) - s.append(buffer_cast(b), - buffer_size(b)); - return s; -} - -class writef_ostream -{ - std::ostream& os_; - bool chunked_; - -public: - writef_ostream(std::ostream& os, bool chunked) - : os_(os) - , chunked_(chunked) - { - } - - template - void - operator()(ConstBufferSequence const& buffers) - { - if(chunked_) - os_ << buffers_to_string( - chunk_encode(buffers)); - else - os_ << buffers_to_string( - buffers); - } -}; - -} // detail - -// Diagnostic output only -template -std::ostream& -operator<<(std::ostream& os, - message const& msg) -{ - error_code ec; - detail::write_preparation wp(msg); - wp.init(ec); - if(ec) - return os; - std::mutex m; - std::condition_variable cv; - bool ready = false; - resume_context resume{ - [&] - { - std::lock_guard lock(m); - ready = true; - cv.notify_one(); - }}; - auto copy = resume; - os << detail::buffers_to_string(wp.sb.data()); - wp.sb.consume(wp.sb.size()); - detail::writef_ostream writef(os, wp.chunked); - for(;;) - { - { - auto result = wp.w(std::move(copy), ec, writef); - if(ec) - return os; - if(result) - break; - if(boost::indeterminate(result)) - { - copy = resume; - std::unique_lock lock(m); - cv.wait(lock, [&]{ return ready; }); - ready = false; - } - } - wp.sb.consume(wp.sb.size()); - for(;;) - { - auto result = wp.w(std::move(copy), ec, writef); - if(ec) - return os; - if(result) - break; - if(boost::indeterminate(result)) - { - copy = resume; - std::unique_lock lock(m); - cv.wait(lock, [&]{ return ready; }); - ready = false; - } - } - } - if(wp.chunked) - { - // VFALCO Unfortunately the current interface to the - // Writer concept prevents us from using coalescing the - // final body chunk with the final chunk delimiter. - // - // write final chunk - os << detail::buffers_to_string(chunk_encode_final()); - if(ec) - return os; - } - os << std::endl; - return os; -} - //------------------------------------------------------------------------------ template @@ -310,9 +195,176 @@ is_upgrade(message const& msg) return false; } +namespace detail { + +struct prepare_info +{ + boost::optional connection_value; + boost::optional content_length; +}; + +template +inline +void +prepare_options(prepare_info& pi, + message& msg) +{ +} + +template +void +prepare_option(prepare_info& pi, + message& msg, + connection value) +{ + pi.connection_value = value; +} + +template< + bool isRequest, class Body, class Headers, + class Opt, class... Opts> +void +prepare_options(prepare_info& pi, + message& msg, + Opt&& opt, Opts&&... opts) +{ + prepare_option(pi, msg, opt); + prepare_options(pi, msg, + std::forward(opts)...); +} + +template +void +prepare_content_length(prepare_info& pi, + message const& msg, + std::true_type) +{ + typename Body::writer w(msg); + //w.init(ec); // VFALCO This is a design problem! + pi.content_length = w.content_length(); +} + +template +void +prepare_content_length(prepare_info& pi, + message const& msg, + std::false_type) +{ + pi.content_length = boost::none; +} + +} // detail + +template +void +prepare_connection( + message& msg) +{ + if(msg.version >= 11) + { + if(! msg.headers.exists("Content-Length") && + ! rfc2616::token_in_list( + msg.headers["Transfer-Encoding"], "chunked")) + if(! rfc2616::token_in_list( + msg.headers["Connection"], "close")) + msg.headers.insert("Connection", "close"); + } + else + { + if(! msg.headers.exists("Content-Length")) + { + // VFALCO We are erasing the whole header when we + // should be removing just the keep-alive. + if(rfc2616::token_in_list( + msg.headers["Connection"], "keep-alive")) + msg.headers.erase("Connection"); + } + } +} + +template< + bool isRequest, class Body, class Headers, + class... Options> +void +prepare(message& msg, + Options&&... options) +{ + // VFALCO TODO + //static_assert(is_WritableBody::value, + // "WritableBody requirements not met"); + detail::prepare_info pi; + detail::prepare_content_length(pi, msg, + detail::has_content_length{}); + detail::prepare_options(pi, msg, + std::forward(options)...); + + if(msg.headers.exists("Connection")) + throw std::invalid_argument( + "prepare called with Connection field set"); + + if(msg.headers.exists("Content-Length")) + throw std::invalid_argument( + "prepare called with Content-Length field set"); + + if(rfc2616::token_in_list( + msg.headers["Transfer-Encoding"], "chunked")) + throw std::invalid_argument( + "prepare called with Transfer-Encoding: chunked set"); + + if(pi.connection_value != connection::upgrade) + { + if(pi.content_length) + { + // VFALCO TODO Use a static string here + msg.headers.insert("Content-Length", + std::to_string(*pi.content_length)); + } + else if(msg.version >= 11) + { + msg.headers.insert("Transfer-Encoding", "chunked"); + } + } + + auto const content_length = + msg.headers.exists("Content-Length"); + + if(pi.connection_value) + { + switch(*pi.connection_value) + { + case connection::upgrade: + msg.headers.insert("Connection", "upgrade"); + break; + + case connection::keep_alive: + if(msg.version < 11) + { + if(content_length) + msg.headers.insert("Connection", "keep-alive"); + } + break; + + case connection::close: + if(msg.version >= 11) + msg.headers.insert("Connection", "close"); + break; + } + } + + // rfc7230 6.7. + if(msg.version < 11 && rfc2616::token_in_list( + msg.headers["Connection"], "upgrade")) + throw std::invalid_argument( + "invalid version for Connection: upgrade"); + + // rfc7230 3.3.2 + if(msg.headers.exists("Content-Length") && + msg.headers.exists("Transfer-Encoding")) + throw std::invalid_argument( + "Content-Length and Transfer-Encoding cannot be combined"); +} + } // http } // beast -#include - #endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index f1640c02..3b6d0ecb 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -8,8 +8,8 @@ #ifndef BEAST_HTTP_IMPL_WRITE_IPP #define BEAST_HTTP_IMPL_WRITE_IPP -#include #include +#include #include #include #include @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include namespace beast { @@ -77,7 +79,7 @@ class write_op if(d.wp.chunked) boost::asio::async_write(d.s, buffer_cat(d.wp.sb.data(), - chunk_encode(buffers)), + detail::chunk_encode(buffers)), std::move(self_)); else boost::asio::async_write(d.s, @@ -104,7 +106,7 @@ class write_op // write body if(d.wp.chunked) boost::asio::async_write(d.s, - chunk_encode(buffers), + detail::chunk_encode(buffers), std::move(self_)); else boost::asio::async_write(d.s, @@ -269,7 +271,7 @@ operator()(error_code ec, std::size_t, bool again) // write final chunk d.state = 5; boost::asio::async_write(d.s, - chunk_encode_final(), std::move(*this)); + detail::chunk_encode_final(), std::move(*this)); return; case 5: @@ -311,7 +313,7 @@ public: // write headers and body if(chunked_) boost::asio::write(stream_, buffer_cat( - sb_.data(), chunk_encode(buffers)), ec_); + sb_.data(), detail::chunk_encode(buffers)), ec_); else boost::asio::write(stream_, buffer_cat( sb_.data(), buffers), ec_); @@ -340,7 +342,7 @@ public: // write body if(chunked_) boost::asio::write(stream_, - chunk_encode(buffers), ec_); + detail::chunk_encode(buffers), ec_); else boost::asio::write(stream_, buffers, ec_); } @@ -357,6 +359,8 @@ write(SyncWriteStream& stream, message const& msg, boost::system::error_code& ec) { + static_assert(is_SyncWriteStream::value, + "SyncWriteStream requirements not met"); detail::write_preparation wp(msg); wp.init(ec); if(ec) @@ -420,7 +424,7 @@ write(SyncWriteStream& stream, // final body chunk with the final chunk delimiter. // // write final chunk - boost::asio::write(stream, chunk_encode_final(), ec); + boost::asio::write(stream, detail::chunk_encode_final(), ec); if(ec) return; } @@ -450,6 +454,68 @@ async_write(AsyncWriteStream& stream, return completion.result.get(); } +namespace detail { + +class ostream_SyncStream +{ + std::ostream& os_; + +public: + ostream_SyncStream(std::ostream& os) + : os_(os) + { + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + error_code ec; + auto const n = write_some(buffers, ec); + if(ec) + throw boost::system::system_error{ec}; + return n; + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers, + error_code& ec) + { + std::size_t n = 0; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for(auto const& buffer : buffers) + { + os_.write(buffer_cast(buffer), + buffer_size(buffer)); + if(os_.fail()) + { + ec = boost::system::errc::make_error_code( + boost::system::errc::no_stream_resources); + break; + } + n += buffer_size(buffer); + } + return n; + } +}; + +} // detail + +template +std::ostream& +operator<<(std::ostream& os, + message const& msg) +{ + detail::ostream_SyncStream oss(os); + error_code ec; + write(oss, msg, ec); + if(ec && ec != boost::asio::error::eof) + throw boost::system::system_error{ec}; + return os; +} + } // http } // beast diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 33e7bb1c..1c0c0c81 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -9,11 +9,8 @@ #define BEAST_HTTP_MESSAGE_HPP #include -#include -#include #include #include -#include #include namespace beast { @@ -23,7 +20,7 @@ namespace detail { struct request_fields { - http::method_t method; + std::string method; std::string url; }; @@ -39,7 +36,7 @@ struct response_fields struct request_params { - http::method_t method; + std::string method; std::string url; int version; }; @@ -145,12 +142,6 @@ using response = message; #endif -// For diagnostic output only -template -std::ostream& -operator<<(std::ostream& os, - message const& m); - /// Write a FieldSequence to a Streambuf. template void @@ -166,6 +157,35 @@ template bool is_upgrade(message const& msg); +/** Connection prepare options. + + These values are used with prepare(). +*/ +enum class connection +{ + /// Indicates the message should specify Connection: close semantics + close, + + /// Indicates the message should specify Connection: keep-alive semantics if possible + keep_alive, + + /// Indicates the message should specify a Connection: upgrade + upgrade +}; + +/** Prepare a message. + + This function will adjust the Content-Length, Transfer-Encoding, + and Connection headers of the message based on the properties of + the body and the options passed in. +*/ +template< + bool isRequest, class Body, class Headers, + class... Options> +void +prepare(message& msg, + Options&&... options); + } // http } // beast diff --git a/include/beast/http/method.hpp b/include/beast/http/method.hpp deleted file mode 100644 index 0c1acf04..00000000 --- a/include/beast/http/method.hpp +++ /dev/null @@ -1,179 +0,0 @@ -// -// 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) -// - -#ifndef BEAST_HTTP_METHOD_HPP -#define BEAST_HTTP_METHOD_HPP - -#include -#include -#include - -namespace beast { -namespace http { - -enum class method_t -{ - http_delete, - http_get, - http_head, - http_post, - http_put, - - // pathological - http_connect, - http_options, - http_trace, - - // webdav - http_copy, - http_lock, - http_mkcol, - http_move, - http_propfind, - http_proppatch, - http_search, - http_unlock, - http_bind, - http_rebind, - http_unbind, - http_acl, - - // subversion - http_report, - http_mkactivity, - http_checkout, - http_merge, - - // upnp - http_msearch, - http_notify, - http_subscribe, - http_unsubscribe, - - // RFC-5789 - http_patch, - http_purge, - - // CalDav - http_mkcalendar, - - // RFC-2068, section 19.6.1.2 - http_link, - http_unlink -}; - -template -std::string -to_string(method_t m) -{ - switch(m) - { - case method_t::http_delete: return "DELETE"; - case method_t::http_get: return "GET"; - case method_t::http_head: return "HEAD"; - case method_t::http_post: return "POST"; - case method_t::http_put: return "PUT"; - - case method_t::http_connect: return "CONNECT"; - case method_t::http_options: return "OPTIONS"; - case method_t::http_trace: return "TRACE"; - - case method_t::http_copy: return "COPY"; - case method_t::http_lock: return "LOCK"; - case method_t::http_mkcol: return "MKCOL"; - case method_t::http_move: return "MOVE"; - case method_t::http_propfind: return "PROPFIND"; - case method_t::http_proppatch: return "PROPPATCH"; - case method_t::http_search: return "SEARCH"; - case method_t::http_unlock: return "UNLOCK"; - - case method_t::http_report: return "REPORT"; - case method_t::http_mkactivity: return "MKACTIVITY"; - case method_t::http_checkout: return "CHECKOUT"; - case method_t::http_merge: return "MERGE"; - - case method_t::http_msearch: return "MSEARCH"; - case method_t::http_notify: return "NOTIFY"; - case method_t::http_subscribe: return "SUBSCRIBE"; - case method_t::http_unsubscribe: return "UNSUBSCRIBE"; - - case method_t::http_patch: return "PATCH"; - case method_t::http_purge: return "PURGE"; - - default: - assert(false); - break; - }; - - return "GET"; -} - -template -Stream& -operator<< (Stream& s, method_t m) -{ - return s << to_string(m); -} - -/** Returns the string corresponding to the numeric HTTP status code. */ -template -std::string -status_text (int status) -{ - switch(status) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - //case 306: return ""; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - default: - break; - } - return "Unknown HTTP status"; -} - -} // http -} // beast - -#endif diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp index 3dcc6d61..4161a877 100644 --- a/include/beast/http/parser.hpp +++ b/include/beast/http/parser.hpp @@ -107,96 +107,14 @@ private: void set(std::true_type) { - // VFALCO This is terrible for setting method - auto m = - [&](char const* s, method_t m) - { - if(this->method_ == s) - { - m_.method = m; - return true; - } - return false; - }; - do - { - if(m("DELETE", method_t::http_delete)) - break; - if(m("GET", method_t::http_get)) - break; - if(m("HEAD", method_t::http_head)) - break; - if(m("POST", method_t::http_post)) - break; - if(m("PUT", method_t::http_put)) - break; - if(m("CONNECT", method_t::http_connect)) - break; - if(m("OPTIONS", method_t::http_options)) - break; - if(m("TRACE", method_t::http_trace)) - break; - if(m("COPY", method_t::http_copy)) - break; - if(m("LOCK", method_t::http_lock)) - break; - if(m("MKCOL", method_t::http_mkcol)) - break; - if(m("MOVE", method_t::http_move)) - break; - if(m("PROPFIND", method_t::http_propfind)) - break; - if(m("PROPPATCH", method_t::http_proppatch)) - break; - if(m("SEARCH", method_t::http_search)) - break; - if(m("UNLOCK", method_t::http_unlock)) - break; - if(m("BIND", method_t::http_bind)) - break; - if(m("REBID", method_t::http_rebind)) - break; - if(m("UNBIND", method_t::http_unbind)) - break; - if(m("ACL", method_t::http_acl)) - break; - if(m("REPORT", method_t::http_report)) - break; - if(m("MKACTIVITY", method_t::http_mkactivity)) - break; - if(m("CHECKOUT", method_t::http_checkout)) - break; - if(m("MERGE", method_t::http_merge)) - break; - if(m("MSEARCH", method_t::http_msearch)) - break; - if(m("NOTIFY", method_t::http_notify)) - break; - if(m("SUBSCRIBE", method_t::http_subscribe)) - break; - if(m("UNSUBSCRIBE",method_t::http_unsubscribe)) - break; - if(m("PATCH", method_t::http_patch)) - break; - if(m("PURGE", method_t::http_purge)) - break; - if(m("MKCALENDAR", method_t::http_mkcalendar)) - break; - if(m("LINK", method_t::http_link)) - break; - if(m("UNLINK", method_t::http_unlink)) - break; - } - while(false); - + m_.method = std::move(this->method_); m_.url = std::move(this->uri_); - } void set(std::false_type) { m_.status = this->status_code(); - m_.reason = this->reason_; + m_.reason = std::move(this->reason_); } int on_headers(error_code&) diff --git a/include/beast/http/status.hpp b/include/beast/http/status.hpp new file mode 100644 index 00000000..ee7a9c9c --- /dev/null +++ b/include/beast/http/status.hpp @@ -0,0 +1,71 @@ +// +// 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) +// + +#ifndef BEAST_HTTP_STATUS_HPP +#define BEAST_HTTP_STATUS_HPP + +namespace beast { +namespace http { + +/** Returns the string corresponding to the numeric HTTP status code. */ +template +char const* +status_text(int status) +{ + switch(status) + { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; +// case 306: return ""; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + default: + break; + } + return "Unknown HTTP status"; +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/streambuf_body.hpp b/include/beast/http/streambuf_body.hpp index 745935d1..02238ec1 100644 --- a/include/beast/http/streambuf_body.hpp +++ b/include/beast/http/streambuf_body.hpp @@ -72,7 +72,7 @@ private: { } - std::size_t + std::uint64_t content_length() const { return body_.size(); diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp index 2711f8c1..81b07044 100644 --- a/include/beast/http/string_body.hpp +++ b/include/beast/http/string_body.hpp @@ -70,7 +70,7 @@ private: { } - std::size_t + std::uint64_t content_length() const { return body_.size(); diff --git a/include/beast/http/type_check.hpp b/include/beast/http/type_check.hpp new file mode 100644 index 00000000..0f3948a1 --- /dev/null +++ b/include/beast/http/type_check.hpp @@ -0,0 +1,62 @@ +// +// 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) +// + +#ifndef BEAST_HTTP_TYPE_CHECK_HPP +#define BEAST_HTTP_TYPE_CHECK_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { + +/// Determine if `T` meets the requirements of `Parser`. +template +class is_Parser +{ + template().complete()), + bool>> + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template().write( + std::declval(), + std::declval())), + std::size_t>> + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + + template().write_eof( + std::declval())), + std::size_t>> + static R check3(int); + template + static std::false_type check3(...); + using type3 = decltype(check3(0)); + +public: + /// `true` if `T` meets the requirements. + static bool const value = + type1::value && type2::value && type3::value; +}; + +} // http +} // beast + +#endif diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp index 0489dda6..ae71b486 100644 --- a/include/beast/http/write.hpp +++ b/include/beast/http/write.hpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include namespace beast { @@ -85,6 +87,20 @@ async_write(AsyncWriteStream& stream, message const& msg, WriteHandler&& handler); +/** Serialize a message to an ostream. + + The function converts the message to its HTTP/1.* serialized + representation and stores the result in the output stream. + + @param os The ostream to write to. + + @param msg The message to write. +*/ +template +std::ostream& +operator<<(std::ostream& os, + message const& msg); + } // http } // beast diff --git a/include/beast/impl/basic_streambuf.ipp b/include/beast/impl/basic_streambuf.ipp index 7ba989ee..2b228931 100644 --- a/include/beast/impl/basic_streambuf.ipp +++ b/include/beast/impl/basic_streambuf.ipp @@ -118,7 +118,7 @@ public: template class basic_streambuf::const_buffers_type { - basic_streambuf const* sb_ = nullptr; + basic_streambuf const* sb_; friend class basic_streambuf; @@ -126,12 +126,12 @@ class basic_streambuf::const_buffers_type const_buffers_type(basic_streambuf const& sb); public: - /// Why? + // Why? using value_type = boost::asio::const_buffer; class const_iterator; - const_buffers_type() = default; + const_buffers_type() = delete; const_buffers_type(const_buffers_type const&) = default; const_buffers_type& operator=(const_buffers_type const&) = default; @@ -157,7 +157,7 @@ public: class const_iterator; - mutable_buffers_type() = default; + mutable_buffers_type() = delete; mutable_buffers_type(mutable_buffers_type const&) = default; mutable_buffers_type& operator=(mutable_buffers_type const&) = default; diff --git a/include/beast/impl/buffers_adapter.ipp b/include/beast/impl/buffers_adapter.ipp index 0040fd5a..d0b22c6a 100644 --- a/include/beast/impl/buffers_adapter.ipp +++ b/include/beast/impl/buffers_adapter.ipp @@ -27,7 +27,7 @@ public: class const_iterator; - const_buffers_type() = default; + const_buffers_type() = delete; const_buffers_type( const_buffers_type const&) = default; const_buffers_type& operator=( @@ -52,7 +52,7 @@ template class buffers_adapter::const_buffers_type::const_iterator { iter_type it_; - buffers_adapter const* ba_; + buffers_adapter const* ba_ = nullptr; public: using value_type = boost::asio::const_buffer; @@ -167,7 +167,7 @@ public: class const_iterator; - mutable_buffers_type() = default; + mutable_buffers_type() = delete; mutable_buffers_type( mutable_buffers_type const&) = default; mutable_buffers_type& operator=( @@ -193,7 +193,7 @@ template class buffers_adapter::mutable_buffers_type::const_iterator { iter_type it_; - buffers_adapter const* ba_; + buffers_adapter const* ba_ = nullptr; public: using value_type = boost::asio::mutable_buffer; diff --git a/include/beast/impl/consuming_buffers.ipp b/include/beast/impl/consuming_buffers.ipp index 4aa203f5..864bde3a 100644 --- a/include/beast/impl/consuming_buffers.ipp +++ b/include/beast/impl/consuming_buffers.ipp @@ -27,7 +27,7 @@ class consuming_buffers::const_iterator typename Buffers::const_iterator; iter_type it_; - consuming_buffers const* b_; + consuming_buffers const* b_ = nullptr; public: using value_type = @@ -59,9 +59,8 @@ public: reference operator*() const { - if(it_ == b_->begin_) - return *it_ + b_->skip_; - return *it_; + return it_ == b_->begin_ ? + *it_ + b_->skip_ : *it_; } pointer diff --git a/include/beast/impl/prepare_buffers.ipp b/include/beast/impl/prepare_buffers.ipp index 65de4a3b..1bf469bc 100644 --- a/include/beast/impl/prepare_buffers.ipp +++ b/include/beast/impl/prepare_buffers.ipp @@ -46,7 +46,7 @@ class prepared_buffers::const_iterator using iter_type = typename BufferSequence::const_iterator; - prepared_buffers const* b_; + prepared_buffers const* b_ = nullptr; typename BufferSequence::const_iterator it_; public: diff --git a/include/beast/impl/static_streambuf.ipp b/include/beast/impl/static_streambuf.ipp index 0d75a820..ab487fab 100644 --- a/include/beast/impl/static_streambuf.ipp +++ b/include/beast/impl/static_streambuf.ipp @@ -26,7 +26,7 @@ public: class const_iterator; - const_buffers_type() = default; + const_buffers_type() = delete; const_buffers_type( const_buffers_type const&) = default; const_buffers_type& operator=( @@ -51,8 +51,8 @@ private: class static_streambuf::const_buffers_type::const_iterator { - std::size_t n_; - std::uint8_t const* p_; + std::size_t n_ = 0; + std::uint8_t const* p_ = nullptr; public: using value_type = boost::asio::const_buffer; @@ -158,7 +158,7 @@ public: class const_iterator; - mutable_buffers_type() = default; + mutable_buffers_type() = delete; mutable_buffers_type( mutable_buffers_type const&) = default; mutable_buffers_type& operator=( @@ -183,8 +183,8 @@ private: class static_streambuf::mutable_buffers_type::const_iterator { - std::size_t n_; - std::uint8_t* p_; + std::size_t n_ = 0; + std::uint8_t* p_ = nullptr; public: using value_type = boost::asio::mutable_buffer; diff --git a/include/beast/to_string.hpp b/include/beast/to_string.hpp new file mode 100644 index 00000000..a21c7e09 --- /dev/null +++ b/include/beast/to_string.hpp @@ -0,0 +1,51 @@ +// +// 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) +// + +#ifndef BEAST_TO_STRING_HPP +#define BEAST_TO_STRING_HPP + +#include +#include +#include + +namespace beast { + +/** Convert a `ConstBufferSequence` to a `std::string`. + + This function will convert the octets in a buffer sequence to a string. + All octets will be inserted into the resulting string, including null + or unprintable characters. + + @param buffers The buffer sequence to convert. + + @returns A string representing the contents of the input area. + + @note This function participates in overload resolution only if + the streambuf parameter meets the requirements of Streambuf. +*/ +template::value> +#endif +> +std::string +to_string(ConstBufferSequence const& buffers) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(buffers)); + for(auto const& buffer : buffers) + s.append(buffer_cast(buffer), + buffer_size(buffer)); + return s; +} + +} // beast + +#endif diff --git a/include/beast/websocket/detail/endian.hpp b/include/beast/websocket/detail/endian.hpp index a92b062a..addfd60a 100644 --- a/include/beast/websocket/detail/endian.hpp +++ b/include/beast/websocket/detail/endian.hpp @@ -53,6 +53,17 @@ little_uint32_to_native(void const* buf) (static_cast(p[3])<<24); } +inline +void +native_to_little_uint32(std::uint32_t v, void* buf) +{ + auto p = reinterpret_cast(buf); + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; +} + } // detail } // websocket } // beast diff --git a/include/beast/websocket/detail/frame.hpp b/include/beast/websocket/detail/frame.hpp index 6ddbf1ac..7ff89747 100644 --- a/include/beast/websocket/detail/frame.hpp +++ b/include/beast/websocket/detail/frame.hpp @@ -141,9 +141,7 @@ write(Streambuf& sb, frame_header const& fh) } if(fh.mask) { - little_uint32_buf_t key(fh.key); - std::copy(key.data(), - key.data() + 4, &b[n]); + native_to_little_uint32(fh.key, &b[n]); n += 4; } sb.commit(buffer_copy( diff --git a/include/beast/websocket/impl/close_op.ipp b/include/beast/websocket/impl/close_op.ipp index c3201903..98da94d2 100644 --- a/include/beast/websocket/impl/close_op.ipp +++ b/include/beast/websocket/impl/close_op.ipp @@ -34,7 +34,6 @@ class stream::close_op close_reason cr; Handler h; fb_type fb; - fmb_type fmb; bool cont; int state = 0; diff --git a/include/beast/websocket/impl/read_frame_op.ipp b/include/beast/websocket/impl/read_frame_op.ipp index 82e2da98..606f801c 100644 --- a/include/beast/websocket/impl/read_frame_op.ipp +++ b/include/beast/websocket/impl/read_frame_op.ipp @@ -42,10 +42,10 @@ class stream::read_frame_op stream& ws; frame_info& fi; Streambuf& sb; - smb_type smb; Handler h; fb_type fb; - fmb_type fmb; + boost::optional smb; + boost::optional fmb; bool cont; int state = 0; @@ -161,7 +161,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.smb = d.sb.prepare( detail::clamp(d.ws.rd_need_)); d.ws.stream_.async_read_some( - d.smb, std::move(*this)); + *d.smb, std::move(*this)); return; case 2: @@ -176,7 +176,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) { d.ws.rd_need_ -= bytes_transferred; auto const pb = prepare_buffers( - bytes_transferred, d.smb); + bytes_transferred, *d.smb); if(d.ws.rd_fh_.mask) detail::mask_inplace(pb, d.ws.rd_key_); if(d.ws.rd_opcode_ == opcode::text) @@ -252,7 +252,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.fmb = d.fb.prepare(static_cast< std::size_t>(d.ws.rd_fh_.len)); boost::asio::async_read(d.ws.stream_, - d.fmb, std::move(*this)); + *d.fmb, std::move(*this)); return; } d.state = 8; @@ -276,7 +276,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) case 7: if(d.ws.rd_fh_.mask) detail::mask_inplace( - d.fmb, d.ws.rd_key_); + *d.fmb, d.ws.rd_key_); d.fb.commit(bytes_transferred); d.state = 8; break; diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 033c7a8b..26a1b5a4 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -99,7 +99,8 @@ stream_base::write_close( fh.rsv3 = false; fh.len = cr.code == close_code::none ? 0 : 2 + cr.reason.size(); - if((fh.mask = (role_ == role_type::client))) + fh.mask = role_ == role_type::client; + if(fh.mask) fh.key = maskgen_(); detail::write(sb, fh); if(cr.code != close_code::none) @@ -143,7 +144,8 @@ stream_base::write_ping(Streambuf& sb, fh.rsv2 = false; fh.rsv3 = false; fh.len = data.size(); - if((fh.mask = (role_ == role_type::client))) + fh.mask = role_ == role_type::client; + if(fh.mask) fh.key = maskgen_(); detail::write(sb, fh); if(data.empty()) @@ -610,7 +612,8 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) fh.rsv2 = false; fh.rsv3 = false; fh.len = buffer_size(bs); - if((fh.mask = (role_ == role_type::client))) + fh.mask = role_ == role_type::client; + if(fh.mask) fh.key = maskgen_(); detail::fh_streambuf fh_buf; detail::write(fh_buf, fh); @@ -698,14 +701,14 @@ build_request(boost::string_ref const& host, http::request req; req.url = "/"; req.version = 11; - req.method = http::method_t::http_get; + req.method = "GET"; req.headers.insert("Host", host); - req.headers.insert("Connection", "upgrade"); req.headers.insert("Upgrade", "websocket"); key = detail::make_sec_ws_key(maskgen_); req.headers.insert("Sec-WebSocket-Key", key); req.headers.insert("Sec-WebSocket-Version", "13"); (*d_)(req); + http::prepare(req, http::connection::upgrade); return req; } @@ -726,7 +729,7 @@ build_response(http::message const& req) }; if(req.version < 11) return err("HTTP version 1.1 required"); - if(req.method != http::method_t::http_get) + if(req.method != "GET") return err("Wrong method"); if(! is_upgrade(req)) return err("Expected Upgrade request"); @@ -748,7 +751,6 @@ build_response(http::message const& req) http::response resp( {101, http::reason_string(101), req.version}); resp.headers.insert("Upgrade", "websocket"); - resp.headers.insert("Connection", "upgrade"); { auto const key = req.headers["Sec-WebSocket-Key"]; @@ -758,6 +760,7 @@ build_response(http::message const& req) } resp.headers.replace("Server", "Beast.WSProto"); (*d_)(resp); + http::prepare(resp, http::connection::upgrade); return resp; } diff --git a/include/beast/write_streambuf.hpp b/include/beast/write_streambuf.hpp index f4e92485..9a5fca6f 100644 --- a/include/beast/write_streambuf.hpp +++ b/include/beast/write_streambuf.hpp @@ -53,9 +53,9 @@ void #else typename std::enable_if::value>::type #endif -write(Streambuf& streambuf, Args&&... args) +write(Streambuf& streambuf, Args const&... args) { - detail::write_streambuf(streambuf, std::forward(args)...); + detail::write_streambuf(streambuf, args...); } } // beast diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ab83bdb7..2eabd99c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,14 +3,14 @@ GroupSources(include/beast) GroupSources(test) -set(CORE_TESTS_SRCS +add_executable (core-tests + ${BEAST_INCLUDES} main.cpp async_completion.cpp basic_streambuf.cpp bind_handler.cpp buffer_cat.cpp buffers_adapter.cpp - buffers_debug.cpp consuming_buffers.cpp handler_alloc.cpp placeholders.cpp @@ -18,26 +18,25 @@ set(CORE_TESTS_SRCS static_streambuf.cpp streambuf.cpp streambuf_readstream.cpp + to_string.cpp type_check.cpp detail/base64.cpp detail/empty_base_optimization.cpp ) -add_executable (core-tests - ${BEAST_INCLUDES} - ${CORE_TESTS_SRCS} -) +if (NOT WIN32) + target_link_libraries(core-tests ${Boost_LIBRARIES}) +endif() -set(HTTP_TESTS_SRCS +add_executable (http-tests + ${BEAST_INCLUDES} main.cpp http/basic_headers.cpp http/basic_parser.cpp - http/chunk_encode.cpp http/empty_body.cpp http/error.cpp http/headers.cpp http/message.cpp - http/method.cpp http/parse_error.cpp http/parser.cpp http/read.cpp @@ -45,17 +44,18 @@ set(HTTP_TESTS_SRCS http/resume_context.cpp http/rfc2616.cpp http/rfc7230.cpp + http/status.cpp http/streambuf_body.cpp http/string_body.cpp http/write.cpp ) -add_executable (http-tests - ${BEAST_INCLUDES} - ${HTTP_TESTS_SRCS} -) +if (NOT WIN32) + target_link_libraries(http-tests ${Boost_LIBRARIES}) +endif() -set(WEBSOCKET_TESTS_SRCS +add_executable (websocket-tests + ${BEAST_INCLUDES} main.cpp websocket/error.cpp websocket/option.cpp @@ -65,18 +65,18 @@ set(WEBSOCKET_TESTS_SRCS websocket/utf8_checker.cpp ) -add_executable (websocket-tests - ${BEAST_INCLUDES} - ${WEBSOCKET_TESTS_SRCS} -) +if (NOT WIN32) + target_link_libraries(websocket-tests ${Boost_LIBRARIES}) +endif() -set(PARSER_BENCH_SRCS +add_executable (parser-bench + ${BEAST_INCLUDES} main.cpp http/nodejs_parser.cpp http/parser_bench.cpp ) -add_executable (parser-bench - ${BEAST_INCLUDES} - ${PARSER_BENCH_SRCS} -) +if (NOT WIN32) + target_link_libraries(parser-bench ${Boost_LIBRARIES}) +endif() + diff --git a/test/Jamfile b/test/Jamfile index 953e79a3..b92ccb48 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -14,7 +14,6 @@ unit-test core-tests : bind_handler.cpp buffer_cat.cpp buffers_adapter.cpp - buffers_debug.cpp consuming_buffers.cpp handler_alloc.cpp placeholders.cpp @@ -22,6 +21,7 @@ unit-test core-tests : static_streambuf.cpp streambuf.cpp streambuf_readstream.cpp + to_string.cpp type_check.cpp detail/base64.cpp detail/empty_base_optimization.cpp @@ -31,12 +31,10 @@ unit-test http-tests : main.cpp http/basic_headers.cpp http/basic_parser.cpp - http/chunk_encode.cpp http/empty_body.cpp http/error.cpp http/headers.cpp http/message.cpp - http/method.cpp http/parse_error.cpp http/parser.cpp http/read.cpp @@ -44,6 +42,7 @@ unit-test http-tests : http/resume_context.cpp http/rfc2616.cpp http/rfc7230.cpp + http/status.cpp http/streambuf_body.cpp http/string_body.cpp http/write.cpp diff --git a/test/basic_streambuf.cpp b/test/basic_streambuf.cpp index 80f8ef92..5a43e777 100644 --- a/test/basic_streambuf.cpp +++ b/test/basic_streambuf.cpp @@ -179,40 +179,81 @@ public: std::size_t v = s.size() - (t + u); { streambuf sb(i); - decltype(sb)::mutable_buffers_type d; - d = sb.prepare(z); expect(buffer_size(d) == z); - d = sb.prepare(0); expect(buffer_size(d) == 0); - d = sb.prepare(y); expect(buffer_size(d) == y); - d = sb.prepare(x); expect(buffer_size(d) == x); - sb.commit(buffer_copy(d, buffer(s.data(), x))); + { + auto d = sb.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = sb.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = sb.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = sb.prepare(x); + expect(buffer_size(d) == x); + sb.commit(buffer_copy(d, buffer(s.data(), x))); + } expect(sb.size() == x); expect(buffer_size(sb.data()) == sb.size()); - d = sb.prepare(x); expect(buffer_size(d) == x); - d = sb.prepare(0); expect(buffer_size(d) == 0); - d = sb.prepare(z); expect(buffer_size(d) == z); - d = sb.prepare(y); expect(buffer_size(d) == y); - sb.commit(buffer_copy(d, buffer(s.data()+x, y))); + { + auto d = sb.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = sb.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = sb.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = sb.prepare(y); + expect(buffer_size(d) == y); + sb.commit(buffer_copy(d, buffer(s.data()+x, y))); + } sb.commit(1); expect(sb.size() == x + y); expect(buffer_size(sb.data()) == sb.size()); - d = sb.prepare(x); expect(buffer_size(d) == x); - d = sb.prepare(y); expect(buffer_size(d) == y); - d = sb.prepare(0); expect(buffer_size(d) == 0); - d = sb.prepare(z); expect(buffer_size(d) == z); - sb.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + { + auto d = sb.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = sb.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = sb.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = sb.prepare(z); + expect(buffer_size(d) == z); + sb.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } sb.commit(2); expect(sb.size() == x + y + z); expect(buffer_size(sb.data()) == sb.size()); expect(to_string(sb.data()) == s); sb.consume(t); - d = sb.prepare(0); expect(buffer_size(d) == 0); + { + auto d = sb.prepare(0); + expect(buffer_size(d) == 0); + } expect(to_string(sb.data()) == s.substr(t, std::string::npos)); sb.consume(u); expect(to_string(sb.data()) == s.substr(t + u, std::string::npos)); sb.consume(v); expect(to_string(sb.data()) == ""); sb.consume(1); - d = sb.prepare(0); expect(buffer_size(d) == 0); + { + auto d = sb.prepare(0); + expect(buffer_size(d) == 0); + } } }}}}} } diff --git a/test/buffers_adapter.cpp b/test/buffers_adapter.cpp index 1b14cf42..08588135 100644 --- a/test/buffers_adapter.cpp +++ b/test/buffers_adapter.cpp @@ -61,43 +61,83 @@ public: mutable_buffer{&buf[i+j], k}}}; buffers_adapter ba(std::move(bs)); expect(ba.max_size() == sizeof(buf)); - decltype(ba)::mutable_buffers_type d; - d = ba.prepare(z); expect(buffer_size(d) == z); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(y); expect(buffer_size(d) == y); - d = ba.prepare(x); expect(buffer_size(d) == x); - ba.commit(buffer_copy(d, buffer(s.data(), x))); + { + auto d = ba.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + ba.commit(buffer_copy(d, buffer(s.data(), x))); + } expect(ba.size() == x); expect(ba.max_size() == sizeof(buf) - x); expect(buffer_size(ba.data()) == ba.size()); - d = ba.prepare(x); expect(buffer_size(d) == x); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(z); expect(buffer_size(d) == z); - d = ba.prepare(y); expect(buffer_size(d) == y); - ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + } ba.commit(1); expect(ba.size() == x + y); expect(ba.max_size() == sizeof(buf) - (x + y)); expect(buffer_size(ba.data()) == ba.size()); - d = ba.prepare(x); expect(buffer_size(d) == x); - d = ba.prepare(y); expect(buffer_size(d) == y); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(z); expect(buffer_size(d) == z); - ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); expect(buffer_size(d) == z); + ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } ba.commit(2); expect(ba.size() == x + y + z); expect(ba.max_size() == 0); expect(buffer_size(ba.data()) == ba.size()); expect(to_string(ba.data()) == s); ba.consume(t); - d = ba.prepare(0); expect(buffer_size(d) == 0); + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } expect(to_string(ba.data()) == s.substr(t, std::string::npos)); ba.consume(u); expect(to_string(ba.data()) == s.substr(t + u, std::string::npos)); ba.consume(v); expect(to_string(ba.data()) == ""); ba.consume(1); - d = ba.prepare(0); expect(buffer_size(d) == 0); + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } try { ba.prepare(1); diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp index e1cfc1e3..1892b4cc 100644 --- a/test/http/basic_parser.cpp +++ b/test/http/basic_parser.cpp @@ -11,7 +11,6 @@ #include "message_fuzz.hpp" #include -#include #include #include #include @@ -183,6 +182,7 @@ public: void testCallbacks() { + using boost::asio::buffer; { cb_checker p; error_code ec; @@ -192,7 +192,7 @@ public: "Content-Length: 1\r\n" "\r\n" "*"; - p.write(s.data(), s.size(), ec); + p.write(buffer(s), ec); if( expect(! ec)) { expect(p.method); @@ -214,7 +214,7 @@ public: "Content-Length: 1\r\n" "\r\n" "*"; - p.write(s.data(), s.size(), ec); + p.write(buffer(s), ec); if( expect(! ec)) { expect(p.reason); @@ -235,10 +235,11 @@ public: void parse(boost::string_ref const& m, F&& f) { + using boost::asio::buffer; { error_code ec; Parser p; - p.write(m.data(), m.size(), ec); + p.write(buffer(m.data(), m.size()), ec); if(expect(p.complete())) if(expect(! ec, ec.message())) f(p); @@ -247,7 +248,7 @@ public: { error_code ec; Parser p; - p.write(&m[0], i, ec); + p.write(buffer(&m[0], i), ec); if(! expect(! ec, ec.message())) continue; if(p.complete()) @@ -256,7 +257,7 @@ public: } else { - p.write(&m[i], m.size() - i, ec); + p.write(buffer(&m[i], m.size() - i), ec); if(! expect(! ec, ec.message())) continue; expect(p.complete()); @@ -271,10 +272,11 @@ public: void parse_ev(boost::string_ref const& m, parse_error ev) { + using boost::asio::buffer; { error_code ec; null_parser p; - p.write(m.data(), m.size(), ec); + p.write(buffer(m.data(), m.size()), ec); if(expect(! p.complete())) expect(ec == ev, ec.message()); } @@ -282,7 +284,7 @@ public: { error_code ec; null_parser p; - p.write(&m[0], i, ec); + p.write(buffer(&m[0], i), ec); if(! expect(! p.complete())) continue; if(ec) @@ -290,7 +292,7 @@ public: expect(ec == ev, ec.message()); continue; } - p.write(&m[i], m.size() - i, ec); + p.write(buffer(&m[i], m.size() - i), ec); if(! expect(! p.complete())) continue; if(! expect(ec == ev, ec.message())) @@ -449,11 +451,12 @@ public: void testUpgrade() { + using boost::asio::buffer; null_parser p; boost::string_ref s = "GET / HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n"; error_code ec; - p.write(s.data(), s.size(), ec); + p.write(buffer(s.data(), s.size()), ec); if(! expect(! ec, ec.message())) return; expect(p.complete()); @@ -481,6 +484,7 @@ public: void testRandomReq(std::size_t N) { + using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; message_fuzz mg; @@ -499,7 +503,7 @@ public: for(std::size_t j = 1; j < s.size() - 1; ++j) { error_code ec; - p.write(&s[0], j, ec); + p.write(buffer(&s[0], j), ec); if(! expect(! ec, ec.message())) { log << escaped_string(s); @@ -507,7 +511,7 @@ public: } if(! p.complete()) { - p.write(&s[j], s.size() - j, ec); + p.write(buffer(&s[j], s.size() - j), ec); if(! expect(! ec, ec.message())) { log << escaped_string(s); @@ -528,6 +532,7 @@ public: void testRandomResp(std::size_t N) { + using boost::asio::buffer; using boost::asio::buffer_cast; using boost::asio::buffer_size; message_fuzz mg; @@ -546,7 +551,7 @@ public: for(std::size_t j = 1; j < s.size() - 1; ++j) { error_code ec; - p.write(&s[0], j, ec); + p.write(buffer(&s[0], j), ec); if(! expect(! ec, ec.message())) { log << escaped_string(s); @@ -554,7 +559,7 @@ public: } if(! p.complete()) { - p.write(&s[j], s.size() - j, ec); + p.write(buffer(&s[j], s.size() - j), ec); if(! expect(! ec, ec.message())) { log << escaped_string(s); diff --git a/test/http/chunk_encode.cpp b/test/http/chunk_encode.cpp deleted file mode 100644 index 31ba7bc1..00000000 --- a/test/http/chunk_encode.cpp +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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. -*/ -//============================================================================== - -#include -#include -#include - -namespace beast { -namespace http { -namespace test { - -class chunk_encode_test : public beast::detail::unit_test::suite -{ -public: - // Convert CR LF to printables for display - static - std::string - encode (std::string const& s) - { - std::string result; - for(auto const c : s) - { - if (c == '\r') - result += "\\r"; - else if (c== '\n') - result += "\\n"; - else - result += c; - } - return result; - } - - // Print the contents of a ConstBufferSequence to the log - template - static - void - print (ConstBufferSequence const& buffers, Log log) - { - for(auto const& buf : buffers) - log << encode (std::string( - boost::asio::buffer_cast(buf), - boost::asio::buffer_size(buf))); - } - - // Convert a ConstBufferSequence to a string - template - static - std::string - buffer_to_string (ConstBufferSequence const& b) - { - std::string s; - auto const n = boost::asio::buffer_size(b); - s.resize(n); - boost::asio::buffer_copy( - boost::asio::buffer(&s[0], n), b); - return s; - } - - // Append a ConstBufferSequence to an existing string - template - static - void - buffer_append (std::string& s, ConstBufferSequence const& b) - { - s += buffer_to_string(b); - } - - // Convert the input sequence of the stream to a - // chunked-encoded string. The input sequence is consumed. - template - static - std::string - streambuf_to_string (Streambuf& sb, - bool final_chunk = false) - { - std::string s; - buffer_append(s, chunk_encode(sb.data(), final_chunk)); - return s; - } - - // Check an input against the correct chunk encoded version - void - check (std::string const& in, std::string const& answer, - bool final_chunk = true) - { - streambuf sb(3); - sb << in; - auto const out = streambuf_to_string (sb, final_chunk); - if (! expect (out == answer)) - log << "expected\n" << encode(answer) << - "\ngot\n" << encode(out); - } - - void testStreambuf() - { - streambuf sb(3); - std::string const s = - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789"; - sb << s; - expect(buffer_to_string(sb.data()) == s); - } - - void - testEncoder() - { - check("", "0\r\n\r\n"); - check("x", "1\r\nx\r\n0\r\n\r\n"); - check("abcd", "4\r\nabcd\r\n0\r\n\r\n"); - check("x", "1\r\nx\r\n", false); - check( - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789" - , - "d2\r\n" - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789" - "0123456789012345678901234567890123456789012345678901234567890123456789" - "\r\n" - "0\r\n\r\n"); - } - - void - run() - { - testStreambuf(); - testEncoder(); - } -}; - -BEAST_DEFINE_TESTSUITE(chunk_encode,http,beast); - -} // test -} // http -} // beast - diff --git a/test/http/message.cpp b/test/http/message.cpp index ee6aee61..f559f49d 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -151,8 +150,7 @@ public: streambuf rb; { - request req( - {beast::http::method_t::http_get, "/", 11}); + request req({"GET", "/", 11}); req.body = "Beast.HTTP"; req.headers.replace("Host", ep.address().to_string() + ":" + @@ -176,7 +174,7 @@ public: void run() override { - //testAsio(); + testAsio(); pass(); } }; diff --git a/test/http/nodejs_parser.hpp b/test/http/nodejs_parser.hpp index 44f13f09..4820a067 100644 --- a/test/http/nodejs_parser.hpp +++ b/test/http/nodejs_parser.hpp @@ -10,7 +10,6 @@ #include "nodejs-parser/http_parser.h" -#include #include #include #include @@ -75,62 +74,62 @@ make_nodejs_error(int http_errno) } inline -beast::http::method_t -convert_http_method(http_method m) +char const* +method_to_string(unsigned method) { using namespace beast; - switch (m) + switch(static_cast(method)) { - case HTTP_DELETE: return http::method_t::http_delete; - case HTTP_GET: return http::method_t::http_get; - case HTTP_HEAD: return http::method_t::http_head; - case HTTP_POST: return http::method_t::http_post; - case HTTP_PUT: return http::method_t::http_put; + case HTTP_DELETE: return "DELETE"; + case HTTP_GET: return "GET"; + case HTTP_HEAD: return "HEAD"; + case HTTP_POST: return "POST"; + case HTTP_PUT: return "PUT"; // pathological - case HTTP_CONNECT: return http::method_t::http_connect; - case HTTP_OPTIONS: return http::method_t::http_options; - case HTTP_TRACE: return http::method_t::http_trace; + case HTTP_CONNECT: return "CONNECT"; + case HTTP_OPTIONS: return "OPTIONS"; + case HTTP_TRACE: return "TRACE"; // webdav - case HTTP_COPY: return http::method_t::http_copy; - case HTTP_LOCK: return http::method_t::http_lock; - case HTTP_MKCOL: return http::method_t::http_mkcol; - case HTTP_MOVE: return http::method_t::http_move; - case HTTP_PROPFIND: return http::method_t::http_propfind; - case HTTP_PROPPATCH: return http::method_t::http_proppatch; - case HTTP_SEARCH: return http::method_t::http_search; - case HTTP_UNLOCK: return http::method_t::http_unlock; - case HTTP_BIND: return http::method_t::http_bind; - case HTTP_REBIND: return http::method_t::http_rebind; - case HTTP_UNBIND: return http::method_t::http_unbind; - case HTTP_ACL: return http::method_t::http_acl; + case HTTP_COPY: return "COPY"; + case HTTP_LOCK: return "LOCK"; + case HTTP_MKCOL: return "MKCOL"; + case HTTP_MOVE: return "MOVE"; + case HTTP_PROPFIND: return "PROPFIND"; + case HTTP_PROPPATCH: return "PROPPATCH"; + case HTTP_SEARCH: return "SEARCH"; + case HTTP_UNLOCK: return "UNLOCK"; + case HTTP_BIND: return "BIND"; + case HTTP_REBIND: return "REBIND"; + case HTTP_UNBIND: return "UNBIND"; + case HTTP_ACL: return "ACL"; // subversion - case HTTP_REPORT: return http::method_t::http_report; - case HTTP_MKACTIVITY: return http::method_t::http_mkactivity; - case HTTP_CHECKOUT: return http::method_t::http_checkout; - case HTTP_MERGE: return http::method_t::http_merge; + case HTTP_REPORT: return "REPORT"; + case HTTP_MKACTIVITY: return "MKACTIVITY"; + case HTTP_CHECKOUT: return "CHECKOUT"; + case HTTP_MERGE: return "MERGE"; // upnp - case HTTP_MSEARCH: return http::method_t::http_msearch; - case HTTP_NOTIFY: return http::method_t::http_notify; - case HTTP_SUBSCRIBE: return http::method_t::http_subscribe; - case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe; + case HTTP_MSEARCH: return "MSEARCH"; + case HTTP_NOTIFY: return "NOTIFY"; + case HTTP_SUBSCRIBE: return "SUBSCRIBE"; + case HTTP_UNSUBSCRIBE: return "UNSUBSCRIBE"; // RFC-5789 - case HTTP_PATCH: return http::method_t::http_patch; - case HTTP_PURGE: return http::method_t::http_purge; + case HTTP_PATCH: return "PATCH"; + case HTTP_PURGE: return "PURGE"; // CalDav - case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar; + case HTTP_MKCALENDAR: return "MKCALENDAR"; // RFC-2068, section 19.6.1.2 - case HTTP_LINK: return http::method_t::http_link; - case HTTP_UNLINK: return http::method_t::http_unlink; + case HTTP_LINK: return "LINK"; + case HTTP_UNLINK: return "UNLINK"; }; - return http::method_t::http_get; + return ""; } } // detail @@ -308,7 +307,7 @@ private: { template().on_request( - std::declval(), std::declval(), + std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval()), std::true_type{})> @@ -324,7 +323,7 @@ private: std::integral_constant::value>; void - call_on_request(method_t method, std::string url, + call_on_request(unsigned method, std::string url, int major, int minor, bool keep_alive, bool upgrade, std::true_type) { @@ -333,7 +332,7 @@ private: } void - call_on_request(method_t, std::string, int, int, bool, bool, + call_on_request(unsigned, std::string, int, int, bool, bool, std::false_type) { } @@ -687,10 +686,9 @@ nodejs_basic_parser::cb_headers_complete(http_parser* p) http_should_keep_alive(p) != 0; if(p->type == http_parser_type::HTTP_REQUEST) { - t.call_on_request(detail::convert_http_method( - http_method(p->method)), t.url_, - p->http_major, p->http_minor, keep_alive, - p->upgrade, has_on_request{}); + t.call_on_request(p->method, t.url_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_request{}); return 0; } return t.call_on_response(p->status_code, t.status_, @@ -807,18 +805,18 @@ private: } bool - on_request(http::method_t method, std::string const& url, + on_request(unsigned method, std::string const& url, int major, int minor, bool keep_alive, bool upgrade, std::true_type) { - m_.method = method; + m_.method = detail::method_to_string(method); m_.url = url; m_.version = major * 10 + minor; return true; } bool - on_request(http::method_t, std::string const&, + on_request(unsigned, std::string const&, int, int, bool, bool, std::false_type) { @@ -826,7 +824,7 @@ private: } bool - on_request(http::method_t method, std::string const& url, + on_request(unsigned method, std::string const& url, int major, int minor, bool keep_alive, bool upgrade) { return on_request(method, url, diff --git a/test/http/parser.cpp b/test/http/parser.cpp index e9ee2b70..895ef73e 100644 --- a/test/http/parser.cpp +++ b/test/http/parser.cpp @@ -20,6 +20,7 @@ class parser_test : public beast::detail::unit_test::suite public: void run() override { + using boost::asio::buffer; { error_code ec; parser #include -#include +#include #include #include #include @@ -74,8 +74,7 @@ public: error_code ec; p.write(sb.data(), ec); if(! expect(! ec, ec.message())) - log << debug::buffers_to_string( - sb.data()) << std::endl; + log << to_string(sb.data()) << std::endl; } } @@ -122,20 +121,20 @@ public: [&] { testParser>( + true, streambuf_body, headers>>( Repeat, creq_); testParser>( + false, streambuf_body, headers>>( Repeat, cres_); }); timedTest(Trials, "http::basic_parser", [&] { testParser>( + true, streambuf_body, headers>>( Repeat, creq_); testParser>( + false, streambuf_body, headers>>( Repeat, cres_); }); pass(); diff --git a/test/http/method.cpp b/test/http/status.cpp similarity index 89% rename from test/http/method.cpp rename to test/http/status.cpp index 9328428c..df270195 100644 --- a/test/http/method.cpp +++ b/test/http/status.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/http/type_check.cpp b/test/http/type_check.cpp new file mode 100644 index 00000000..82020206 --- /dev/null +++ b/test/http/type_check.cpp @@ -0,0 +1,9 @@ +// +// 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 diff --git a/test/http/write.cpp b/test/http/write.cpp index 3ea41773..9e4b9e41 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -7,3 +7,252 @@ // Test that header file is self-contained. #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class write_test : public beast::detail::unit_test::suite +{ +public: + struct string_SyncStream + { + std::string str; + + template + std::size_t + write_some(ConstBufferSequence const& buffers) + { + error_code ec; + auto const n = write_some(buffers, ec); + if(ec) + throw boost::system::system_error{ec}; + return n; + } + + template + std::size_t write_some( + ConstBufferSequence const& buffers, error_code& ec) + { + auto const n = buffer_size(buffers); + using boost::asio::buffer_size; + using boost::asio::buffer_cast; + str.reserve(str.size() + n); + for(auto const& buffer : buffers) + str.append(buffer_cast(buffer), + buffer_size(buffer)); + return n; + } + }; + + struct test_Body + { + using value_type = std::string; + + class writer + { + value_type const& body_; + + public: + template + explicit + writer(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(boost::asio::buffer(body_)); + return true; + } + }; + }; + + template + std::string + str(message const& m) + { + string_SyncStream ss; + write(ss, m); + return ss.str; + } + + void + testWrite() + { + // auto content-length HTTP/1.0 + { + message m{{ + "GET", "/", 10}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m); + expect(str(m) == + "GET / HTTP/1.0\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + ); + } + // keep-alive HTTP/1.0 + { + message m{{ + "GET", "/", 10}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m, connection::keep_alive); + expect(str(m) == + "GET / HTTP/1.0\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "Connection: keep-alive\r\n" + "\r\n" + "*" + ); + } + // upgrade HTTP/1.0 + { + message m{{ + "GET", "/", 10}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + try + { + prepare(m, connection::upgrade); + fail(); + } + catch(std::exception const&) + { + pass(); + } + } + // no content-length HTTP/1.0 + { + message m{{ + "GET", "/", 10}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m); + string_SyncStream ss; + error_code ec; + write(ss, m, ec); + expect(ec == boost::asio::error::eof); + expect(ss.str == + "GET / HTTP/1.0\r\n" + "User-Agent: test\r\n" + "\r\n" + "*" + ); + } + // auto content-length HTTP/1.1 + { + message m{{ + "GET", "/", 11}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m); + expect(str(m) == + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "\r\n" + "*" + ); + } + // close HTTP/1.1 + { + message m{{ + "GET", "/", 11}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m, connection::close); + string_SyncStream ss; + error_code ec; + write(ss, m, ec); + expect(ec == boost::asio::error::eof); + expect(ss.str == + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Content-Length: 1\r\n" + "Connection: close\r\n" + "\r\n" + "*" + ); + } + // upgrade HTTP/1.1 + { + message m{{ + "GET", "/", 11}}; + m.headers.insert("User-Agent", "test"); + prepare(m, connection::upgrade); + expect(str(m) == + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Connection: upgrade\r\n" + "\r\n" + ); + } + // no content-length HTTP/1.1 + { + message m{{ + "GET", "/", 11}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m); + string_SyncStream ss; + error_code ec; + write(ss, m, ec); + expect(ss.str == + "GET / HTTP/1.1\r\n" + "User-Agent: test\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "*\r\n" + "0\r\n\r\n" + ); + } + } + + void testConvert() + { + message m{{ + "GET", "/", 11}}; + m.headers.insert("User-Agent", "test"); + m.body = "*"; + prepare(m); + expect(boost::lexical_cast(m) == + "GET / HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: 1\r\n\r\n*"); + } + + void run() override + { + testWrite(); + testConvert(); + } +}; + +BEAST_DEFINE_TESTSUITE(write,http,beast); + +} // http +} // beast diff --git a/test/main.cpp b/test/main.cpp index 22efdfcc..abc6bb99 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -17,26 +17,77 @@ */ //============================================================================== +#include +#include +#include #include #include #include +#include +#include +#include #ifdef _MSC_VER # ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN -#include +# include # undef WIN32_LEAN_AND_MEAN # else -#include +# include # endif #endif #include +namespace beast { +namespace detail { +namespace unit_test { + +std::string +prefix(suite_info const& s) +{ + if (s.manual()) + return "|M| "; + return " "; +} + +template +void +print(Log& log, suite_list const& c) +{ + std::size_t manual = 0; + for(auto const& s : c) + { + log << + prefix (s) << + s.full_name(); + if(s.manual()) + ++manual; + } + log << + amount(c.size(), "suite") << " total, " << + amount(manual, "manual suite") + ; +} + +template +void +print(Log& log) +{ + log << "------------------------------------------"; + print(log, global_suites()); + log << "------------------------------------------"; +} + +} // unit_test +} // detail +} // beast + // Simple main used to produce stand // alone executables that run unit tests. -int main() +int main(int ac, char const* av[]) { + using namespace std; using namespace beast::detail::unit_test; #ifdef _MSC_VER @@ -47,10 +98,41 @@ int main() } #endif + namespace po = boost::program_options; + po::options_description desc("Options"); + desc.add_options() + ("help,h", "Produce a help message") + ("print,r", "Print the list of available test suites") + ("suites,s", po::value(), "suites to run") + ; + + po::positional_options_description p; + po::variables_map vm; + po::store(po::parse_command_line(ac, av, desc), vm); + po::notify(vm); + + beast::detail::debug_ostream log; + + if(vm.count("help")) { - beast::detail::debug_ostream s; - reporter r (s); - bool failed (r.run_each (global_suites())); + log << desc; + } + else if(vm.count("print")) + { + print(log); + } + else + { + std::string suites; + if(vm.count("suites") > 0) + suites = vm["suites"].as(); + reporter r(log); + bool failed; + if(! suites.empty()) + failed = r.run_each_if(global_suites(), + match_auto(suites)); + else + failed = r.run_each(global_suites()); if (failed) return EXIT_FAILURE; return EXIT_SUCCESS; diff --git a/test/static_streambuf.cpp b/test/static_streambuf.cpp index 22c7bcc2..46e68aa8 100644 --- a/test/static_streambuf.cpp +++ b/test/static_streambuf.cpp @@ -52,40 +52,81 @@ public: { std::memset(buf, 0, sizeof(buf)); static_streambuf_n ba; - decltype(ba)::mutable_buffers_type d; - d = ba.prepare(z); expect(buffer_size(d) == z); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(y); expect(buffer_size(d) == y); - d = ba.prepare(x); expect(buffer_size(d) == x); - ba.commit(buffer_copy(d, buffer(s.data(), x))); + { + auto d = ba.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + ba.commit(buffer_copy(d, buffer(s.data(), x))); + } expect(ba.size() == x); expect(buffer_size(ba.data()) == ba.size()); - d = ba.prepare(x); expect(buffer_size(d) == x); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(z); expect(buffer_size(d) == z); - d = ba.prepare(y); expect(buffer_size(d) == y); - ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); + expect(buffer_size(d) == z); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + ba.commit(buffer_copy(d, buffer(s.data()+x, y))); + } ba.commit(1); expect(ba.size() == x + y); expect(buffer_size(ba.data()) == ba.size()); - d = ba.prepare(x); expect(buffer_size(d) == x); - d = ba.prepare(y); expect(buffer_size(d) == y); - d = ba.prepare(0); expect(buffer_size(d) == 0); - d = ba.prepare(z); expect(buffer_size(d) == z); - ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + { + auto d = ba.prepare(x); + expect(buffer_size(d) == x); + } + { + auto d = ba.prepare(y); + expect(buffer_size(d) == y); + } + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } + { + auto d = ba.prepare(z); + expect(buffer_size(d) == z); + ba.commit(buffer_copy(d, buffer(s.data()+x+y, z))); + } ba.commit(2); expect(ba.size() == x + y + z); expect(buffer_size(ba.data()) == ba.size()); expect(to_string(ba.data()) == s); ba.consume(t); - d = ba.prepare(0); expect(buffer_size(d) == 0); + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } expect(to_string(ba.data()) == s.substr(t, std::string::npos)); ba.consume(u); expect(to_string(ba.data()) == s.substr(t + u, std::string::npos)); ba.consume(v); expect(to_string(ba.data()) == ""); ba.consume(1); - d = ba.prepare(0); expect(buffer_size(d) == 0); + { + auto d = ba.prepare(0); + expect(buffer_size(d) == 0); + } try { ba.prepare(1); diff --git a/test/buffers_debug.cpp b/test/to_string.cpp similarity index 88% rename from test/buffers_debug.cpp rename to test/to_string.cpp index cd420b86..7e0981fb 100644 --- a/test/buffers_debug.cpp +++ b/test/to_string.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include