Improvements to increase code coverage:

* Don't include the test code in coverage reports
* Add test code for missing coverage

Other:

* Improve the README.md
* Fix warning in sha1_context
* Tidy up the examples use of namespaces
* Various fixes to documentation and javadocs
This commit is contained in:
Vinnie Falco
2016-05-04 17:27:50 -04:00
parent 231988622c
commit 133e7a3b16
49 changed files with 1054 additions and 447 deletions

View File

@@ -1,4 +1,8 @@
# Beast [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov](https://codecov.io/gh/sublimator/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/sublimator/Beast)
# Beast
[![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov]
(https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![Documentation]
(https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/)
Beast provides implementations of the HTTP and WebSocket protocols
built on top of Boost.Asio and other parts of boost.
@@ -9,10 +13,69 @@ Requirements:
* C++11 or greater
* OpenSSL (optional)
Linking applications with beast:
Example WebSocket program:
```C++
#include <beast/to_string.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
You need to include the .cpp file `src/beast_http_nodejs_parser.cpp`
in the build script or Makefile for your program in order to link.
int main()
{
// Normal boost::asio setup
std::string const host = "echo.websocket.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r(ios);
boost::asio::ip::tcp::socket sock(ios);
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
// WebSocket connect and send message using beast
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
ws.handshake(host, "/");
ws.write(boost::asio::buffer("Hello, world!"));
// Receive WebSocket message, print and close using beast
beast::streambuf sb;
beast::websocket::opcode op;
ws.read(op, sb);
ws.close(beast::websocket::close_code::normal);
std::cout << to_string(sb.data()) << "\n";
}
```
Example HTTP program:
```C++
#include <beast/http.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
int main()
{
// Normal boost::asio setup
std::string const host = "boost.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r(ios);
boost::asio::ip::tcp::socket sock(ios);
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
// Send HTTP request using beast
beast::http::request_v1<beast::http::empty_body> req({"GET", "/", 11});
req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port()));
req.headers.replace("User-Agent", "Beast");
beast::http::prepare(req);
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::streambuf sb;
beast::http::response_v1<beast::http::streambuf_body> resp;
beast::http::read(sock, sb, resp);
std::cout << resp;
}
```
Links:

View File

@@ -2,6 +2,7 @@
General:
* Use SFINAE on return values (search for "class =")
* Remove http,websocket error_code types and use the one in <beast/error.hpp>
Boost.Http
* Use enum instead of bool in isRequest
@@ -17,6 +18,8 @@ Docs:
- See if we can include them now that xsl is fixed
* Implement cleanup-param to remove spaces around template arguments
e.g. in basic_streambuf move constructor members
* Don't put using namespace at file scope in examples,
do something like "using ba = boost::asio" instead.
Core:
* Replace Jamroot with Jamfile
@@ -26,13 +29,13 @@ Core:
WebSocket:
* optimized versions of key/masking, choose prepared_key size
* invokable unit test
* Finish up all of static_string including tests
* Don't rely on default constructible mutable buffers
type in read_frame_op (smb_type). To see the error, use
boost::asio::streambuf in websocket_async_echo_peer
* Don't try to read requests into empty_body
HTTP:
* Define Parser concept in HTTP
- Need parse version of read() so caller can set parser options
like maximum size of headers, maximum body size, etc
* trim public interface of rfc2616.h to essentials only
* add bool should_close(message_v1 const&) to replace the use
of eof return value from write and async_write
@@ -40,7 +43,7 @@ HTTP:
* More fine grained parser errors
* HTTP parser size limit with test (configurable?)
* HTTP parser trailers with test
* URL parser, strong URL checking in HTTP parser
* URL parser, strong URL character checking in HTTP parser
* Update for rfc7230
* Consider rename to MessageBody concept
* Fix prepare() calling content_length() without init()
@@ -48,3 +51,7 @@ HTTP:
* Complete allocator testing in basic_streambuf, basic_headers
* Fix http::async_write op, case 3 should break at the end.
* Add tests for writer using the resume function / coros
* Custom HTTP error codes for various situations
* Make empty_body write-only, remove reader nested type
* Add concepts WritableBody ReadableBody with type checks,
check them in read and write functions

View File

@@ -112,19 +112,17 @@ int main()
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
using namespace beast::http;
// Send HTTP request using beast
request<empty_body> req({"GET", "/", 11});
beast::http::request_v1<beast::http::empty_body> 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);
beast::http::prepare(req);
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::streambuf sb;
response<streambuf_body> resp;
read(sock, sb, resp);
beast::http::response_v1<beast::http::streambuf_body> resp;
beast::http::read(sock, sb, resp);
std::cout << resp;
}
```
@@ -147,18 +145,16 @@ int main()
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
using namespace beast::websocket;
// WebSocket connect and send message using beast
stream<boost::asio::ip::tcp::socket&> ws(sock);
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
ws.handshake(host, "/");
ws.write(boost::asio::buffer("Hello, world!"));
// Receive WebSocket message, print and close using beast
beast::streambuf sb;
opcode op;
beast::websocket::opcode op;
ws.read(op, sb);
ws.close(close_code::normal);
ws.close(beast::websocket::close_code::normal);
std::cout << to_string(sb.data()) << "\n";
}
```

View File

@@ -44,7 +44,7 @@ libraries. It is not designed to be end-user facing. There is no convenient
class that implements the core of a web server, nor is there a convenient
class to quickly perform common operations such as fetching a file or
connecting and retrieving a document from a secure connection. These
use-cases are important. But this library does not try to do that. Instead,
use-cases are important, but this library does not try to do that. Instead,
it offers primitives that can be used to build those user-facing algorithms.
A HTTP message (referred to hereafter as "message") contains request or

View File

@@ -28,7 +28,7 @@ using namespace boost::asio;
template<class String>
void
err(error_code const& ec, String const& what)
err(beast::error_code const& ec, String const& what)
{
std::cerr << what << ": " << ec.message() << std::endl;
}

View File

@@ -20,18 +20,16 @@ int main()
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "http"}));
using namespace beast::http;
// Send HTTP request using beast
request_v1<empty_body> req({"GET", "/", 11});
beast::http::request_v1<beast::http::empty_body> 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);
beast::http::prepare(req);
beast::http::write(sock, req);
// Receive and print HTTP response using beast
beast::streambuf sb;
response_v1<streambuf_body> resp;
read(sock, sb, resp);
beast::http::response_v1<beast::http::streambuf_body> resp;
beast::http::read(sock, sb, resp);
std::cout << resp;
}

View File

@@ -21,17 +21,15 @@ int main()
boost::asio::connect(sock,
r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
using namespace beast::websocket;
// WebSocket connect and send message using beast
stream<boost::asio::ip::tcp::socket&> ws(sock);
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
ws.handshake(host, "/");
ws.write(boost::asio::buffer("Hello, world!"));
// Receive WebSocket message, print and close using beast
beast::streambuf sb;
opcode op;
beast::websocket::opcode op;
ws.read(op, sb);
ws.close(close_code::normal);
ws.close(beast::websocket::close_code::normal);
std::cout << to_string(sb.data()) << "\n";
}

View File

@@ -31,7 +31,6 @@ namespace beast {
template<class MutableBufferSequence>
class buffers_adapter
{
private:
static_assert(is_MutableBufferSequence<MutableBufferSequence>::value,
"MutableBufferSequence requirements not met");

View File

@@ -5,18 +5,20 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_HTTP_ERROR_HPP
#define BEAST_HTTP_ERROR_HPP
#ifndef BEAST_ERROR_HPP
#define BEAST_ERROR_HPP
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
namespace beast {
namespace http {
/// The type of error code used by the library
using error_code = boost::system::error_code;
} // http
/// The type of system error thrown by the library
using system_error = boost::system::system_error;
} // beast
#endif

View File

@@ -12,7 +12,6 @@
#include <beast/http/basic_parser_v1.hpp>
#include <beast/http/body_type.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/error.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/message.hpp>
#include <beast/http/message_v1.hpp>

View File

@@ -11,7 +11,7 @@
// Convenience header to include everything
// needed when declarating a user defined Body type.
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <beast/http/message.hpp>
#include <beast/http/resume_context.hpp>
#include <boost/logic/tribool.hpp>

View File

@@ -149,7 +149,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_req_url_start:
if(ch == ' ')
return err(parse_error::bad_uri);
// VFALCO TODO Require valid URL character
// VFALCO TODO Better checking for valid URL characters
if(! is_text(ch))
return err(parse_error::bad_uri);
if(cb(&self::call_on_uri))
return used();
s_ = s_req_url;
@@ -163,7 +165,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
s_ = s_req_http_start;
break;
}
// VFALCO TODO Require valid URL character
// VFALCO TODO Better checking for valid URL characters
if(! is_text(ch))
return err(parse_error::bad_uri);
break;
case s_req_http_start:

View File

@@ -212,6 +212,18 @@ operator()(error_code ec, std::size_t bytes_transferred, bool again)
//------------------------------------------------------------------------------
template<class SyncReadStream, class Streambuf,
bool isRequest, class Body, class Headers>
void
read(SyncReadStream& stream, Streambuf& streambuf,
message_v1<isRequest, Body, Headers>& msg)
{
error_code ec;
read(stream, streambuf, msg, ec);
if(ec)
throw boost::system::system_error{ec};
}
template<class SyncReadStream, class Streambuf,
bool isRequest, class Body, class Headers>
void

View File

@@ -8,8 +8,7 @@
#ifndef BEAST_HTTP_PARSE_ERROR_HPP
#define BEAST_HTTP_PARSE_ERROR_HPP
#include <beast/http/error.hpp>
#include <boost/system/error_code.hpp>
#include <beast/error.hpp>
namespace beast {
namespace http {

View File

@@ -8,8 +8,8 @@
#ifndef BEAST_HTTP_PARSER_V1_HPP
#define BEAST_HTTP_PARSER_V1_HPP
#include <beast/error.hpp>
#include <beast/http/basic_parser_v1.hpp>
#include <beast/http/error.hpp>
#include <beast/http/message_v1.hpp>
#include <boost/optional.hpp>
#include <functional>

View File

@@ -8,7 +8,7 @@
#ifndef BEAST_HTTP_READ_HPP
#define BEAST_HTTP_READ_HPP
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <beast/http/parser_v1.hpp>
#include <beast/async_completion.hpp>
#include <boost/asio/buffer.hpp>
@@ -47,13 +47,7 @@ template<class SyncReadStream, class Streambuf,
bool isRequest, class Body, class Headers>
void
read(SyncReadStream& stream, Streambuf& streambuf,
message_v1<isRequest, Body, Headers>& msg)
{
error_code ec;
read(stream, streambuf, msg, ec);
if(ec)
throw boost::system::system_error{ec};
}
message_v1<isRequest, Body, Headers>& msg);
/** Read a HTTP/1 message from a stream.

View File

@@ -8,7 +8,7 @@
#ifndef BEAST_HTTP_TYPE_CHECK_HPP
#define BEAST_HTTP_TYPE_CHECK_HPP
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <boost/asio/buffer.hpp>
#include <type_traits>
#include <utility>

View File

@@ -8,7 +8,7 @@
#ifndef BEAST_HTTP_WRITE_HPP
#define BEAST_HTTP_WRITE_HPP
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <beast/http/message_v1.hpp>
#include <beast/async_completion.hpp>
#include <boost/system/error_code.hpp>
@@ -33,7 +33,7 @@ namespace http {
The implementation will automatically perform chunk encoding if
the contents of the message indicate that chunk encoding is required.
If the semantics of the message indicate that the connection should
be closed after the message is sent, the error returns from this
be closed after the message is sent, the error thrown from this
function will be `boost::asio::error::eof`.
@param stream The stream to which the data is to be written.
@@ -64,7 +64,7 @@ write(SyncWriteStream& stream,
The implementation will automatically perform chunk encoding if
the contents of the message indicate that chunk encoding is required.
If the semantics of the message indicate that the connection should
be closed after the message is sent, the error returns from this
be closed after the message is sent, the error returned from this
function will be `boost::asio::error::eof`.
@param stream The stream to which the data is to be written.

View File

@@ -582,7 +582,7 @@ basic_streambuf<Allocator>::prepare(size_type n) ->
list_.push_back(e);
if(out_ == list_.end())
out_ = list_.iterator_to(e);
if(n > e.size())
if(n >= e.size())
{
out_end_ = e.size();
n -= e.size();

View File

@@ -469,49 +469,32 @@ template<class MutableBufferSequence>
void
buffers_adapter<MutableBufferSequence>::consume(std::size_t n)
{
for(;;)
using boost::asio::buffer_size;
while(begin_ != out_)
{
if(begin_ != out_)
auto const avail =
buffer_size(*begin_) - in_pos_;
if(n < avail)
{
auto const avail =
buffer_size(*begin_) - in_pos_;
if(n < avail)
{
in_size_ -= n;
in_pos_ += n;
break;
}
n -= avail;
in_size_ -= avail;
in_pos_ = 0;
++begin_;
}
else
{
auto const avail = out_pos_ - in_pos_;
if(n < avail)
{
in_size_ -= n;
in_pos_ += n;
}
else
{
in_size_ -= avail;
if(out_pos_ != out_end_||
out_ != std::prev(bs_.end()))
{
in_pos_ = out_pos_;
}
else
{
// Use the whole buffer now.
in_pos_ = 0;
out_pos_ = 0;
out_end_ = 0;
}
}
break;
in_size_ -= n;
in_pos_ += n;
return;
}
n -= avail;
in_size_ -= avail;
in_pos_ = 0;
++begin_;
}
auto const avail = out_pos_ - in_pos_;
if(n < avail)
{
in_size_ -= n;
in_pos_ += n;
}
else
{
in_size_ -= avail;
in_pos_ = out_pos_;
}
}

View File

@@ -279,13 +279,6 @@ public:
return N;
}
/// Reduces memory usage by freeing unused memory.
void
shrink_to_fit()
{
// no-op
}
/// Clears the contents.
void
clear()
@@ -310,7 +303,8 @@ public:
/// Compare two character sequences.
template<std::size_t M>
int compare(static_string<M, CharT, Traits> const& rhs) const;
int
compare(static_string<M, CharT, Traits> const& rhs) const;
/// Return the characters as a `basic_string`.
std::basic_string<CharT, Traits>
@@ -488,7 +482,8 @@ assign(CharT const* s)
namespace detail {
template<std::size_t N, std::size_t M, class CharT, class Traits>
int compare(
int
compare(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -512,27 +507,13 @@ int compare(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
int compare(
inline
int
compare(
const CharT (&s)[M],
static_string<N, CharT, Traits> const& rhs)
{
if(M-1 < rhs.size())
{
auto const v = Traits::compare(
&s[0], rhs.data(), M-1);
if(v == 0)
return -1;
return v;
}
else if(M-1 > rhs.size())
{
auto const v = Traits::compare(
&s[0], rhs.data(), rhs.size());
if(v == 0)
return 1;
return v;
}
return Traits::compare(&s[0], rhs.data(), M-1);
return -compare(rhs, s);
}
} // detail
@@ -540,7 +521,8 @@ int compare(
#if ! GENERATING_DOCS
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator==(
bool
operator==(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -548,7 +530,8 @@ bool operator==(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator!=(
bool
operator!=(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -556,7 +539,8 @@ bool operator!=(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator<(
bool
operator<(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -564,7 +548,8 @@ bool operator<(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator<=(
bool
operator<=(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -572,7 +557,8 @@ bool operator<=(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator>(
bool
operator>(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -580,7 +566,8 @@ bool operator>(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator>=(
bool
operator>=(
static_string<N, CharT, Traits> const& lhs,
static_string<M, CharT, Traits> const& rhs)
{
@@ -590,7 +577,8 @@ bool operator>=(
//---
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator==(
bool
operator==(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -598,7 +586,8 @@ bool operator==(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator==(
bool
operator==(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -606,7 +595,8 @@ bool operator==(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator!=(
bool
operator!=(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -614,7 +604,8 @@ bool operator!=(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator!=(
bool
operator!=(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -622,7 +613,8 @@ bool operator!=(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator<(
bool
operator<(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -630,7 +622,8 @@ bool operator<(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator<(
bool
operator<(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -638,7 +631,8 @@ bool operator<(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator<=(
bool
operator<=(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -646,7 +640,8 @@ bool operator<=(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator<=(
bool
operator<=(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -654,7 +649,8 @@ bool operator<=(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator>(
bool
operator>(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -662,7 +658,8 @@ bool operator>(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator>(
bool
operator>(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{
@@ -670,7 +667,8 @@ bool operator>(
}
template<std::size_t N, std::size_t M, class CharT, class Traits>
bool operator>=(
bool
operator>=(
const CharT (&s)[N],
static_string<M, CharT, Traits> const& rhs)
{
@@ -678,7 +676,8 @@ bool operator>=(
}
template<std::size_t N, class CharT, class Traits, std::size_t M>
bool operator>=(
bool
operator>=(
static_string<N, CharT, Traits> const& lhs,
const CharT (&s)[M])
{

View File

@@ -127,7 +127,7 @@ ror(T t, unsigned n = 1)
static_cast<typename std::make_unsigned<T>::type>(t) >> n));
}
// 32-bit Uuoptimized
// 32-bit Unoptimized
//
template<class = void>
void

View File

@@ -8,15 +8,11 @@
#ifndef BEAST_WEBSOCKET_ERROR_HPP
#define BEAST_WEBSOCKET_ERROR_HPP
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <beast/error.hpp>
namespace beast {
namespace websocket {
/// The type of error used by functions and completion handlers.
using error_code = boost::system::error_code;
/// Error codes returned from @ref stream operations.
enum class error
{

View File

@@ -617,7 +617,7 @@ public:
If the passed HTTP request is a valid HTTP WebSocket Upgrade
request, a HTTP response is sent back indicating a successful
upgrade. When this asynchronous operaiton completes, the stream is
upgrade. When this asynchronous operation completes, the stream is
then ready to send and receive WebSocket protocol frames and messages.
If the HTTP request is invalid or cannot be satisfied, a HTTP

View File

@@ -136,7 +136,7 @@ namespace websocket_helpers {
template<class Socket>
inline
void
call_teardown(Socket& socket, websocket::error_code& ec)
call_teardown(Socket& socket, error_code& ec)
{
using websocket::teardown;
teardown(socket, ec);

View File

@@ -13,6 +13,7 @@ add_executable (core-tests
buffer_concepts.cpp
buffers_adapter.cpp
consuming_buffers.cpp
error.cpp
handler_alloc.cpp
handler_concepts.cpp
http.cpp
@@ -38,12 +39,14 @@ endif()
add_executable (http-tests
${BEAST_INCLUDES}
fail_stream.hpp
string_stream.hpp
yield_to.hpp
main.cpp
http/basic_headers.cpp
http/basic_parser_v1.cpp
http/body_type.cpp
http/empty_body.cpp
http/error.cpp
http/headers.cpp
http/message.cpp
http/message_v1.cpp
@@ -79,6 +82,8 @@ endif()
add_executable (websocket-tests
${BEAST_INCLUDES}
fail_stream.hpp
string_stream.hpp
yield_to.hpp
websocket/websocket_async_echo_peer.hpp
websocket/websocket_sync_echo_peer.hpp
main.cpp

View File

@@ -16,6 +16,7 @@ unit-test core-tests :
buffer_concepts.cpp
buffers_adapter.cpp
consuming_buffers.cpp
error.cpp
handler_alloc.cpp
handler_concepts.cpp
http.cpp
@@ -41,7 +42,6 @@ unit-test http-tests :
http/basic_parser_v1.cpp
http/body_type.cpp
http/empty_body.cpp
http/error.cpp
http/headers.cpp
http/message.cpp
http/message_v1.cpp

View File

@@ -9,8 +9,10 @@
#include <beast/basic_streambuf.hpp>
#include <beast/streambuf.hpp>
#include <beast/to_string.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp>
#include <algorithm>
#include <atomic>
#include <memory>
#include <string>
@@ -137,29 +139,116 @@ public:
class basic_streambuf_test : public beast::detail::unit_test::suite
{
public:
template<class ConstBufferSequence>
template<class Alloc1, class Alloc2>
static
std::string
to_string(ConstBufferSequence const& bs)
bool
eq(basic_streambuf<Alloc1> const& sb1,
basic_streambuf<Alloc2> const& sb2)
{
return to_string(sb1.data()) == to_string(sb2.data());
}
void testSpecialMembers()
{
using boost::asio::buffer;
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<char const*>(b),
buffer_size(b));
return s;
std::string const s = "Hello, world";
expect(s.size() == 12);
for(std::size_t i = 1; i < 12; ++i) {
for(std::size_t x = 1; x < 4; ++x) {
for(std::size_t y = 1; y < 4; ++y) {
std::size_t z = s.size() - (x + y);
{
streambuf sb(i);
sb.commit(buffer_copy(sb.prepare(x), buffer(s.data(), x)));
sb.commit(buffer_copy(sb.prepare(y), buffer(s.data()+x, y)));
sb.commit(buffer_copy(sb.prepare(z), buffer(s.data()+x+y, z)));
expect(to_string(sb.data()) == s);
{
streambuf sb2(sb);
expect(eq(sb, sb2));
}
{
streambuf sb2;
sb2 = sb;
expect(eq(sb, sb2));
}
{
streambuf sb2(std::move(sb));
expect(to_string(sb2.data()) == s);
expect(buffer_size(sb.data()) == 0);
sb = std::move(sb2);
expect(to_string(sb.data()) == s);
expect(buffer_size(sb2.data()) == 0);
}
sb = sb;
sb = std::move(sb);
}
}}}
}
void testAllocator()
{
{
using alloc_type =
test_allocator<char, false, false, false, false>;
using sb_type = basic_streambuf<alloc_type>;
sb_type sb;
expect(sb.get_allocator().id() == 1);
}
{
using alloc_type =
test_allocator<char, false, false, false, false>;
using sb_type = basic_streambuf<alloc_type>;
sb_type sb;
expect(sb.get_allocator().id() == 2);
sb_type sb2(sb);
expect(sb2.get_allocator().id() == 2);
sb_type sb3(sb, alloc_type{});
//expect(sb3.get_allocator().id() == 3);
}
}
void
testPrepare()
{
using boost::asio::buffer_size;
{
streambuf sb(2);
expect(buffer_size(sb.prepare(5)) == 5);
expect(buffer_size(sb.prepare(8)) == 8);
expect(buffer_size(sb.prepare(7)) == 7);
}
{
streambuf sb(2);
sb.prepare(2);
{
auto const bs = sb.prepare(5);
expect(std::distance(
bs.begin(), bs.end()) == 2);
}
{
auto const bs = sb.prepare(8);
expect(std::distance(
bs.begin(), bs.end()) == 3);
}
{
auto const bs = sb.prepare(4);
expect(std::distance(
bs.begin(), bs.end()) == 2);
}
}
}
void testCommit()
{
using boost::asio::buffer_size;
streambuf sb(2);
expect(buffer_size(sb.prepare(5)) == 5);
expect(buffer_size(sb.prepare(8)) == 8);
expect(buffer_size(sb.prepare(7)) == 7);
sb.prepare(2);
sb.prepare(5);
sb.commit(1);
expect(buffer_size(sb.data()) == 1);
}
void testStreambuf()
@@ -257,88 +346,67 @@ public:
}}}}}
}
template<class Alloc1, class Alloc2>
static
bool
eq(basic_streambuf<Alloc1> const& sb1,
basic_streambuf<Alloc2> const& sb2)
void testIterators()
{
return to_string(sb1.data()) == to_string(sb2.data());
}
void testSpecial()
{
using boost::asio::buffer;
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
std::string const s = "Hello, world";
expect(s.size() == 12);
for(std::size_t i = 1; i < 12; ++i) {
for(std::size_t x = 1; x < 4; ++x) {
for(std::size_t y = 1; y < 4; ++y) {
std::size_t z = s.size() - (x + y);
streambuf sb(1);
sb.prepare(1);
sb.commit(1);
sb.prepare(2);
sb.commit(2);
expect(buffer_size(sb.data()) == 3);
sb.prepare(1);
expect(buffer_size(sb.prepare(3)) == 3);
expect(read_size_helper(sb, 3) == 3);
sb.commit(2);
try
{
streambuf sb(i);
sb.commit(buffer_copy(sb.prepare(x), buffer(s.data(), x)));
sb.commit(buffer_copy(sb.prepare(y), buffer(s.data()+x, y)));
sb.commit(buffer_copy(sb.prepare(z), buffer(s.data()+x+y, z)));
expect(to_string(sb.data()) == s);
{
streambuf sb2(sb);
expect(eq(sb, sb2));
}
{
streambuf sb2;
sb2 = sb;
expect(eq(sb, sb2));
}
{
streambuf sb2(std::move(sb));
expect(to_string(sb2.data()) == s);
expect(buffer_size(sb.data()) == 0);
sb = std::move(sb2);
expect(to_string(sb.data()) == s);
expect(buffer_size(sb2.data()) == 0);
}
streambuf sb0(0);
fail();
}
}}}
catch(std::exception const&)
{
pass();
}
std::size_t n;
n = 0;
for(auto it = sb.data().begin();
it != sb.data().end(); it++)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().begin();
it != sb.data().end(); ++it)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().end();
it != sb.data().begin(); it--)
++n;
expect(n == 4);
n = 0;
for(auto it = sb.data().end();
it != sb.data().begin(); --it)
++n;
expect(n == 4);
}
void testAllocator()
{
{
using alloc_type =
test_allocator<char, false, false, false, false>;
using sb_type = basic_streambuf<alloc_type>;
sb_type sb;
expect(sb.get_allocator().id() == 1);
}
{
using alloc_type =
test_allocator<char, false, false, false, false>;
using sb_type = basic_streambuf<alloc_type>;
sb_type sb;
expect(sb.get_allocator().id() == 2);
sb_type sb2(sb);
expect(sb2.get_allocator().id() == 2);
sb_type sb3(sb, alloc_type{});
//expect(sb3.get_allocator().id() == 3);
}
}
void testStream()
void testOutputStream()
{
streambuf sb;
sb << "x";
expect(to_string(sb.data()) == "x");
}
void run() override
{
testPrepare();
testStreambuf();
testSpecial();
testSpecialMembers();
testAllocator();
testStream();
testPrepare();
testCommit();
testStreambuf();
testIterators();
testOutputStream();
}
};

View File

@@ -68,9 +68,64 @@ public:
}
}
void testIterators()
{
using boost::asio::buffer_size;
using boost::asio::const_buffer;
char buf[9];
std::vector<const_buffer> b1{
const_buffer{buf+0, 1},
const_buffer{buf+1, 2}};
std::array<const_buffer, 3> b2{{
const_buffer{buf+3, 1},
const_buffer{buf+4, 2},
const_buffer{buf+6, 3}}};
auto bs = buffer_cat(b1, b2);
try
{
std::size_t n = 0;
for(auto it = bs.begin(); n < 100; ++it)
++n;
fail();
}
catch(std::exception const&)
{
pass();
}
try
{
std::size_t n = 0;
for(auto it = bs.end(); n < 100; --it)
++n;
fail();
}
catch(std::exception const&)
{
pass();
}
try
{
expect((buffer_size(*bs.end()) == 0, false));
}
catch(std::exception const&)
{
pass();
}
auto bs2 = bs;
expect(bs.begin() != bs2.begin());
expect(bs.end() != bs2.end());
decltype(bs)::const_iterator it;
decltype(bs2)::const_iterator it2;
expect(it == it2);
}
void run() override
{
testBufferCat();
testIterators();
}
};

View File

@@ -8,6 +8,7 @@
// Test that header file is self-contained.
#include <beast/buffers_adapter.hpp>
#include <beast/streambuf.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/streambuf.hpp>
@@ -149,9 +150,38 @@ public:
}
}}}}}}
}
void testCommit()
{
using boost::asio::buffer_size;
{
using sb_type = boost::asio::streambuf;
sb_type sb;
buffers_adapter<
sb_type::mutable_buffers_type> ba(sb.prepare(3));
expect(buffer_size(ba.prepare(3)) == 3);
ba.commit(2);
expect(buffer_size(ba.data()) == 2);
}
{
using sb_type = beast::streambuf;
sb_type sb(2);
sb.prepare(3);
buffers_adapter<
sb_type::mutable_buffers_type> ba(sb.prepare(8));
expect(buffer_size(ba.prepare(8)) == 8);
ba.commit(2);
expect(buffer_size(ba.data()) == 2);
ba.consume(1);
ba.commit(6);
ba.consume(2);
expect(buffer_size(ba.data()) == 5);
ba.consume(5);
}
}
void run() override
{
testBuffersAdapter();
testCommit();
}
};

View File

@@ -81,10 +81,22 @@ public:
expect(buffer_copy(cb2, cb) == 0);
}
void testIterator()
{
using boost::asio::const_buffer;
std::array<const_buffer, 3> ba;
consuming_buffers<decltype(ba)> cb(ba);
std::size_t n = 0;
for(auto it = cb.end(); it != cb.begin(); --it)
++n;
expect(n == 3);
}
void run() override
{
testBuffers();
testNullBuffers();
testIterator();
}
};

View File

@@ -7,7 +7,6 @@
#include <beast/detail/sha1.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/algorithm/hex.hpp>
#include <array>
namespace beast {
@@ -16,20 +15,45 @@ namespace detail {
class sha1_test : public beast::detail::unit_test::suite
{
public:
static
inline
std::uint8_t
unhex(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
if(c >= 'A' && c <= 'F')
return c - 'A' + 10;
throw std::invalid_argument("not a hex digit");
}
static
std::string
unhex(std::string const& in)
{
std::string out;
out.reserve(in.size() / 2);
if(in.size() % 2)
throw std::domain_error("invalid hex string");
for(std::size_t i = 0; i < in.size(); i += 2)
out.push_back(
(unhex(in[i])<<4) + unhex(in[i+1]));
return out;
}
void
check(std::string const& message, std::string const& answer)
{
using digest_type =
std::array<std::uint8_t, sha1_context::digest_size>;
digest_type digest;
if(! expect(boost::algorithm::unhex(
answer, digest.begin()) == digest.end()))
return;
std::string digest;
digest = unhex(answer);
sha1_context ctx;
digest_type result;
std::string result;
result.resize(sha1_context::digest_size);
init(ctx);
update(ctx, message.data(), message.size());
finish(ctx, result.data());
finish(ctx, &result[0]);
expect(result == digest);
}

View File

@@ -6,4 +6,4 @@
//
// Test that header file is self-contained.
#include <beast/http/error.hpp>
#include <beast/error.hpp>

View File

@@ -22,10 +22,12 @@
#include <beast/async_completion.hpp>
#include <beast/bind_handler.hpp>
#include <beast/error.hpp>
#include <beast/websocket/teardown.hpp>
#include <beast/detail/get_lowest_layer.hpp>
#include <boost/system/error_code.hpp>
namespace beast {
namespace test {
/* A stream wrapper that fails.
@@ -35,12 +37,6 @@ namespace beast {
template<class NextLayer>
class fail_stream
{
using error_code =
boost::system::error_code;
using system_error =
boost::system::system_error;
error_code ec_;
std::size_t n_ = 0;
NextLayer next_layer_;
@@ -204,6 +200,7 @@ async_teardown(fail_stream<NextLayer>& stream,
stream.next_layer(), std::forward<TeardownHandler>(handler));
}
} // test
} // beast
#endif

View File

@@ -12,7 +12,6 @@
namespace beast {
namespace http {
namespace test {
class basic_headers_test : public beast::detail::unit_test::suite
{
@@ -48,6 +47,7 @@ public:
bh h3(std::move(h1));
expect(h3.size() == 2);
expect(h1.size() == 0);
h2 = std::move(h2);
}
void run() override
@@ -58,6 +58,5 @@ public:
BEAST_DEFINE_TESTSUITE(basic_headers,http,beast);
} // test
} // asio
} // beast

View File

@@ -10,9 +10,9 @@
#include "message_fuzz.hpp"
#include <beast/error.hpp>
#include <beast/streambuf.hpp>
#include <beast/write_streambuf.hpp>
#include <beast/http/error.hpp>
#include <beast/http/rfc2616.hpp>
#include <beast/detail/ci_char_traits.hpp>
#include <beast/detail/unit_test/suite.hpp>
@@ -29,8 +29,6 @@ namespace http {
class basic_parser_v1_test : public beast::detail::unit_test::suite
{
std::mt19937 rng_;
public:
struct cb_req_checker
{
@@ -302,17 +300,6 @@ public:
//--------------------------------------------------------------------------
template<class UInt = std::size_t>
UInt
rand(std::size_t n)
{
return static_cast<UInt>(
std::uniform_int_distribution<
std::size_t>{0, n-1}(rng_));
}
//--------------------------------------------------------------------------
// Parse a valid message with expected version
//
template<bool isRequest>
@@ -481,6 +468,42 @@ public:
parse_ev<true>("GET / HTTP/1.1\r\nf :", parse_error::bad_field);
}
void testCorrupt()
{
using boost::asio::buffer;
std::string s;
for(std::size_t n = 0;;++n)
{
// Create a request and set one octet to an invalid char
s =
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 00\r\n"
"\r\n";
auto const len = s.size();
if(n >= s.size())
{
pass();
break;
}
s[n] = 0;
for(std::size_t m = 1; m < len - 1; ++m)
{
null_parser<true> p;
error_code ec;
p.write(buffer(s.data(), m), ec);
if(ec)
{
pass();
continue;
}
p.write(buffer(s.data() + m, len - m), ec);
expect(ec);
}
}
}
void
testRandomReq(std::size_t N)
{
@@ -629,6 +652,7 @@ public:
testFlags();
testUpgrade();
testBad();
testCorrupt();
testRandomReq(100);
testRandomResp(100);
testBody();

View File

@@ -1,9 +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)
//
// Test that header file is self-contained.
#include <beast/http/body_type.hpp>

View File

@@ -34,7 +34,6 @@ namespace test {
class sync_echo_http_server
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
@@ -136,11 +135,46 @@ private:
class message_test : public beast::detail::unit_test::suite
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
void testFunctions()
{
request_v1<empty_body> m;
m.version = 10;
expect(! is_upgrade(m));
m.headers.insert("Transfer-Encoding", "chunked");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
}
m.headers.erase("Transfer-Encoding");
m.headers.insert("Content-Length", "0");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
}
m.headers.erase("Content-Length");
m.headers.insert("Connection", "keep-alive");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
}
}
void
syncEcho(endpoint_type ep)
{
@@ -174,6 +208,7 @@ public:
void run() override
{
testFunctions();
testAsio();
pass();
}

View File

@@ -10,7 +10,7 @@
#include "nodejs-parser/http_parser.h"
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <beast/http/message_v1.hpp>
#include <beast/http/rfc2616.hpp>
#include <beast/buffer_concepts.hpp>

View File

@@ -7,3 +7,97 @@
// Test that header file is self-contained.
#include <beast/http/read.hpp>
#include "../fail_stream.hpp"
#include "../string_stream.hpp"
#include "../yield_to.hpp"
#include <beast/http/streambuf_body.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/asio/spawn.hpp>
namespace beast {
namespace http {
class read_test
: public beast::detail::unit_test::suite
, public test::enable_yield_to
{
public:
void testRead(yield_context do_yield)
{
static std::size_t constexpr limit = 100;
std::size_t n;
for(n = 1; n < limit; ++n)
{
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 0\r\n"
"\r\n"
);
request_v1<streambuf_body> m;
try
{
read(fs, sb, m);
break;
}
catch(std::exception const&)
{
}
}
expect(n < limit);
for(n = 1; n < limit; ++n)
{
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 0\r\n"
"\r\n"
);
request_v1<streambuf_body> m;
error_code ec;
read(fs, sb, m, ec);
if(! ec)
break;
}
expect(n < limit);
ios_.post(
[&]{
n = 1;
});
for(n = 1; n < limit; ++n)
{
streambuf sb;
test::fail_stream<test::string_stream> fs(n, ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"User-Agent: test\r\n"
"Content-Length: 0\r\n"
"\r\n"
);
request_v1<streambuf_body> m;
error_code ec;
async_read(fs, sb, m, do_yield[ec]);
if(! ec)
break;
}
expect(n < limit);
}
void run() override
{
yield_to(std::bind(&read_test::testRead,
this, std::placeholders::_1));
}
};
BEAST_DEFINE_TESTSUITE(read,http,beast);
} // http
} // beast

View File

@@ -8,7 +8,7 @@
// Test that header file is self-contained.
#include <beast/http/write.hpp>
#include <beast/http/error.hpp>
#include <beast/error.hpp>
#include <beast/http/headers.hpp>
#include <beast/http/message.hpp>
#include <beast/http/empty_body.hpp>

View File

@@ -87,10 +87,26 @@ public:
expect(buffer_copy(pbc, cb) == 0);
}
void testIterator()
{
using boost::asio::const_buffer;
char b[3];
std::array<const_buffer, 3> bs{{
const_buffer{&b[0], 1},
const_buffer{&b[1], 1},
const_buffer{&b[2], 1}}};
auto pb = prepare_buffers(2, bs);
std::size_t n = 0;
for(auto it = pb.end(); it != pb.begin(); --it)
++n;
expect(n == 2);
}
void run() override
{
testBuffers();
testNullBuffers();
testIterator();
}
};

View File

@@ -93,6 +93,7 @@ public:
expect(s3.back() == 'x');
s2 = "y";
expect(s2 == "y");
expect(s3 == "x");
s1 = s2;
expect(s1 == "y");
s1.clear();
@@ -140,6 +141,35 @@ public:
pass();
}
}
{
str1 s1("x");
str2 s2;
s2 = s1;
try
{
s1.resize(2);
fail();
}
catch(std::length_error const&)
{
pass();
}
s1 = "1";
s2 = "2";
expect(s1.compare(s2) < 0);
expect(s2.compare(s1) > 0);
expect(s1 < "10");
expect(s2 > "1");
expect("10" > s1);
expect("1" < s2);
}
pass();
}
void testCompare()
{
using str1 = static_string<1>;
using str2 = static_string<2>;
{
str2 s1("x");
str2 s2("x");
@@ -175,12 +205,49 @@ public:
expect(! ("x" > s));
expect(! ("x" != s));
}
pass();
{
str2 s("x");
expect(s <= "y");
expect(s < "y");
expect(s != "y");
expect(! (s == "y"));
expect(! (s >= "y"));
expect(! (s > "x"));
expect("y" >= s);
expect("y" > s);
expect("y" != s);
expect(! ("y" == s));
expect(! ("y" <= s));
expect(! ("y" < s));
}
{
str1 s1("x");
str2 s2("y");
expect(s1 <= s2);
expect(s1 < s2);
expect(s1 != s2);
expect(! (s1 == s2));
expect(! (s1 >= s2));
expect(! (s1 > s2));
}
{
str1 s1("x");
str2 s2("xx");
expect(s1 < s2);
expect(s2 > s1);
}
{
str1 s1("x");
str2 s2("yy");
expect(s1 < s2);
expect(s2 > s1);
}
}
void run() override
{
testMembers();
testCompare();
}
};

View File

@@ -25,6 +25,8 @@ public:
streambuf_readstream<socket_type, streambuf> srs(ios);
streambuf_readstream<socket_type, streambuf> srs2(std::move(srs));
srs = std::move(srs2);
expect(&srs.get_io_service() == &ios);
expect(&srs.get_io_service() == &srs2.get_io_service());
}
{
socket_type sock(ios);

114
test/string_stream.hpp Normal file
View File

@@ -0,0 +1,114 @@
//
// 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_TEST_STRING_STREAM_HPP
#define BEAST_TEST_STRING_STREAM_HPP
#include <beast/bind_handler.hpp>
#include <beast/error.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_service.hpp>
#include <string>
namespace beast {
namespace test {
// meets the requirements of AsyncStream, SyncStream
class string_stream
{
std::string s_;
boost::asio::io_service& ios_;
public:
string_stream(boost::asio::io_service& ios,
std::string s)
: s_(std::move(s))
, ios_(ios)
{
}
boost::asio::io_service&
get_io_service()
{
return ios_;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
error_code ec;
auto const n = read_some(buffers, ec);
if(ec)
throw boost::system::system_error{ec};
return n;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
error_code& ec)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
s_.erase(0, n);
return n;
}
template<class MutableBufferSequence, class ReadHandler>
typename async_completion<ReadHandler,
void(error_code, std::size_t)>::result_type
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
s_.erase(0, n);
async_completion<ReadHandler,
void(error_code, std::size_t)> completion(handler);
ios_.post(bind_handler(
completion.handler, error_code{}, n));
return completion.result.get();
}
template<class ConstBufferSequence>
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<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
error_code&)
{
return boost::asio::buffer_size(buffers);
}
template<class ConstBuffeSequence, class WriteHandler>
typename async_completion<WriteHandler,
void(error_code, std::size_t)>::result_type
async_write_some(ConstBuffeSequence const& buffers,
WriteHandler&& handler)
{
async_completion<WriteHandler,
void(error_code, std::size_t)> completion(handler);
ios_.post(bind_handler(completion.handler,
error_code{}, boost::asio::buffer_size(buffers)));
return completion.result.get();
}
};
} // test
} // beast
#endif

View File

@@ -9,164 +9,31 @@
#include <beast/websocket/stream.hpp>
#include "../fail_stream.hpp"
#include "../string_stream.hpp"
#include "../yield_to.hpp"
#include "websocket_async_echo_peer.hpp"
#include "websocket_sync_echo_peer.hpp"
#include <beast/bind_handler.hpp>
#include <beast/streambuf.hpp>
#include <beast/to_string.hpp>
#include <beast/detail/unit_test/suite.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/optional.hpp>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include <beast/http/parser_v1.hpp>
namespace beast {
namespace websocket {
class stream_test : public beast::detail::unit_test::suite
class stream_test
: public beast::detail::unit_test::suite
, public test::enable_yield_to
{
boost::asio::io_service ios_;
boost::optional<boost::asio::io_service::work> work_;
std::thread thread_;
std::mutex m_;
std::condition_variable cv_;
bool running_ = false;;
public:
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
// meets the requirements of AsyncStream, SyncStream
class string_Stream
{
std::string s_;
boost::asio::io_service& ios_;
public:
string_Stream(boost::asio::io_service& ios,
std::string s)
: s_(s)
, ios_(ios)
{
}
boost::asio::io_service&
get_io_service()
{
return ios_;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
error_code ec;
auto const n = read_some(buffers, ec);
if(ec)
throw boost::system::system_error{ec};
return n;
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
error_code& ec)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
s_.erase(0, n);
return n;
}
template<class MutableBufferSequence, class ReadHandler>
typename async_completion<ReadHandler,
void(error_code, std::size_t)>::result_type
async_read_some(MutableBufferSequence const& buffers,
ReadHandler&& handler)
{
auto const n = boost::asio::buffer_copy(
buffers, boost::asio::buffer(s_));
s_.erase(0, n);
async_completion<ReadHandler,
void(error_code, std::size_t)> completion(handler);
ios_.post(bind_handler(
completion.handler, error_code{}, n));
return completion.result.get();
}
template<class ConstBufferSequence>
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<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
error_code&)
{
return boost::asio::buffer_size(buffers);
}
template<class ConstBuffeSequence, class WriteHandler>
typename async_completion<WriteHandler,
void(error_code, std::size_t)>::result_type
async_write_some(ConstBuffeSequence const& buffers,
WriteHandler&& handler)
{
async_completion<WriteHandler,
void(error_code, std::size_t)> completion(handler);
ios_.post(bind_handler(completion.handler,
error_code{}, boost::asio::buffer_size(buffers)));
return completion.result.get();
}
};
stream_test()
: work_(ios_)
, thread_([&]{ ios_.run(); })
{
}
~stream_test()
{
work_ = boost::none;
thread_.join();
}
template<class Function>
void exec(Function&& f)
{
{
std::lock_guard<std::mutex> lock(m_);
running_ = true;
}
boost::asio::spawn(ios_,
[&](boost::asio::yield_context do_yield)
{
f(do_yield);
std::lock_guard<std::mutex> lock(m_);
running_ = false;
cv_.notify_all();
}
, boost::coroutines::attributes(2 * 1024 * 1024));
std::unique_lock<std::mutex> lock(m_);
cv_.wait(lock, [&]{ return ! running_; });
}
void testSpecialMembers()
{
stream<socket_type> ws(ios_);
@@ -177,6 +44,7 @@ public:
stream<socket_type> ws2(ios_);
ws = std::move(ws2);
}
expect(&ws.get_io_service() == &ios_);
pass();
}
@@ -187,7 +55,15 @@ public:
ws.set_option(read_buffer_size(8192));
ws.set_option(read_message_max(1 * 1024 * 1024));
ws.set_option(write_buffer_size(2048));
pass();
try
{
ws.set_option(message_type(opcode::close));
fail();
}
catch(std::exception const&)
{
pass();
}
}
template<std::size_t N>
@@ -198,10 +74,10 @@ public:
return boost::asio::const_buffers_1(&s[0], N-1);
}
void testAccept(boost::asio::yield_context do_yield)
void testAccept(yield_context do_yield)
{
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
@@ -220,7 +96,7 @@ public:
}
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"\r\n");
try
@@ -234,7 +110,7 @@ public:
}
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
@@ -247,7 +123,7 @@ public:
expect(! ec, ec.message());
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"\r\n");
error_code ec;
@@ -255,7 +131,7 @@ public:
expect(ec);
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
@@ -268,7 +144,7 @@ public:
expect(! ec, ec.message());
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"\r\n");
error_code ec;
@@ -276,7 +152,7 @@ public:
expect(ec);
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
"Connection: upgrade\r\n"
@@ -295,7 +171,7 @@ public:
}
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"\r\n");
try
{
@@ -309,7 +185,7 @@ public:
}
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
"Connection: upgrade\r\n"
@@ -322,7 +198,7 @@ public:
expect(! ec, ec.message());
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"GET / HTTP/1.1\r\n"
"\r\n");
error_code ec;
@@ -330,7 +206,7 @@ public:
expect(ec);
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
"Connection: upgrade\r\n"
@@ -343,7 +219,7 @@ public:
expect(! ec, ec.message());
}
{
stream<string_Stream> ws(ios_,
stream<test::string_stream> ws(ios_,
"\r\n");
error_code ec;
ws.async_accept(strbuf(
@@ -353,7 +229,7 @@ public:
}
void testHandshake(endpoint_type const& ep,
boost::asio::yield_context do_yield)
yield_context do_yield)
{
{
// disconnected socket
@@ -423,7 +299,7 @@ public:
}
void testErrorHandling(endpoint_type const& ep,
boost::asio::yield_context do_yield)
yield_context do_yield)
{
static std::size_t constexpr limit = 100;
std::size_t n;
@@ -436,7 +312,7 @@ public:
sock.connect(ep, ec);
if(! expect(! ec, ec.message()))
break;
stream<fail_stream<socket_type&>> ws(n, sock);
stream<test::fail_stream<socket_type&>> ws(n, sock);
try
{
ws.handshake("localhost", "/");
@@ -475,7 +351,7 @@ public:
sock.connect(ep, ec);
if(! expect(! ec, ec.message()))
break;
stream<fail_stream<socket_type&>> ws(n, sock);
stream<test::fail_stream<socket_type&>> ws(n, sock);
ws.handshake("localhost", "/", ec);
if(ec)
continue;
@@ -510,7 +386,7 @@ public:
sock.connect(ep, ec);
if(! expect(! ec, ec.message()))
break;
stream<fail_stream<socket_type&>> ws(n, sock);
stream<test::fail_stream<socket_type&>> ws(n, sock);
ws.async_handshake("localhost", "/", do_yield[ec]);
if(ec)
break;
@@ -538,13 +414,70 @@ public:
expect(n < limit);
}
void testMask(endpoint_type const& ep,
yield_context do_yield)
{
{
std::vector<char> v;
for(char n = 0; n < 20; ++n)
{
error_code ec;
socket_type sock(ios_);
sock.connect(ep, ec);
if(! expect(! ec, ec.message()))
break;
stream<socket_type&> ws(sock);
ws.handshake("localhost", "/", ec);
if(! expect(! ec, ec.message()))
break;
ws.write(boost::asio::buffer(v), ec);
if(! expect(! ec, ec.message()))
break;
opcode op;
streambuf sb;
ws.read(op, sb, ec);
if(! expect(! ec, ec.message()))
break;
expect(to_string(sb.data()) ==
std::string{v.data(), v.size()});
v.push_back(n+1);
}
}
{
std::vector<char> v;
for(char n = 0; n < 20; ++n)
{
error_code ec;
socket_type sock(ios_);
sock.connect(ep, ec);
if(! expect(! ec, ec.message()))
break;
stream<socket_type&> ws(sock);
ws.handshake("localhost", "/", ec);
if(! expect(! ec, ec.message()))
break;
ws.async_write(boost::asio::buffer(v), do_yield[ec]);
if(! expect(! ec, ec.message()))
break;
opcode op;
streambuf sb;
ws.async_read(op, sb, do_yield[ec]);
if(! expect(! ec, ec.message()))
break;
expect(to_string(sb.data()) ==
std::string{v.data(), v.size()});
v.push_back(n+1);
}
}
}
void run() override
{
testSpecialMembers();
testOptions();
exec(std::bind(&stream_test::testAccept,
yield_to(std::bind(&stream_test::testAccept,
this, std::placeholders::_1));
auto const any = endpoint_type{
@@ -552,22 +485,30 @@ public:
{
sync_echo_peer server(true, any);
exec(std::bind(&stream_test::testHandshake,
yield_to(std::bind(&stream_test::testHandshake,
this, server.local_endpoint(),
std::placeholders::_1));
exec(std::bind(&stream_test::testErrorHandling,
yield_to(std::bind(&stream_test::testErrorHandling,
this, server.local_endpoint(),
std::placeholders::_1));
yield_to(std::bind(&stream_test::testMask,
this, server.local_endpoint(),
std::placeholders::_1));
}
{
async_echo_peer server(true, any, 1);
exec(std::bind(&stream_test::testHandshake,
yield_to(std::bind(&stream_test::testHandshake,
this, server.local_endpoint(),
std::placeholders::_1));
exec(std::bind(&stream_test::testErrorHandling,
yield_to(std::bind(&stream_test::testErrorHandling,
this, server.local_endpoint(),
std::placeholders::_1));
yield_to(std::bind(&stream_test::testMask,
this, server.local_endpoint(),
std::placeholders::_1));
}

View File

@@ -37,7 +37,6 @@ namespace websocket {
class async_echo_peer
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;

View File

@@ -36,7 +36,6 @@ namespace websocket {
class sync_echo_peer
{
public:
using error_code = boost::system::error_code;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;

86
test/yield_to.hpp Normal file
View File

@@ -0,0 +1,86 @@
//
// 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_TEST_ENABLE_YIELD_TO_HPP
#define BEAST_TEST_ENABLE_YIELD_TO_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/optional.hpp>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
namespace beast {
namespace test {
// Mix-in to support tests using coroutines
class enable_yield_to
{
protected:
boost::asio::io_service ios_;
private:
boost::optional<boost::asio::io_service::work> work_;
std::thread thread_;
std::mutex m_;
std::condition_variable cv_;
bool running_ = false;;
protected:
using yield_context =
boost::asio::yield_context;
enable_yield_to()
: work_(ios_)
, thread_([&]
{
ios_.run();
}
)
{
}
~enable_yield_to()
{
work_ = boost::none;
thread_.join();
}
// Run a function in a coroutine
template<class Function>
void
yield_to(Function&& f);
};
template<class Function>
void
enable_yield_to::yield_to(Function&& f)
{
{
std::lock_guard<std::mutex> lock(m_);
running_ = true;
}
boost::asio::spawn(ios_,
[&](boost::asio::yield_context do_yield)
{
f(do_yield);
std::lock_guard<std::mutex> lock(m_);
running_ = false;
cv_.notify_all();
}
, boost::coroutines::attributes(2 * 1024 * 1024));
std::unique_lock<std::mutex> lock(m_);
cv_.wait(lock, [&]{ return ! running_; });
}
} // test
} // beast
#endif