Merge subtree Beast 1.0.0-b5

Merge commit 'a9e507da9b636d394bb43d6bf8002d013530f57a' into develop
This commit is contained in:
Vinnie Falco
2016-05-25 14:47:27 -04:00
82 changed files with 3885 additions and 2710 deletions

View File

@@ -236,26 +236,14 @@
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\BeastConfig.h"> <ClInclude Include="..\..\src\BeastConfig.h">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\abstract_ostream.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_abstract_ostream.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_scoped_ostream.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_std_ostream.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\debug_ostream.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\global_suites.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\global_suites.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\match.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\match.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\print.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\recorder.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\recorder.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\reporter.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\reporter.hpp">
@@ -306,8 +294,6 @@
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\detail\stream_concepts.hpp"> <ClInclude Include="..\..\src\beast\include\beast\core\detail\stream_concepts.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\detail\temp_dir.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\detail\write_streambuf.hpp"> <ClInclude Include="..\..\src\beast\include\beast\core\detail\write_streambuf.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\error.hpp"> <ClInclude Include="..\..\src\beast\include\beast\core\error.hpp">
@@ -352,6 +338,8 @@
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\body_type.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\body_type.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\concepts.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\detail\basic_parser_v1.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\detail\basic_parser_v1.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\detail\chunk_encode.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\detail\chunk_encode.hpp">
@@ -394,8 +382,6 @@
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\string_body.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\string_body.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\type_check.hpp">
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\write.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\write.hpp">
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\websocket.hpp"> <ClInclude Include="..\..\src\beast\include\beast\websocket.hpp">
@@ -428,6 +414,8 @@
</None> </None>
<None Include="..\..\src\beast\include\beast\websocket\impl\handshake_op.ipp"> <None Include="..\..\src\beast\include\beast\websocket\impl\handshake_op.ipp">
</None> </None>
<None Include="..\..\src\beast\include\beast\websocket\impl\ping_op.ipp">
</None>
<None Include="..\..\src\beast\include\beast\websocket\impl\read_frame_op.ipp"> <None Include="..\..\src\beast\include\beast\websocket\impl\read_frame_op.ipp">
</None> </None>
<None Include="..\..\src\beast\include\beast\websocket\impl\read_op.ipp"> <None Include="..\..\src\beast\include\beast\websocket\impl\read_op.ipp">

View File

@@ -537,24 +537,9 @@
<ClInclude Include="..\..\src\BeastConfig.h"> <ClInclude Include="..\..\src\BeastConfig.h">
<Filter>.</Filter> <Filter>.</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\abstract_ostream.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
<Filter>extras\beast\unit_test</Filter> <Filter>extras\beast\unit_test</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_abstract_ostream.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_scoped_ostream.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\basic_std_ostream.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\debug_ostream.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp">
<Filter>extras\beast\unit_test\detail</Filter> <Filter>extras\beast\unit_test\detail</Filter>
</ClInclude> </ClInclude>
@@ -564,9 +549,6 @@
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\match.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\match.hpp">
<Filter>extras\beast\unit_test</Filter> <Filter>extras\beast\unit_test</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\print.hpp">
<Filter>extras\beast\unit_test</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\recorder.hpp"> <ClInclude Include="..\..\src\beast\extras\beast\unit_test\recorder.hpp">
<Filter>extras\beast\unit_test</Filter> <Filter>extras\beast\unit_test</Filter>
</ClInclude> </ClInclude>
@@ -642,9 +624,6 @@
<ClInclude Include="..\..\src\beast\include\beast\core\detail\stream_concepts.hpp"> <ClInclude Include="..\..\src\beast\include\beast\core\detail\stream_concepts.hpp">
<Filter>include\beast\core\detail</Filter> <Filter>include\beast\core\detail</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\detail\temp_dir.hpp">
<Filter>include\beast\core\detail</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\core\detail\write_streambuf.hpp"> <ClInclude Include="..\..\src\beast\include\beast\core\detail\write_streambuf.hpp">
<Filter>include\beast\core\detail</Filter> <Filter>include\beast\core\detail</Filter>
</ClInclude> </ClInclude>
@@ -711,6 +690,9 @@
<ClInclude Include="..\..\src\beast\include\beast\http\body_type.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\body_type.hpp">
<Filter>include\beast\http</Filter> <Filter>include\beast\http</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\concepts.hpp">
<Filter>include\beast\http</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\detail\basic_parser_v1.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\detail\basic_parser_v1.hpp">
<Filter>include\beast\http\detail</Filter> <Filter>include\beast\http\detail</Filter>
</ClInclude> </ClInclude>
@@ -774,9 +756,6 @@
<ClInclude Include="..\..\src\beast\include\beast\http\string_body.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\string_body.hpp">
<Filter>include\beast\http</Filter> <Filter>include\beast\http</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\type_check.hpp">
<Filter>include\beast\http</Filter>
</ClInclude>
<ClInclude Include="..\..\src\beast\include\beast\http\write.hpp"> <ClInclude Include="..\..\src\beast\include\beast\http\write.hpp">
<Filter>include\beast\http</Filter> <Filter>include\beast\http</Filter>
</ClInclude> </ClInclude>
@@ -825,6 +804,9 @@
<None Include="..\..\src\beast\include\beast\websocket\impl\handshake_op.ipp"> <None Include="..\..\src\beast\include\beast\websocket\impl\handshake_op.ipp">
<Filter>include\beast\websocket\impl</Filter> <Filter>include\beast\websocket\impl</Filter>
</None> </None>
<None Include="..\..\src\beast\include\beast\websocket\impl\ping_op.ipp">
<Filter>include\beast\websocket\impl</Filter>
</None>
<None Include="..\..\src\beast\include\beast\websocket\impl\read_frame_op.ipp"> <None Include="..\..\src\beast\include\beast\websocket\impl\read_frame_op.ipp">
<Filter>include\beast\websocket\impl</Filter> <Filter>include\beast\websocket\impl</Filter>
</None> </None>

View File

@@ -34,8 +34,8 @@ packages: &gcc5_pkgs
- autotools-dev - autotools-dev
- libc6-dbg - libc6-dbg
packages: &clang36_pkgs packages: &clang38_pkgs
- clang-3.6 - clang-3.8
- g++-5 - g++-5
- python-software-properties - python-software-properties
- libssl-dev - libssl-dev
@@ -53,56 +53,57 @@ packages: &clang36_pkgs
matrix: matrix:
include: include:
# GCC/Debug # GCC/Debug
# - compiler: gcc
# env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64
# addons: &ao_gcc5
# apt:
# sources: ['ubuntu-toolchain-r-test']
# packages: *gcc5_pkgs
# # GCC/Release
# - compiler: gcc
# env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=64
# addons: *ao_gcc5
# Coverage
- compiler: gcc - compiler: gcc
env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64
addons: &ao_gcc5 addons: &ao_gcc5
apt: apt:
sources: ['ubuntu-toolchain-r-test'] sources: ['ubuntu-toolchain-r-test']
packages: *gcc5_pkgs packages: *gcc5_pkgs
# - compiler: gcc # # Clang/Debug
# env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=32 # - compiler: clang
# addons: *ao_gcc5 # env: GCC_VER=5 VARIANT=debug CLANG_VER=3.8 ADDRESS_MODEL=64
# addons: &ao_clang38
# apt:
# sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8']
# packages: *clang38_pkgs
# GCC/Release # # Clang/Release
- compiler: gcc # - compiler: clang
env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=64 # env: GCC_VER=5 VARIANT=release CLANG_VER=3.8 ADDRESS_MODEL=64
addons: *ao_gcc5 # addons: *ao_clang38
# # - compiler: gcc # Clang/AddressSanitizer
# # env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=32
# # addons: *ao_gcc5
# Clang/Debug
- compiler: clang - compiler: clang
env: GCC_VER=5 VARIANT=debug CLANG_VER=3.6 ADDRESS_MODEL=64 env: GCC_VER=5 VARIANT=asan CLANG_VER=3.8 ADDRESS_MODEL=64
addons: &ao_clang36 addons: &ao_clang38
apt: apt:
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8']
packages: *clang36_pkgs packages: *clang38_pkgs
# # - compiler: clang # Clang/MemorySanitizer
# # env: GCC_VER=5 VARIANT=debug CLANG_VER=3.6 ADDRESS_MODEL=32 # VFALCO Generates false positives unless libc++ is compiled with msan turned on
# # addons: *ao_clang36 #- compiler: clang
# env: GCC_VER=5 VARIANT=msan CLANG_VER=3.8 ADDRESS_MODEL=64 MSAN_OPTIONS=poison_in_dtor=1,sanitize-memory-track-origins=2
# addons: *ao_clang38
# Clang/Release # Clang/UndefinedBehaviourSanitizer
- compiler: clang - compiler: clang
env: GCC_VER=5 VARIANT=release CLANG_VER=3.6 ADDRESS_MODEL=64 env: GCC_VER=5 VARIANT=usan CLANG_VER=3.8 ADDRESS_MODEL=64
addons: *ao_clang36 addons: *ao_clang38
# # - compiler: clang
# # env: GCC_VER=5 VARIANT=release CLANG_VER=3.6 ADDRESS_MODEL=32
# # addons: *ao_clang36
# Coverage
- compiler: gcc
env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64
addons: *ao_gcc5
# ASAN
- compiler: gcc
env: GCC_VER=5 VARIANT=asan ADDRESS_MODEL=64
addons: *ao_gcc5
cache: cache:
directories: directories:

View File

@@ -61,6 +61,22 @@ variant asan
<linkflags>"-fsanitize=address" <linkflags>"-fsanitize=address"
; ;
variant msan
:
debug
:
<cxxflags>"-fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins=2 -fsanitize-memory-use-after-dtor"
<linkflags>"-fsanitize=memory"
;
variant usan
:
debug
:
<cxxflags>"-fsanitize=undefined -fno-omit-frame-pointer"
<linkflags>"-fsanitize=undefined"
;
project beast project beast
: requirements : requirements
<include>. <include>.
@@ -90,7 +106,7 @@ project beast
<os>SOLARIS:<define>__EXTENSIONS__ <os>SOLARIS:<define>__EXTENSIONS__
<os>SOLARIS:<library>socket <os>SOLARIS:<library>socket
<os>SOLARIS:<library>nsl <os>SOLARIS:<library>nsl
<os>NT:<define>_WIN32_WINNT=0x0501 <os>NT:<define>_WIN32_WINNT=0x0601
<os>NT,<toolset>cw:<library>ws2_32 <os>NT,<toolset>cw:<library>ws2_32
<os>NT,<toolset>cw:<library>mswsock <os>NT,<toolset>cw:<library>mswsock
<os>NT,<toolset>gcc:<library>ws2_32 <os>NT,<toolset>gcc:<library>ws2_32

View File

@@ -2,7 +2,8 @@
[![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov] [![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://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/) (https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License]
(https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt)
Beast provides implementations of the HTTP and WebSocket protocols Beast provides implementations of the HTTP and WebSocket protocols
built on top of Boost.Asio and other parts of boost. built on top of Boost.Asio and other parts of boost.

View File

@@ -32,6 +32,13 @@ WebSocket:
* Don't try to read requests into empty_body * Don't try to read requests into empty_body
* Give callers control over the http request/response used during handshake * Give callers control over the http request/response used during handshake
* Investigate poor autobahn results in Debug builds * Investigate poor autobahn results in Debug builds
* Fall through composed operation switch cases
* Replace stream::error_ with stream::state_, example states: ok, error, abort_io
Need a cancel state so waking up a ping stored in invokable knows to call the
final handler with operation_aborted
* Use close_code::no_code instead of close_code::none
* Make request_type, response_type public APIs,
use in stream member function signatures
HTTP: HTTP:
* Define Parser concept in HTTP * Define Parser concept in HTTP

View File

@@ -22,7 +22,6 @@
[template mdash[] '''&mdash; '''] [template mdash[] '''&mdash; ''']
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>'''] [template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>'''] [template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
[template ticket[number]'''<ulink url="https://svn.boost.org/trac/boost/ticket/'''[number]'''">'''#[number]'''</ulink>''']
[def __POSIX__ /POSIX/] [def __POSIX__ /POSIX/]
[def __Windows__ /Windows/] [def __Windows__ /Windows/]
[def __accept__ [@http://www.opengroup.org/onlinepubs/000095399/functions/accept.html `accept()`]] [def __accept__ [@http://www.opengroup.org/onlinepubs/000095399/functions/accept.html `accept()`]]
@@ -191,8 +190,15 @@ supporting its development.
[include websocket.qbk] [include websocket.qbk]
[section:types Type Requirements] [section:types Type Requirements]
[include core_types.qbk] [include types/Body.qbk]
[include http_types.qbk] [include types/BufferSequence.qbk]
[include types/Field.qbk]
[include types/FieldSequence.qbk]
[include types/Parser.qbk]
[include types/Reader.qbk]
[include types/Streambuf.qbk]
[include types/Streams.qbk]
[include types/Writer.qbk]
[endsect] [endsect]
[include design.qbk] [include design.qbk]

View File

@@ -41,14 +41,19 @@
</simplelist> </simplelist>
<bridgehead renderas="sect3">Type Traits</bridgehead> <bridgehead renderas="sect3">Type Traits</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__is_Body">is_Body</link></member>
<member><link linkend="beast.ref.http__is_Parser">is_Parser</link></member> <member><link linkend="beast.ref.http__is_Parser">is_Parser</link></member>
<member><link linkend="beast.ref.http__is_ReadableBody">is_ReadableBody</link></member>
<member><link linkend="beast.ref.http__is_WritableBody">is_WritableBody</link></member>
</simplelist> </simplelist>
</entry> </entry>
<entry valign="top"> <entry valign="top">
<bridgehead renderas="sect3">Functions</bridgehead> <bridgehead renderas="sect3">Functions</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.http__async_parse">async_parse</link></member>
<member><link linkend="beast.ref.http__async_read">async_read</link></member> <member><link linkend="beast.ref.http__async_read">async_read</link></member>
<member><link linkend="beast.ref.http__async_write">async_write</link></member> <member><link linkend="beast.ref.http__async_write">async_write</link></member>
<member><link linkend="beast.ref.http__parse">parse</link></member>
<member><link linkend="beast.ref.http__prepare">prepare</link></member> <member><link linkend="beast.ref.http__prepare">prepare</link></member>
<member><link linkend="beast.ref.http__read">read</link></member> <member><link linkend="beast.ref.http__read">read</link></member>
<member><link linkend="beast.ref.http__write">write</link></member> <member><link linkend="beast.ref.http__write">write</link></member>
@@ -62,6 +67,7 @@
<member><link linkend="beast.types.Body">Body</link></member> <member><link linkend="beast.types.Body">Body</link></member>
<member><link linkend="beast.types.Field">Field</link></member> <member><link linkend="beast.types.Field">Field</link></member>
<member><link linkend="beast.types.FieldSequence">FieldSequence</link></member> <member><link linkend="beast.types.FieldSequence">FieldSequence</link></member>
<member><link linkend="beast.types.Parser">Parser</link></member>
<member><link linkend="beast.types.Reader">Reader</link></member> <member><link linkend="beast.types.Reader">Reader</link></member>
<member><link linkend="beast.types.Writer">Writer</link></member> <member><link linkend="beast.types.Writer">Writer</link></member>
</simplelist> </simplelist>
@@ -70,16 +76,20 @@
<bridgehead renderas="sect3">Classes</bridgehead> <bridgehead renderas="sect3">Classes</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.websocket__close_reason">close_reason</link></member> <member><link linkend="beast.ref.websocket__close_reason">close_reason</link></member>
<member><link linkend="beast.ref.websocket__ping_data">ping_data</link></member>
<member><link linkend="beast.ref.websocket__stream">stream</link></member> <member><link linkend="beast.ref.websocket__stream">stream</link></member>
<member><link linkend="beast.ref.websocket__reason_string">reason_string</link></member>
</simplelist> </simplelist>
<bridgehead renderas="sect3">Options</bridgehead> <bridgehead renderas="sect3">Options</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.websocket__auto_fragment_size">auto_fragment_size</link></member> <member><link linkend="beast.ref.websocket__auto_fragment_size">auto_fragment_size</link></member>
<member><link linkend="beast.ref.websocket__decorate">decorate</link></member> <member><link linkend="beast.ref.websocket__decorate">decorate</link></member>
<member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member> <member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member>
<member><link linkend="beast.ref.websocket__mask_buffer_size">mask_buffer_size</link></member>
<member><link linkend="beast.ref.websocket__message_type">message_type</link></member>
<member><link linkend="beast.ref.websocket__pong_callback">pong_callback</link></member>
<member><link linkend="beast.ref.websocket__read_buffer_size">read_buffer_size</link></member> <member><link linkend="beast.ref.websocket__read_buffer_size">read_buffer_size</link></member>
<member><link linkend="beast.ref.websocket__read_message_max">read_message_max</link></member> <member><link linkend="beast.ref.websocket__read_message_max">read_message_max</link></member>
<member><link linkend="beast.ref.websocket__write_buffer_size">write_buffer_size</link></member>
</simplelist> </simplelist>
</entry> </entry>
<entry valign="top"> <entry valign="top">
@@ -91,6 +101,7 @@
<bridgehead renderas="sect3">Constants</bridgehead> <bridgehead renderas="sect3">Constants</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.websocket__close_code">close_code</link></member> <member><link linkend="beast.ref.websocket__close_code">close_code</link></member>
<member><link linkend="beast.ref.websocket__error">error</link></member>
<member><link linkend="beast.ref.websocket__opcode">opcode</link></member> <member><link linkend="beast.ref.websocket__opcode">opcode</link></member>
</simplelist> </simplelist>
</entry> </entry>
@@ -163,10 +174,10 @@
<bridgehead renderas="sect3">Concepts</bridgehead> <bridgehead renderas="sect3">Concepts</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.types.BufferSequence">BufferSequence</link></member> <member><link linkend="beast.types.BufferSequence">BufferSequence</link></member>
<member><link linkend="beast.types.stream.AsyncStream">AsyncStream</link></member> <member><link linkend="beast.types.streams.AsyncStream">AsyncStream</link></member>
<member><link linkend="beast.types.stream.Stream">Stream</link></member> <member><link linkend="beast.types.streams.Stream">Stream</link></member>
<member><link linkend="beast.types.Streambuf">Streambuf</link></member> <member><link linkend="beast.types.Streambuf">Streambuf</link></member>
<member><link linkend="beast.types.stream.SyncStream">SyncStream</link></member> <member><link linkend="beast.types.streams.SyncStream">SyncStream</link></member>
</simplelist> </simplelist>
</entry> </entry>
</row> </row>

View File

@@ -1528,7 +1528,7 @@
<xsl:text> </xsl:text> <xsl:text> </xsl:text>
<xsl:choose> <xsl:choose>
<xsl:when test="type = 'class AsyncStream'"> <xsl:when test="type = 'class AsyncStream'">
<xsl:text>class ``[link beast.types.stream.AsyncStream [*AsyncStream]]``</xsl:text> <xsl:text>class ``[link beast.types.streams.AsyncStream [*AsyncStream]]``</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="type = 'class AsyncReadStream'"> <xsl:when test="type = 'class AsyncReadStream'">
<xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]``</xsl:text> <xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]``</xsl:text>
@@ -1556,13 +1556,13 @@
<xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]``</xsl:text> <xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]``</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="declname = 'Stream' or type = 'class Stream'"> <xsl:when test="declname = 'Stream' or type = 'class Stream'">
<xsl:text>class ``[link beast.types.stream.Stream [*Stream]]``</xsl:text> <xsl:text>class ``[link beast.types.streams.Stream [*Stream]]``</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="declname = 'Streambuf' or type = 'class Streambuf'"> <xsl:when test="declname = 'Streambuf' or type = 'class Streambuf'">
<xsl:text>class ``[link beast.types.Streambuf [*Streambuf]]``</xsl:text> <xsl:text>class ``[link beast.types.Streambuf [*Streambuf]]``</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="type = 'class SyncStream'"> <xsl:when test="type = 'class SyncStream'">
<xsl:text>class ``[link beast.types.stream.SyncStream [*SyncStream]]``</xsl:text> <xsl:text>class ``[link beast.types.streams.SyncStream [*SyncStream]]``</xsl:text>
</xsl:when> </xsl:when>
<xsl:when test="declname = 'SyncReadStream' or type = 'class SyncReadStream'"> <xsl:when test="declname = 'SyncReadStream' or type = 'class SyncReadStream'">
<xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]``</xsl:text> <xsl:text>class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]``</xsl:text>

View File

@@ -0,0 +1,50 @@
[/
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)
]
[section:Body Body]
In this table:
* `X` is a type meeting the requirements of [*`Body`].
[table Body requirements
[[operation] [type] [semantics, pre/post-conditions]]
[
[`X::value_type`]
[]
[
The type of the `message::body` member.
If this is not movable or not copyable, the containing message
will be not movable or not copyable.
]
]
[
[`X:value_type{}`]
[]
[`DefaultConstructible`]
]
[
[`Body::reader`]
[]
[
If present, a type meeting the requirements of
[link beast.types.Reader [*`Reader`]].
Provides an implementation to parse the body.
]
]
[
[`Body::writer`]
[]
[
If present, a type meeting the requirements of
[link beast.types.Writer [*`Writer`]].
Provides an implementation to serialize the body.
]
]
]
[endsect]

View File

@@ -0,0 +1,15 @@
[/
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)
]
[section:BufferSequence BufferSequence]
A `BufferSequence` is a type meeting either of the following requirements:
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]]
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]]
[endsect]

View File

@@ -0,0 +1,41 @@
[/
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)
]
[section:Field Field]
A [*`Field`] represents a single HTTP header field/value pair.
In this table:
* `X` denotes a type meeting the requirements of [*`Field`].
* `a` denotes a value of type `X`.
[table Field requirements
[[operation][type][semantics, pre/post-conditions]]
[
[`a.name()`]
[`boost::string_ref`]
[
This function returns a value implicitly convertible to
`boost::string_ref` containing the case-insensitive field
name, without leading or trailing white space.
]
]
[
[`a.value()`]
[`boost::string_ref`]
[
This function returns a value implicitly convertible to
`boost::string_ref` containing the value for the field. The
value is considered canonical if there is no leading or
trailing whitespace.
]
]
]
[endsect]

View File

@@ -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)
]
[section:FieldSequence FieldSequence]
A [*`FieldSequence`] is an iterable container whose value type meets
the requirements of [link beast.types.Field [*`Field`]].
In this table:
* `X` denotes a type that meets the requirements of [*`FieldSequence`].
* `a` is a value of type `X`.
[table FieldSequence requirements
[[operation][type][semantics, pre/post-conditions]]
[
[`X::value_type`]
[]
[
A type that meets the requirements of `Field`.
]
]
[
[`X::const_iterator`]
[]
[
A type that meets the requirements of `ForwardIterator`.
]
]
[
[`a.begin()`]
[`X::const_iterator`]
[
Returns an iterator to the beginning of the field sequence.
]
]
[
[`a.end()`]
[`X::const_iterator`]
[
Returns an iterator to the end of the field sequence.
]
]
]
[endsect]

View File

@@ -0,0 +1,58 @@
[/
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)
]
[section:Parser Parser]
A [*`Parser`] is used to deserialize HTTP/1 messages from [link beast.types.streams streams].
Objects of this type are used with [link beast.ref.http__parse http::parse] and
[link beast.ref.http__async_parse http::async_parse].
In this table:
* `X` denotes a type meeting the requirements of [*`Parser`].
* `a` denotes a value of type `X`.
* `b` is a value meeting the requirements of [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConvertibleToConstBuffer.html [*`ConvertibleToConstBuffer`]].
* `ec` is a value of type [link beast.ref.error_code `error_code&`].
[table Parser requirements
[[operation] [type] [semantics, pre/post-conditions]]
[
[`a.complete()`]
[`bool`]
[
Returns `true` when a complete HTTP/1 message has been parsed.
]
]
[
[`a.write(b, ec)`]
[`std::size_t`]
[
Parses the octets in the specified input buffer sequentially until
an error occurs, the end of the buffer is reached, or a complete
HTTP/1 message has been parsed. If an error occurs, `ec` is set
to the error code and parsing stops. This function returns the
number of bytes consumed from the input buffer.
]
]
[
[`a.write_eof(ec)`]
[`void`]
[
Indicates to the parser that no more octets will be available.
Typically this function is called when the end of stream is reached.
For example, if a call to `boost::asio::ip::tcp::socket::read_some`
generates a `boost::asio::error::eof` error. Some HTTP/1 messages
determine the end of the message body by an end of file marker or
closing of the connection.
]
]
]
[endsect]

View File

@@ -0,0 +1,54 @@
[/
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)
]
[section:Reader Reader]
Parser implementations will construct the corresponding `reader` object
during the parse. This customization point allows the Body to determine
the strategy for storing incoming message body data.
In this table:
* `X` denotes a type meeting the requirements of [*`Reader`].
* `a` denotes a value of type `X`.
* `p` is any pointer.
* `n` is a value convertible to `std::size_t`.
* `ec` is a value of type `error_code&`.
* `m` denotes a value of type `message const&` where
`std::is_same<decltype(m.body), Body::value_type>:value == true`
[table Reader requirements
[[operation] [type] [semantics, pre/post-conditions]]
[
[`X a(m);`]
[]
[
`a` is constructible from `m`. The lifetime of `m` is
guaranteed to end no earlier than after `a` is destroyed.
]
]
[
[`a.write(p, n, ec)`]
[`void`]
[
Deserializes the input sequence into the body.
If `ec` is set, the deserialization is aborted and the error
is returned to the caller.
]
]
]
[note Definitions for required `Reader` member functions should be declared
inline so the generated code becomes part of the implementation. ]
[endsect]

View File

@@ -5,54 +5,32 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
] ]
[section:BufferSequence BufferSequence]
A `BufferSequence` is a type meeting either of the following requirements:
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]]
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]]
[endsect]
[section:stream Streams]
Stream types represent objects capable of performing synchronous or
asynchronous I/O. They are based on concepts from `boost::asio`.
[heading:Stream Stream]
A type modeling [*`Stream`] meets either or both of the following requirements:
* [link beast.types.stream.AsyncStream [*`AsyncStream`]]
* [link beast.types.stream.SyncStream [*`SyncStream`]]
[heading:AsyncStream AsyncStream]
A type modeling [*`AsyncStream`] meets the following requirements:
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]]
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]]
[heading:SyncStream SyncStream]
A type modeling [*`SyncStream`] meets the following requirements:
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]]
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]]
[endsect]
[section:Streambuf Streambuf] [section:Streambuf Streambuf]
A [*`Streambuf`] represents a logical octet sequence divided in two sections,
the input sequence and the output sequence. Octets are written to the output
sequence, then moved to the input sequence where they are available for
reading. When some or all of the input sequence is no longer needed, it may
be consumed.
The interface to this concept is intended to permit the following
implementation strategies:
* A single contiguous octet array, which is reallocated as necessary to
accommodate changes in the size of the octet sequence.
* A sequence of one or more octet arrays, where each array is of the same
size. Additional octet array objects are appended to the sequence to
accommodate changes in the size of the octet sequence.
* A sequence of one or more octet arrays of varying sizes. Additional octet
array objects are appended to the sequence to accommodate changes in the
size of the character sequence. This is the implementation approached
currently offered by [link beast.ref.basic_streambuf `basic_streambuf`].
In the table below: In the table below:
* `X` denotes a class * `X` denotes a class meeting the requirements of [*`Streambuf`]
* `a` denotes a value of type `X` * `a` denotes a value of type `X`
* `n` denotes a value convertible to `std::size_t` * `n` denotes a value convertible to `std::size_t`
* `U`, `T` denote unspecified types. * `U`, `T` denote unspecified types.
@@ -62,12 +40,12 @@ In the table below:
[ [
[`X::const_buffers_type`] [`X::const_buffers_type`]
[`T`] [`T`]
[`T` meets the requirements for `ConstBufferSequence`.] [`T` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`].]
] ]
[ [
[`X::mutable_buffers_type`] [`X::mutable_buffers_type`]
[`U`] [`U`]
[`U` meets the requirements for `MutableBufferSequence`.] [`U` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`].]
] ]
[ [
[`a.commit(n)`] [`a.commit(n)`]
@@ -98,7 +76,7 @@ In the table below:
[ [
[`a.max_size()`] [`a.max_size()`]
[`std::size_t`] [`std::size_t`]
[Returns the maximum size of the `Streambuf`.] [Returns the maximum size of the stream buffer.]
] ]
[ [
[`read_size_helper(a, n)`] [`read_size_helper(a, n)`]

View File

@@ -0,0 +1,34 @@
[/
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)
]
[section:streams Streams]
Stream types represent objects capable of performing synchronous or
asynchronous I/O. They are based on concepts from `boost::asio`.
[heading:Stream Stream]
A type modeling [*`Stream`] meets either or both of the following requirements:
* [*`AsyncStream`]
* [*`SyncStream`]
[heading:AsyncStream AsyncStream]
A type modeling [*`AsyncStream`] meets the following requirements:
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]]
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]]
[heading:SyncStream SyncStream]
A type modeling [*`SyncStream`] meets the following requirements:
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]]
* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]]
[endsect]

View File

@@ -5,199 +5,6 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
] ]
[section:Body Body]
In this table:
* `X` is a type meeting the requirements of [*`Body`].
[table Body requirements
[[operation] [type] [semantics, pre/post-conditions]]
[
[`X::value_type`]
[]
[
The type of the `message::body` member.
If this is not movable or not copyable, the containing message
will be not movable or not copyable.
]
]
[
[`X:value_type{}`]
[]
[`DefaultConstructible`]
]
[
[`Body::reader`]
[]
[
If present, a type meeting the requirements of
[link beast.types.Reader [*`Reader`]].
Provides an implementation to parse the body.
]
]
[
[`Body::writer`]
[]
[
If present, a type meeting the requirements of
[link beast.types.Writer [*`Writer`]].
Provides an implementation to serialize the body.
]
]
]
[endsect]
[section:BufferSequence BufferSequence]
A `BufferSequence` is a type meeting either of the following requirements:
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]]
* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]]
[endsect]
[section:Field Field]
A [*`Field`] represents a single HTTP header field/value pair.
In this table:
* `X` denotes a type meeting the requirements of [*`Field`].
* `a` denotes a value of type `X`.
[table Field requirements
[[operation][type][semantics, pre/post-conditions]]
[
[`a.name()`]
[`boost::string_ref`]
[
This function returns a value implicitly convertible to
`boost::string_ref` containing the case-insensitve field
name, without leading or trailing white space.
]
]
[
[`a.value()`]
[`boost::string_ref`]
[
This function returns a value implicitly convertible to
`boost::string_ref` containing the value for the field. The
value is considered canonical if there is no leading or
trailing whitespace.
]
]
]
[endsect]
[section:FieldSequence FieldSequence]
A [*`FieldSequence`] is an iterable container whose value type meets
the requirements of [link beast.types.Field [*`Field`]].
In this table:
* `X` denotes a type that meets the requirements of [*`FieldSequence`].
* `a` is a value of type `X`.
[table FieldSequence requirements
[[operation][type][semantics, pre/post-conditions]]
[
[`X::value_type`]
[]
[
A type that meets the requirements of `Field`.
]
]
[
[`X::const_iterator`]
[]
[
A type that meets the requirements of `ForwardIterator`.
]
]
[
[`a.begin()`]
[`X::const_iterator`]
[
Returns an iterator to the beginning of the field sequence.
]
]
[
[`a.end()`]
[`X::const_iterator`]
[
Returns an iterator to the end of the field sequence.
]
]
]
[endsect]
[section:Reader Reader]
Parser implementations will construct the corresponding `reader` object
during the parse. This customization point allows the Body to determine
the strategy for storing incoming message body data.
In this table:
* `X` denotes a type meeting the requirements of [*`Reader`].
* `a` denotes a value of type `X`.
* `p` is any pointer.
* `n` is a value convertible to `std::size_t`.
* `ec` is a value of type `error_code&`.
* `m` denotes a value of type `message const&` where
`std::is_same<decltype(m.body), Body::value_type>:value == true`
[table Reader requirements
[[operation] [type] [semantics, pre/post-conditions]]
[
[`X a(m);`]
[]
[
`a` is constructible from `m`. The lifetime of `m` is
guaranteed to end no earlier than after `a` is destroyed.
]
]
[
[`a.write(p, n, ec)`]
[`void`]
[
Deserializes the input sequence into the body.
If `ec` is set, the deserialization is aborted and the error
is returned to the caller.
]
]
]
[note Definitions for required `Reader` member functions should be declared
inline so the generated code becomes part of the implementation. ]
[endsect]
[section:Writer Writer] [section:Writer Writer]
A `Writer` serializes the message body. The implementation creates an instance A `Writer` serializes the message body. The implementation creates an instance
@@ -370,4 +177,3 @@ public:
``` ```
[endsect] [endsect]

View File

@@ -46,13 +46,15 @@ model, handler allocation, and handler invocation hooks. Calls to
Beast.WebSocket asynchronous initiation functions allow callers the choice Beast.WebSocket asynchronous initiation functions allow callers the choice
of using a completion handler, stackful or stackless coroutines, futures, of using a completion handler, stackful or stackless coroutines, futures,
or user defined customizations (for example, Boost.Fiber). The or user defined customizations (for example, Boost.Fiber). The
implementation uses handler invocation hooks (`asio_handler_invoke`), implementation uses handler invocation hooks
([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]),
providing execution guarantees on composed operations in a manner providing execution guarantees on composed operations in a manner
identical to Boost.Asio. The implementation also uses handler allocation identical to Boost.Asio. The implementation also uses handler allocation hooks
hooks (`asio_handler_allocate`) when allocating memory internally for ([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`])
composed operations. when allocating memory internally for composed operations.
There is no need for inheritance or virtual members in `websocket::stream`. There is no need for inheritance or virtual members in a
[link beast.ref.websocket__stream `beast::websocket::stream`].
All operations are templated and transparent to the compiler, allowing for All operations are templated and transparent to the compiler, allowing for
maximum inlining and optimization. maximum inlining and optimization.
@@ -67,43 +69,47 @@ both Boost.Asio and the WebSocket protocol specification described in
[section:creating Creating the socket] [section:creating Creating the socket]
The interface to Beast's WebSocket implementation is a single template The interface to Beast's WebSocket implementation is a single template
class [link beast.ref.websocket__stream `websocket::stream`] which wraps a class [link beast.ref.websocket__stream `beast::websocket::stream`] which
"next layer" object. The next layer object must meet the requirements of wraps a "next layer" object. The next layer object must meet the requirements
`SyncReadStream` and `SyncWriteStream` if synchronous operations are performed, of [link beast.types.streams.SyncStream [*`SyncReadStream`]] if synchronous
`AsyncReadStream` and `AsyncWriteStream` is asynchronous operations are operations are performed, or
performed, or both. Arguments supplied during construction are passed to [link beast.types.streams.AsyncStream [*`AsyncStream`]] if asynchronous
next layer's constructor. Here we declare two websockets which have ownership operations are performed, or both. Arguments supplied during construction are
of the next layer: passed to next layer's constructor. Here we declare two websockets which have
ownership of the next layer:
``` ```
io_service ios; boost::asio::io_service ios;
websocket::stream<ip::tcp::socket> ws(ios); beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
ssl::context ctx(ssl::context::sslv23); boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
websocket::stream<ssl::stream<ip::tcp::socket>> wss(ios, ctx); beast::websocket::stream<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> wss(ios, ctx);
``` ```
For servers that can handshake in multiple protocols, it may be desired For servers that can handshake in multiple protocols, it may be desired
to wrap an object that already exists. This socket can be moved in: to wrap an object that already exists. This socket can be moved in:
``` ```
tcp::socket&& sock; boost::asio::ip::tcp::socket&& sock;
... ...
websocket::stream<ip::tcp::socket> ws(std::move(sock)); beast::websocket::stream<
boost::asio::ip::tcp::socket> ws(std::move(sock));
``` ```
Or, the wrapper can be constructed with a non-owning reference. In Or, the wrapper can be constructed with a non-owning reference. In
this case, the caller is responsible for managing the lifetime of the this case, the caller is responsible for managing the lifetime of the
underlying socket being wrapped: underlying socket being wrapped:
``` ```
tcp::socket sock; boost::asio::ip::tcp::socket sock;
... ...
websocket::stream<ip::tcp::socket&> ws(sock); beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
``` ```
The layer being wrapped can be accessed through the websocket's "next layer", The layer being wrapped can be accessed through the websocket's "next layer",
permitting callers to interact directly with its interface. permitting callers to interact directly with its interface.
``` ```
ssl::context ctx(ssl::context::sslv23); boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
websocket::stream<ssl::stream<ip::tcp::socket>> ws(ios, ctx); beast::websocket::stream<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws(ios, ctx);
... ...
ws.next_layer().shutdown(); // ssl::stream shutdown ws.next_layer().shutdown(); // ssl::stream shutdown
``` ```
@@ -122,17 +128,18 @@ Connections are established by using the interfaces which already exist
for the next layer. For example, making an outgoing connection: for the next layer. For example, making an outgoing connection:
``` ```
std::string const host = "mywebapp.com"; std::string const host = "mywebapp.com";
io_service ios; boost::asio::io_service ios;
tcp::resolver r(ios); boost::asio::ip::tcp::resolver r(ios);
websocket::stream<ip::tcp::socket> ws(ios); beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"})); boost::asio::connect(ws.next_layer(),
r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"}));
``` ```
Accepting an incoming connection: Accepting an incoming connection:
``` ```
void do_accept(tcp::acceptor& acceptor) void do_accept(boost::asio::ip::tcp::acceptor& acceptor)
{ {
websocket::stream<ip::tcp::socket> ws(acceptor.get_io_service()); beast::websocket::stream<boost::asio::ip::tcp::socket> ws(acceptor.get_io_service());
acceptor.accept(ws.next_layer()); acceptor.accept(ws.next_layer());
} }
``` ```
@@ -149,26 +156,26 @@ A WebSocket session begins when one side sends the HTTP Upgrade request
for websocket, and the other side sends an appropriate HTTP response for websocket, and the other side sends an appropriate HTTP response
indicating that the request was accepted and that the connection has indicating that the request was accepted and that the connection has
been upgraded. The HTTP Upgrade request must include the Host HTTP field, been upgraded. The HTTP Upgrade request must include the Host HTTP field,
and the URI of the resource to request. `hanshake` is used to send the and the URI of the resource to request. `handshake` is used to send the
request with the required host and resource strings. request with the required host and resource strings.
``` ```
websocket::stream<ip::tcp::socket> ws(ios); beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
... ...
ws.set_option(websocket::keep_alive(true)); ws.set_option(beast::websocket::keep_alive(true));
ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices"); ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices");
``` ```
The `websocket::stream` automatically handles receiving and processing The [link beast.ref.websocket__stream `beast::websocket::stream`] automatically
the HTTP response to the handshake request. The call to handshake is handles receiving and processing the HTTP response to the handshake request.
successful if a HTTP response is received with the 101 "Switching Protocols" The call to handshake is successful if a HTTP response is received with the
status code. On failure, an error is returned or an exception is thrown. 101 "Switching Protocols" status code. On failure, an error is returned or an
Depending on the keep alive setting, the socket may remain open for a exception is thrown. Depending on the keep alive setting, the socket may remain
subsequent handshake attempt open for a subsequent handshake attempt
Performing a handshake for an incoming websocket upgrade request operates Performing a handshake for an incoming websocket upgrade request operates
similarly. If the handshake fails, an error is returned or exception thrown: similarly. If the handshake fails, an error is returned or exception thrown:
``` ```
websocket::stream<ip::tcp::socket> ws(ios); beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
... ...
ws.accept(); ws.accept();
``` ```
@@ -178,12 +185,12 @@ on the connection, or might have already received an entire HTTP request
containing the upgrade request. Overloads of `accept` allow callers to containing the upgrade request. Overloads of `accept` allow callers to
pass in this additional buffered handshake data. pass in this additional buffered handshake data.
``` ```
void do_accept(tcp::socket& sock) void do_accept(boost::asio::ip::tcp::socket& sock)
{ {
boost::asio::streambuf sb; boost::asio::streambuf sb;
read_until(sock, sb, "\r\n\r\n"); boost::asio::read_until(sock, sb, "\r\n\r\n");
... ...
websocket::stream<ip::tcp::socket&> ws(sock); beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
ws.accept(sb.data()); ws.accept(sb.data());
... ...
} }
@@ -192,12 +199,12 @@ void do_accept(tcp::socket& sock)
Alternatively, the caller can pass an entire HTTP request if it was Alternatively, the caller can pass an entire HTTP request if it was
obtained elsewhere: obtained elsewhere:
``` ```
void do_accept(tcp::socket& sock) void do_accept(boost::asio::ip::tcp::socket& sock)
{ {
boost::asio::streambuf sb; boost::asio::streambuf sb;
http::request<http::empty_body> request; beast::http::request<http::empty_body> request;
http::read(sock, request); beast::http::read(sock, request);
if(http::is_upgrade(request)) if(beast::http::is_upgrade(request))
{ {
websocket::stream<ip::tcp::socket&> ws(sock); websocket::stream<ip::tcp::socket&> ws(sock);
ws.accept(request); ws.accept(request);
@@ -206,8 +213,6 @@ void do_accept(tcp::socket& sock)
} }
``` ```
[note Identifiers in the `http` namespace are part of Beast.HTTP. ]
[endsect] [endsect]
@@ -218,20 +223,21 @@ After the WebSocket handshake is accomplished, callers may send and receive
messages using the message oriented interface. This interface requires that messages using the message oriented interface. This interface requires that
all of the buffers representing the message are known ahead of time: all of the buffers representing the message are known ahead of time:
``` ```
void echo(websocket::stream<ip::tcp::socket>& ws) void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
{ {
streambuf sb; beast::streambuf sb;
websocket::opcode::value op; beast::websocket::opcode::value op;
ws.read(sb); ws.read(sb);
ws.set_option(websocket::message_type(op)); ws.set_option(beast::websocket::message_type(op));
websocket::write(ws, sb.data()); ws.write(sb.data());
sb.consume(sb.size()); sb.consume(sb.size());
} }
``` ```
[important Calls to `set_option` must be made from the same implicit [important Calls to [link beast.ref.websocket__stream.set_option `set_option`]
or explicit strand as that used to perform other operations. ] must be made from the same implicit or explicit strand as that used to perform
other operations. ]
[endsect] [endsect]
@@ -249,18 +255,19 @@ message ahead of time:
For these cases, the frame oriented interface may be used. This For these cases, the frame oriented interface may be used. This
example reads and echoes a complete message using this interface: example reads and echoes a complete message using this interface:
``` ```
void echo(websocket::stream<ip::tcp::socket>& ws) void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
{ {
streambuf sb; beast::streambuf sb;
websocket::frame_info fi; beast::websocket::frame_info fi;
for(;;) for(;;)
{ {
ws.read_frame(fi, sb); ws.read_frame(fi, sb);
if(fi.fin) if(fi.fin)
break; break;
} }
ws.set_option(websocket::message_type(fi.op)); ws.set_option(beast::websocket::message_type(fi.op));
consuming_buffers<streambuf::const_buffers_type> cb(sb.data()); beast::consuming_buffers<
beast::streambuf::const_buffers_type> cb(sb.data());
for(;;) for(;;)
{ {
using boost::asio::buffer_size; using boost::asio::buffer_size;
@@ -287,10 +294,11 @@ void echo(websocket::stream<ip::tcp::socket>& ws)
During read operations, the implementation automatically reads and processes During read operations, the implementation automatically reads and processes
WebSocket control frames such as ping, pong, and close. Pings are replied WebSocket control frames such as ping, pong, and close. Pings are replied
to as soon as possible, pongs are noted. The receipt of a close frame to as soon as possible, pongs are delivered to the pong callback. The receipt
initiates the WebSocket close procedure, eventually resulting in the of a close frame initiates the WebSocket close procedure, eventually resulting
error code `websocket::error::closed` being delivered to the caller in in the error code [link beast.ref.websocket__error `error::closed`] being
a subsequent read operation, assuming no other error takes place. delivered to the caller in a subsequent read operation, assuming no other error
takes place.
To ensure timely delivery of control frames, large messages are broken up To ensure timely delivery of control frames, large messages are broken up
into smaller sized frames. The implementation chooses the size and number into smaller sized frames. The implementation chooses the size and number
@@ -298,19 +306,49 @@ of the frames making up the message. The automatic fragment size option
gives callers control over the size of these frames: gives callers control over the size of these frames:
``` ```
... ...
ws.set_option(websocket::auto_fragment_size(8192)); ws.set_option(beast::websocket::auto_fragment_size(8192));
``` ```
The WebSocket protocol defines a procedure and control message for initiating The WebSocket protocol defines a procedure and control message for initiating
a close of the session. Handling of close initiated by the remote end of the a close of the session. Handling of close initiated by the remote end of the
connection is performed automatically. To manually initiate a close, use connection is performed automatically. To manually initiate a close, use
`websocket::stream::close`: [link beast.ref.websocket__stream.close `close`]:
``` ```
ws.close(); ws.close();
``` ```
[note To receive the `websocket::error::closed` error, a read operation [note To receive the [link beast.ref.websocket__error `error::closed`]
is required. ] error, a read operation is required. ]
[endsect]
[section:pongs Pong messages]
To receive pong control frames, callers may register a "pong callback" using
[link beast.ref.websocket__stream.set_option `set_option`]:
the following signature:
```
void on_pong(ping_data const& payload);
...
ws.set_option(pong_callback{&on_pong});
```
When a pong callback is registered, any pongs received through either
synchronous read functions or asynchronous read functions will invoke the
pong callback, passing the payload in the pong message as the argument.
Unlike regular completion handlers used in calls to asynchronous initiation
functions, the pong callback only needs to be set once. The callback is not
reset when a pong is received. The same callback is used for both synchronous
and asynchronous reads. The pong callback is passive; in order to receive
pongs, a synchronous or asynchronous stream read function must be active.
[note When an asynchronous read function receives a pong, the the pong callback
is invoked in the same manner as that used to invoke the final completion
handler of the corresponding read function.]
[endsect] [endsect]
@@ -320,7 +358,8 @@ is required. ]
Because calls to read data may return a variable amount of bytes, the Because calls to read data may return a variable amount of bytes, the
interface to calls that read data require an object that meets the requirements interface to calls that read data require an object that meets the requirements
of `Streambuf`. This concept is modeled on `boost::asio::basic_streambuf`. of [link beast.types.Streambuf [*`Streambuf`]]. This concept is modeled on
[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`].
The implementation does not perform queueing or buffering of messages. If The implementation does not perform queueing or buffering of messages. If
desired, these features should be provided by callers. The impact of this desired, these features should be provided by callers. The impact of this
@@ -331,6 +370,7 @@ of the underlying TCP/IP connection.
[endsect] [endsect]
[section:async Asynchronous interface] [section:async Asynchronous interface]
Asynchronous versions are available for all functions: Asynchronous versions are available for all functions:
@@ -361,12 +401,14 @@ void echo(websocket::stream<ip::tcp::socket>& ws,
[section:io_service io_service] [section:io_service The io_service]
The creation and operation of the `boost::asio::io_service` associated with The creation and operation of the
the underlying stream is left to the callers, permitting any implementation [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`]
strategy including one that does not require threads for environments where associated with the underlying stream is left to the callers, permitting any
threads are unavailable. Beast.WSProto itself does not use or require threads. implementation strategy including one that does not require threads for
environments where threads are unavailable. Beast.WebSocket itself does not
use or require threads.
[endsect] [endsect]
@@ -374,13 +416,13 @@ threads are unavailable. Beast.WSProto itself does not use or require threads.
[section:safety Thread Safety] [section:safety Thread Safety]
Like a regular asio socket, a `websocket::stream` is not thread safe. Callers Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is
are responsible for synchronizing operations on the socket using an implicit not thread safe. Callers are responsible for synchronizing operations on the
or explicit strand, as per the Asio documentation. The asynchronous interface socket using an implicit or explicit strand, as per the Asio documentation.
supports one active read and one active write simultaneously. Undefined The asynchronous interface supports one active read and one active write
behavior results if two or more reads or two or more writes are attempted simultaneously. Undefined behavior results if two or more reads or two or
concurrently. Caller initiated WebSocket ping, pong, and close operations more writes are attempted concurrently. Caller initiated WebSocket ping, pong,
each count as an active write. and close operations each count as an active write.
The implementation uses composed asynchronous operations internally; a high The implementation uses composed asynchronous operations internally; a high
level read can cause both reads and writes to take place on the underlying level read can cause both reads and writes to take place on the underlying

View File

@@ -157,25 +157,35 @@ public:
return next_layer_.async_write_some(buffers, return next_layer_.async_write_some(buffers,
std::forward<WriteHandler>(handler)); std::forward<WriteHandler>(handler));
} }
friend
void
teardown(fail_stream<NextLayer>& stream,
boost::system::error_code& ec)
{
if(stream.pfc_->fail(ec))
return;
websocket_helpers::call_teardown(stream.next_layer(), ec);
}
template<class TeardownHandler>
friend
void
async_teardown(fail_stream<NextLayer>& stream,
TeardownHandler&& handler)
{
error_code ec;
if(stream.pfc_->fail(ec))
{
stream.get_io_service().post(
bind_handler(std::move(handler), ec));
return;
}
websocket_helpers::call_async_teardown(
stream.next_layer(), std::forward<TeardownHandler>(handler));
}
}; };
template<class NextLayer>
void
teardown(fail_stream<NextLayer>& stream,
boost::system::error_code& ec)
{
websocket_helpers::call_teardown(stream.next_layer(), ec);
}
template<class NextLayer, class TeardownHandler>
void
async_teardown(fail_stream<NextLayer>& stream,
TeardownHandler&& handler)
{
websocket_helpers::call_async_teardown(
stream.next_layer(), std::forward<TeardownHandler>(handler));
}
} // test } // test
} // beast } // beast

View File

@@ -1,20 +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_UNIT_TEST_ABSTRACT_OSTREAM_HPP
#define BEAST_UNIT_TEST_ABSTRACT_OSTREAM_HPP
#include <beast/unit_test/basic_abstract_ostream.hpp>
namespace beast {
/** An abstract ostream for `char`. */
using abstract_ostream = basic_abstract_ostream <char>;
} // beast
#endif

View File

@@ -1,85 +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_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP
#include <beast/unit_test/basic_scoped_ostream.hpp>
#include <functional>
#include <memory>
#include <sstream>
namespace beast {
/** Abstraction for an output stream similar to std::basic_ostream. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
class basic_abstract_ostream
{
public:
using string_type = std::basic_string <CharT, Traits>;
using scoped_stream_type = basic_scoped_ostream <CharT, Traits>;
basic_abstract_ostream() = default;
virtual
~basic_abstract_ostream() = default;
basic_abstract_ostream (basic_abstract_ostream const&) = default;
basic_abstract_ostream& operator= (
basic_abstract_ostream const&) = default;
/** Returns `true` if the stream is active.
Inactive streams do not produce output.
*/
/** @{ */
virtual
bool
active() const
{
return true;
}
explicit
operator bool() const
{
return active();
}
/** @} */
/** Called to output each string. */
virtual
void
write (string_type const& s) = 0;
scoped_stream_type
operator<< (std::ostream& manip (std::ostream&))
{
return scoped_stream_type (manip,
[this](string_type const& s)
{
this->write (s);
});
}
template <class T>
scoped_stream_type
operator<< (T const& t)
{
return scoped_stream_type (t,
[this](string_type const& s)
{
this->write (s);
});
}
};
} // beast
#endif

View File

@@ -1,136 +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_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP
#include <functional>
#include <memory>
#include <sstream>
// gcc libstd++ doesn't have move constructors for basic_ostringstream
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316
//
#ifndef BEAST_NO_STDLIB_STREAM_MOVE
# ifdef __GLIBCXX__
# define BEAST_NO_STDLIB_STREAM_MOVE 1
# else
# define BEAST_NO_STDLIB_STREAM_MOVE 0
# endif
#endif
namespace beast {
template <
class CharT,
class Traits
>
class basic_abstract_ostream;
/** Scoped output stream that forwards to a functor upon destruction. */
template <
class CharT,
class Traits = std::char_traits <CharT>,
class Allocator = std::allocator <CharT>
>
class basic_scoped_ostream
{
private:
using handler_t = std::function <void (
std::basic_string <CharT, Traits, Allocator> const&)>;
using stream_type = std::basic_ostringstream <
CharT, Traits, Allocator>;
handler_t m_handler;
#if BEAST_NO_STDLIB_STREAM_MOVE
std::unique_ptr <stream_type> m_ss;
stream_type& stream()
{
return *m_ss;
}
#else
stream_type m_ss;
stream_type& stream()
{
return m_ss;
}
#endif
public:
using string_type = std::basic_string <CharT, Traits>;
// Disallow copy since that would duplicate the output
basic_scoped_ostream (basic_scoped_ostream const&) = delete;
basic_scoped_ostream& operator= (basic_scoped_ostream const) = delete;
template <class Handler>
explicit basic_scoped_ostream (Handler&& handler)
: m_handler (std::forward <Handler> (handler))
#if BEAST_NO_STDLIB_STREAM_MOVE
, m_ss (new stream_type())
#endif
{
}
template <class T, class Handler>
basic_scoped_ostream (T const& t, Handler&& handler)
: m_handler (std::forward <Handler> (handler))
#if BEAST_NO_STDLIB_STREAM_MOVE
, m_ss (new stream_type())
#endif
{
stream() << t;
}
basic_scoped_ostream (basic_abstract_ostream <
CharT, Traits>& ostream)
: m_handler (
[&](string_type const& s)
{
ostream.write (s);
})
{
}
basic_scoped_ostream (basic_scoped_ostream&& other)
: m_handler (std::move (other.m_handler))
, m_ss (std::move (other.m_ss))
{
}
~basic_scoped_ostream()
{
auto const& s (stream().str());
if (! s.empty())
m_handler (s);
}
basic_scoped_ostream&
operator<< (std::ostream& manip (std::ostream&))
{
stream() << manip;
return *this;
}
template <class T>
basic_scoped_ostream&
operator<< (T const& t)
{
stream() << t;
return *this;
}
};
} // beast
#endif

View File

@@ -1,60 +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_UNIT_TEST_BASIC_STD_OSTREAM_HPP
#define BEAST_UNIT_TEST_BASIC_STD_OSTREAM_HPP
#include <beast/unit_test/basic_abstract_ostream.hpp>
#include <ostream>
namespace beast {
/** Wraps an existing std::basic_ostream as an abstract_ostream. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
class basic_std_ostream
: public basic_abstract_ostream <CharT, Traits>
{
private:
using typename basic_abstract_ostream <CharT, Traits>::string_type;
std::reference_wrapper <std::ostream> m_stream;
public:
explicit basic_std_ostream (
std::basic_ostream <CharT, Traits>& stream)
: m_stream (stream)
{
}
void
write (string_type const& s) override
{
m_stream.get() << s << std::endl;
}
};
using std_ostream = basic_std_ostream <char>;
//------------------------------------------------------------------------------
/** Returns a basic_std_ostream using template argument deduction. */
template <
class CharT,
class Traits = std::char_traits <CharT>
>
basic_std_ostream <CharT, Traits>
make_std_ostream (std::basic_ostream <CharT, Traits>& stream)
{
return basic_std_ostream <CharT, Traits> (stream);
}
} // beast
#endif

View File

@@ -1,78 +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_UNIT_TEST_DEBUG_OSTREAM_HPP
#define BEAST_UNIT_TEST_DEBUG_OSTREAM_HPP
#include <beast/unit_test/abstract_ostream.hpp>
#include <iostream>
#ifdef _MSC_VER
# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
#include <windows.h>
# undef WIN32_LEAN_AND_MEAN
# else
#include <windows.h>
# endif
# ifdef min
# undef min
# endif
# ifdef max
# undef max
# endif
#endif
namespace beast {
#ifdef _MSC_VER
/** A basic_abstract_ostream that redirects output to an attached debugger. */
class debug_ostream
: public abstract_ostream
{
private:
bool m_debugger;
public:
debug_ostream()
: m_debugger (IsDebuggerPresent() != FALSE)
{
// Note that the check for an attached debugger is made only
// during construction time, for efficiency. A stream created before
// the debugger is attached will not have output redirected.
}
void
write (string_type const& s) override
{
if (m_debugger)
{
OutputDebugStringA ((s + "\n").c_str());
return;
}
std::cout << s << std::endl;
}
};
#else
class debug_ostream
: public abstract_ostream
{
public:
void
write (string_type const& s) override
{
std::cout << s << std::endl;
}
};
#endif
} // beast
#endif

View File

@@ -0,0 +1,144 @@
//
// 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_UNIT_TEST_DSTREAM_HPP
#define BEAST_UNIT_TEST_DSTREAM_HPP
#include <boost/utility/base_from_member.hpp>
#include <iostream>
#include <memory>
#include <ostream>
#include <sstream>
#include <string>
#ifdef _MSC_VER
# ifndef NOMINMAX
# define NOMINMAX 1
# endif
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
# undef WIN32_LEAN_AND_MEAN
# undef NOMINMAX
#endif
namespace beast {
namespace unit_test {
namespace detail {
#ifdef _MSC_VER
template<class CharT, class Traits, class Allocator>
class dstream_buf
: public std::basic_stringbuf<CharT, Traits, Allocator>
{
bool dbg_;
template<class T>
void write(T const*) = delete;
void write(char const* s)
{
if(dbg_)
OutputDebugStringA(s);
else
std::cout << s;
}
void write(wchar_t const* s)
{
if(dbg_)
OutputDebugStringW(s);
else
std::wcout << s;
}
public:
dstream_buf()
: dbg_(IsDebuggerPresent() != FALSE)
{
}
~dstream_buf()
{
sync();
}
int
sync() override
{
write(this->str().c_str());
this->str("");
return 0;
}
};
#else
template<class CharT, class Traits, class Allocator>
class dstream_buf
: public std::basic_stringbuf<CharT, Traits, Allocator>
{
template<class T>
void write(T const*) = delete;
void write(char const* s)
{
std::cout << s;
}
void write(wchar_t const* s)
{
std::wcout << s;
}
public:
~dstream_buf()
{
sync();
}
int
sync() override
{
write(this->str().c_str());
this->str("");
return 0;
}
};
#endif
} // detail
/// A std::ostream that redirects output to the debugger if attached.
template<
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
>
class basic_dstream
: private boost::base_from_member<
detail::dstream_buf<CharT, Traits, Allocator>>
, public std::basic_ostream<CharT, Traits>
{
public:
basic_dstream()
: std::basic_ostream<CharT, Traits>(&this->member)
{
}
};
using dstream = basic_dstream<char>;
using dwstream = basic_dstream<wchar_t>;
} // test
} // beast
#endif

View File

@@ -15,7 +15,8 @@ namespace unit_test {
namespace detail { namespace detail {
template <class = void> /// Holds test suites registered during static initialization.
inline
suite_list& suite_list&
global_suites() global_suites()
{ {
@@ -23,26 +24,20 @@ global_suites()
return s; return s;
} }
template <class Suite> template<class Suite>
struct insert_suite struct insert_suite
{ {
template <class = void> insert_suite(char const* name, char const* module,
insert_suite (char const* name, char const* module, char const* library, bool manual)
char const* library, bool manual); {
global_suites().insert<Suite>(
name, module, library, manual);
}
}; };
template <class Suite>
template <class>
insert_suite<Suite>::insert_suite (char const* name,
char const* module, char const* library, bool manual)
{
global_suites().insert <Suite> (
name, module, library, manual);
}
} // detail } // detail
/** Holds suites registered during static initialization. */ /// Holds test suites registered during static initialization.
inline inline
suite_list const& suite_list const&
global_suites() global_suites()

View File

@@ -6,12 +6,13 @@
// //
#include <beast/unit_test/amount.hpp> #include <beast/unit_test/amount.hpp>
#include <beast/unit_test/dstream.hpp>
#include <beast/unit_test/global_suites.hpp> #include <beast/unit_test/global_suites.hpp>
#include <beast/unit_test/match.hpp> #include <beast/unit_test/match.hpp>
#include <beast/unit_test/reporter.hpp> #include <beast/unit_test/reporter.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <beast/unit_test/debug_ostream.hpp>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
@@ -25,11 +26,10 @@
# endif # endif
#endif #endif
#include <cstdlib>
namespace beast { namespace beast {
namespace unit_test { namespace unit_test {
static
std::string std::string
prefix(suite_info const& s) prefix(suite_info const& s)
{ {
@@ -38,32 +38,34 @@ prefix(suite_info const& s)
return " "; return " ";
} }
template<class Log> static
void void
print(Log& log, suite_list const& c) print(std::ostream& os, suite_list const& c)
{ {
std::size_t manual = 0; std::size_t manual = 0;
for(auto const& s : c) for(auto const& s : c)
{ {
log << os << prefix (s) << s.full_name() << '\n';
prefix (s) <<
s.full_name();
if(s.manual()) if(s.manual())
++manual; ++manual;
} }
log << os <<
amount(c.size(), "suite") << " total, " << amount(c.size(), "suite") << " total, " <<
amount(manual, "manual suite") amount(manual, "manual suite") <<
'\n'
; ;
} }
template<class Log> // Print the list of suites
// Used with the --print command line option
static
void void
print(Log& log) print(std::ostream& os)
{ {
log << "------------------------------------------"; os << "------------------------------------------\n";
print(log, global_suites()); print(os, global_suites());
log << "------------------------------------------"; os << "------------------------------------------" <<
std::endl;
} }
} // unit_test } // unit_test
@@ -97,11 +99,11 @@ int main(int ac, char const* av[])
po::store(po::parse_command_line(ac, av, desc), vm); po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm); po::notify(vm);
beast::debug_ostream log; dstream log;
if(vm.count("help")) if(vm.count("help"))
{ {
log << desc; log << desc << std::endl;
} }
else if(vm.count("print")) else if(vm.count("print"))
{ {

View File

@@ -1,67 +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_UNIT_TEST_PRINT_H_INCLUDED
#define BEAST_UNIT_TEST_PRINT_H_INCLUDED
#include <beast/unit_test/amount.hpp>
#include <beast/unit_test/results.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <beast/unit_test/basic_std_ostream.hpp>
#include <iostream>
#include <string>
namespace beast {
namespace unit_test {
/** Write test results to the specified output stream. */
/** @{ */
template <class = void>
void
print (results const& r, abstract_ostream& stream)
{
for (auto const& s : r)
{
for (auto const& c : s)
{
stream <<
s.name() <<
(c.name().empty() ? "" : ("." + c.name()));
std::size_t i (1);
for (auto const& t : c.tests)
{
if (! t.pass)
stream <<
"#" << i <<
" failed: " << t.reason;
++i;
}
}
}
stream <<
amount (r.size(), "suite") << ", " <<
amount (r.cases(), "case") << ", " <<
amount (r.total(), "test") << " total, " <<
amount (r.failed(), "failure")
;
}
template <class = void>
void
print (results const& r, std::ostream& stream = std::cout)
{
auto s (make_std_ostream (stream));
print (r, s);
}
} // unit_test
} // beast
#endif

View File

@@ -10,8 +10,6 @@
#include <beast/unit_test/amount.hpp> #include <beast/unit_test/amount.hpp>
#include <beast/unit_test/recorder.hpp> #include <beast/unit_test/recorder.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <beast/unit_test/basic_std_ostream.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@@ -30,7 +28,7 @@ namespace detail {
/** A simple test runner that writes everything to a stream in real time. /** A simple test runner that writes everything to a stream in real time.
The totals are output when the object is destroyed. The totals are output when the object is destroyed.
*/ */
template <class = void> template<class = void>
class reporter : public runner class reporter : public runner
{ {
private: private:
@@ -42,7 +40,11 @@ private:
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 0; std::size_t failed = 0;
case_results (std::string const& name_ = ""); explicit
case_results(std::string name_ = "")
: name(std::move(name_))
{
}
}; };
struct suite_results struct suite_results
@@ -51,14 +53,16 @@ private:
std::size_t cases = 0; std::size_t cases = 0;
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 0; std::size_t failed = 0;
typename clock_type::time_point start = typename clock_type::time_point start = clock_type::now();
clock_type::now();
explicit explicit
suite_results (std::string const& name_ = ""); suite_results(std::string const& name_ = "")
: name(std::move(name_))
{
}
void void
add (case_results const& r); add(case_results const& r);
}; };
struct results struct results
@@ -76,35 +80,30 @@ private:
std::size_t total = 0; std::size_t total = 0;
std::size_t failed = 0; std::size_t failed = 0;
std::vector<run_time> top; std::vector<run_time> top;
typename clock_type::time_point start = typename clock_type::time_point start = clock_type::now();
clock_type::now();
void void
add (suite_results const& r); add (suite_results const& r);
}; };
boost::optional <std_ostream> std_ostream_; std::ostream& os_;
std::reference_wrapper <beast::abstract_ostream> stream_;
results results_; results results_;
suite_results suite_results_; suite_results suite_results_;
case_results case_results_; case_results case_results_;
public: public:
reporter (reporter const&) = delete; reporter(reporter const&) = delete;
reporter& operator= (reporter const&) = delete; reporter& operator=(reporter const&) = delete;
~reporter(); ~reporter();
explicit explicit
reporter (std::ostream& stream = std::cout); reporter(std::ostream& os = std::cout);
explicit
reporter (beast::abstract_ostream& stream);
private: private:
static static
std::string std::string
fmtdur (typename clock_type::duration const& d); fmtdur(typename clock_type::duration const& d);
virtual virtual
void void
@@ -137,42 +136,27 @@ private:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class _> template<class _>
reporter<_>::case_results::case_results (
std::string const& name_)
: name (name_)
{
}
template <class _>
reporter<_>::suite_results::suite_results (
std::string const& name_)
: name (name_)
{
}
template <class _>
void void
reporter<_>::suite_results::add (case_results const& r) reporter<_>::
suite_results::add(case_results const& r)
{ {
++cases; ++cases;
total += r.total; total += r.total;
failed += r.failed; failed += r.failed;
} }
template <class _> template<class _>
void void
reporter<_>::results::add ( reporter<_>::
suite_results const& r) results::add(suite_results const& r)
{ {
++suites; ++suites;
total += r.total; total += r.total;
cases += r.cases; cases += r.cases;
failed += r.failed; failed += r.failed;
auto const elapsed = clock_type::now() - r.start;
auto const elapsed = if (elapsed >= std::chrono::seconds{1})
clock_type::now() - r.start;
if (elapsed >= std::chrono::seconds(1))
{ {
auto const iter = std::lower_bound(top.begin(), auto const iter = std::lower_bound(top.begin(),
top.end(), elapsed, top.end(), elapsed,
@@ -196,50 +180,40 @@ reporter<_>::results::add (
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class _> template<class _>
reporter<_>::reporter ( reporter<_>::
std::ostream& stream) reporter(std::ostream& os)
: std_ostream_ (std::ref (stream)) : os_(os)
, stream_ (*std_ostream_)
{ {
} }
template <class _> template<class _>
reporter<_>::~reporter() reporter<_>::~reporter()
{ {
if (results_.top.size() > 0) if(results_.top.size() > 0)
{ {
stream_.get() << "Longest suite times:"; os_ << "Longest suite times:\n";
for (auto const& i : results_.top) for(auto const& i : results_.top)
stream_.get() << std::setw(8) << os_ << std::setw(8) <<
fmtdur(i.second) << " " << i.first; fmtdur(i.second) << " " << i.first << '\n';
} }
auto const elapsed = auto const elapsed = clock_type::now() - results_.start;
clock_type::now() - results_.start; os_ <<
stream_.get() <<
fmtdur(elapsed) << ", " << fmtdur(elapsed) << ", " <<
amount (results_.suites, "suite") << ", " << amount{results_.suites, "suite"} << ", " <<
amount (results_.cases, "case") << ", " << amount{results_.cases, "case"} << ", " <<
amount (results_.total, "test") << " total, " << amount{results_.total, "test"} << " total, " <<
amount (results_.failed, "failure"); amount{results_.failed, "failure"} <<
std::endl;
} }
template <class _> template<class _>
reporter<_>::reporter (
abstract_ostream& stream)
: stream_ (stream)
{
}
template <class _>
std::string std::string
reporter<_>::fmtdur ( reporter<_>::fmtdur(typename clock_type::duration const& d)
typename clock_type::duration const& d)
{ {
using namespace std::chrono; using namespace std::chrono;
auto const ms = auto const ms = duration_cast<milliseconds>(d);
duration_cast<milliseconds>(d); if (ms < seconds{1})
if (ms < seconds(1))
return std::to_string(ms.count()) + "ms"; return std::to_string(ms.count()) + "ms";
std::stringstream ss; std::stringstream ss;
ss << std::fixed << std::setprecision(1) << ss << std::fixed << std::setprecision(1) <<
@@ -247,67 +221,68 @@ reporter<_>::fmtdur (
return ss.str(); return ss.str();
} }
template <class _> template<class _>
void void
reporter<_>::on_suite_begin ( reporter<_>::
suite_info const& info) on_suite_begin(suite_info const& info)
{ {
suite_results_ = suite_results (info.full_name()); suite_results_ = suite_results{info.full_name()};
} }
template <class _> template<class _>
void void
reporter<_>::on_suite_end() reporter<_>::on_suite_end()
{ {
results_.add (suite_results_); results_.add(suite_results_);
} }
template <class _> template<class _>
void void
reporter<_>::on_case_begin ( reporter<_>::
std::string const& name) on_case_begin(std::string const& name)
{ {
case_results_ = case_results (name); case_results_ = case_results (name);
os_ <<
stream_.get() <<
suite_results_.name << suite_results_.name <<
(case_results_.name.empty() ? (case_results_.name.empty() ?
"" : (" " + case_results_.name)); "" : (" " + case_results_.name)) << std::endl;
} }
template <class _> template<class _>
void void
reporter<_>::on_case_end() reporter<_>::
on_case_end()
{ {
suite_results_.add (case_results_); suite_results_.add(case_results_);
} }
template <class _> template<class _>
void void
reporter<_>::on_pass() reporter<_>::
on_pass()
{ {
++case_results_.total; ++case_results_.total;
} }
template <class _> template<class _>
void void
reporter<_>::on_fail ( reporter<_>::
std::string const& reason) on_fail(std::string const& reason)
{ {
++case_results_.failed; ++case_results_.failed;
++case_results_.total; ++case_results_.total;
stream_.get() << os_ <<
"#" << case_results_.total << "#" << case_results_.total << " failed" <<
" failed" << (reason.empty() ? "" : ": ") << reason << std::endl;
(reason.empty() ? "" : ": ") << reason;
} }
template <class _> template<class _>
void void
reporter<_>::on_log ( reporter<_>::
std::string const& s) on_log(std::string const& s)
{ {
stream_.get() << s; os_ << s;
os_.flush();
} }
} // detail } // detail

View File

@@ -9,9 +9,9 @@
#define BEAST_UNIT_TEST_RUNNER_H_INCLUDED #define BEAST_UNIT_TEST_RUNNER_H_INCLUDED
#include <beast/unit_test/suite_info.hpp> #include <beast/unit_test/suite_info.hpp>
#include <beast/unit_test/abstract_ostream.hpp>
#include <cassert> #include <cassert>
#include <mutex> #include <mutex>
#include <ostream>
#include <string> #include <string>
namespace beast { namespace beast {
@@ -23,28 +23,6 @@ namespace unit_test {
*/ */
class runner class runner
{ {
private:
// Reroutes log output to the runner
class stream_t : public abstract_ostream
{
private:
runner& owner_;
public:
stream_t() = delete;
stream_t& operator= (stream_t const&) = delete;
template <class = void>
stream_t (runner& owner);
void
write (string_type const& s) override
{
owner_.log (s);
}
};
stream_t stream_;
std::string arg_; std::string arg_;
bool default_ = false; bool default_ = false;
bool failed_ = false; bool failed_ = false;
@@ -52,21 +30,20 @@ private:
std::recursive_mutex mutex_; std::recursive_mutex mutex_;
public: public:
runner() = default;
virtual ~runner() = default; virtual ~runner() = default;
runner (runner const&) = default; runner(runner const&) = delete;
runner& operator= (runner const&) = default; runner& operator=(runner const&) = delete;
template <class = void>
runner();
/** Set the argument string. /** Set the argument string.
The argument string is available to suites and The argument string is available to suites and
allows for customization of the test. Each suite allows for customization of the test. Each suite
defines its own syntax for the argumnet string. defines its own syntax for the argumnet string.
The same argument is passed to all suites. The same argument is passed to all suites.
*/ */
void void
arg (std::string const& s) arg(std::string const& s)
{ {
arg_ = s; arg_ = s;
} }
@@ -81,7 +58,7 @@ public:
/** Run the specified suite. /** Run the specified suite.
@return `true` if any conditions failed. @return `true` if any conditions failed.
*/ */
template <class = void> template<class = void>
bool bool
run (suite_info const& s); run (suite_info const& s);
@@ -124,69 +101,63 @@ public:
bool bool
run_each_if (SequenceContainer const& c, Pred pred = Pred{}); run_each_if (SequenceContainer const& c, Pred pred = Pred{});
private: protected:
// //
// Overrides // Overrides
// //
/** Called when a new suite starts. */ /// Called when a new suite starts.
virtual virtual
void void
on_suite_begin (suite_info const&) on_suite_begin(suite_info const&)
{ {
} }
/** Called when a suite ends. */ /// Called when a suite ends.
virtual virtual
void void
on_suite_end() on_suite_end()
{ {
} }
/** Called when a new case starts. */ /// Called when a new case starts.
virtual virtual
void void
on_case_begin (std::string const&) on_case_begin(std::string const&)
{ {
} }
/** Called when a new case ends. */ /// Called when a new case ends.
virtual virtual
void void
on_case_end() on_case_end()
{ {
} }
/** Called for each passing condition. */ /// Called for each passing condition.
virtual virtual
void void
on_pass () on_pass()
{ {
} }
/** Called for each failing condition. */ /// Called for each failing condition.
virtual virtual
void void
on_fail (std::string const&) on_fail(std::string const&)
{ {
} }
/** Called when a test logs output. */ /// Called when a test logs output.
virtual virtual
void void
on_log (std::string const&) on_log(std::string const&)
{ {
} }
private: private:
friend class suite; friend class suite;
abstract_ostream&
stream()
{
return stream_;
}
// Start a new testcase. // Start a new testcase.
template <class = void> template <class = void>
void void
@@ -207,20 +178,6 @@ private:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class>
runner::stream_t::stream_t (runner& owner)
: owner_ (owner)
{
}
//------------------------------------------------------------------------------
template <class>
runner::runner()
: stream_ (*this)
{
}
template <class> template <class>
bool bool
runner::run (suite_info const& s) runner::run (suite_info const& s)

View File

@@ -9,15 +9,23 @@
#define BEAST_UNIT_TEST_SUITE_HPP #define BEAST_UNIT_TEST_SUITE_HPP
#include <beast/unit_test/runner.hpp> #include <beast/unit_test/runner.hpp>
#include <string> #include <ostream>
#include <sstream> #include <sstream>
#include <string>
namespace beast { namespace beast {
namespace unit_test { namespace unit_test {
class thread; class thread;
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
/** A testsuite class. /** A testsuite class.
Derived classes execute a series of testcases, where each testcase is Derived classes execute a series of testcases, where each testcase is
a series of pass/fail tests. To provide a unit test using this class, a series of pass/fail tests. To provide a unit test using this class,
derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a
@@ -25,13 +33,6 @@ class thread;
*/ */
class suite class suite
{ {
public:
enum abort_t
{
no_abort_on_fail,
abort_on_fail
};
private: private:
bool abort_ = false; bool abort_ = false;
bool aborted_ = false; bool aborted_ = false;
@@ -44,95 +45,100 @@ private:
char const* char const*
what() const noexcept override what() const noexcept override
{ {
return "suite aborted"; return "test suite aborted";
} }
}; };
public: template<class CharT, class Traits, class Allocator>
// Memberspace class log_buf
class log_t : public std::basic_stringbuf<CharT, Traits, Allocator>
{ {
private: suite& suite_;
friend class suite;
suite* suite_ = nullptr;
public: public:
log_t () = default; explicit
log_buf(suite& self)
: suite_(self)
{
}
template <class T> ~log_buf()
abstract_ostream::scoped_stream_type {
operator<< (T const& t); sync();
}
/** Returns the raw stream used for output. */ int
abstract_ostream& sync() override
stream(); {
auto const& s = this->str();
if(s.size() > 0)
suite_.runner_->on_log(s);
this->str("");
return 0;
}
};
template<
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>
>
class log_os : public std::basic_ostream<CharT, Traits>
{
log_buf<CharT, Traits, Allocator> buf_;
public:
explicit
log_os(suite& self)
: std::basic_ostream<CharT, Traits>(&buf_)
, buf_(self)
{
}
}; };
private:
class scoped_testcase; class scoped_testcase;
// Memberspace
class testcase_t class testcase_t
{ {
private: suite& suite_;
friend class suite;
suite* suite_ = nullptr;
std::stringstream ss_; std::stringstream ss_;
public: public:
testcase_t() = default; explicit
testcase_t(suite& self)
: suite_(self)
{
}
/** Open a new testcase. /** Open a new testcase.
A testcase is a series of evaluated test conditions. A test suite
may have multiple test cases. A test is associated with the last A testcase is a series of evaluated test conditions. A test
opened testcase. When the test first runs, a default unnamed suite may have multiple test cases. A test is associated with
case is opened. Tests with only one case may omit the call the last opened testcase. When the test first runs, a default
to testcase. unnamed case is opened. Tests with only one case may omit the
@param abort If `true`, the suite will be stopped on first failure. call to testcase.
@param abort Determines if suite continues running after a failure.
*/ */
void void
operator() (std::string const& name, operator()(std::string const& name,
abort_t abort = no_abort_on_fail); abort_t abort = no_abort_on_fail);
/** Stream style composition of testcase names. */
/** @{ */
scoped_testcase scoped_testcase
operator() (abort_t abort); operator()(abort_t abort);
template <class T> template <class T>
scoped_testcase scoped_testcase
operator<< (T const& t); operator<<(T const& t);
/** @} */
}; };
public: public:
/** Type for scoped stream logging. /** Logging output stream.
To use this type, declare a local variable of the type
on the stack in derived class member function and construct
it from log.stream();
@code Text sent to the log output stream will be forwarded to
the output stream associated with the runner.
scoped_stream ss (log.stream();
ss << "Hello" << std::endl;
ss << "world" << std::endl;
@endcode
Streams constructed in this fashion will not have the line
ending automatically appended.
Thread safety:
The scoped_stream may only be used by one thread.
Multiline output sent to the stream will be atomically
written to the underlying abstract_Ostream
*/ */
using scoped_stream = abstract_ostream::scoped_stream_type; log_os<char> log;
/** Memberspace for logging. */
log_t log;
/** Memberspace for declaring test cases. */ /** Memberspace for declaring test cases. */
testcase_t testcase; testcase_t testcase;
@@ -145,6 +151,12 @@ public:
return *p_this_suite(); return *p_this_suite();
} }
suite()
: log(*this)
, testcase(*this)
{
}
/** Invokes the test using the specified runner. /** Invokes the test using the specified runner.
Data members are set up here instead of the constructor as a Data members are set up here instead of the constructor as a
convenience to writing the derived class to avoid repetition of convenience to writing the derived class to avoid repetition of
@@ -272,84 +284,51 @@ private:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class T>
inline
abstract_ostream::scoped_stream_type
suite::log_t::operator<< (T const& t)
{
return suite_->runner_->stream() << t;
}
/** Returns the raw stream used for output. */
inline
abstract_ostream&
suite::log_t::stream()
{
return suite_->runner_->stream();
}
//------------------------------------------------------------------------------
// Helper for streaming testcase names // Helper for streaming testcase names
class suite::scoped_testcase class suite::scoped_testcase
{ {
private: private:
suite* suite_; suite& suite_;
std::stringstream* ss_; std::stringstream& ss_;
public: public:
~scoped_testcase(); scoped_testcase& operator=(scoped_testcase const&) = delete;
scoped_testcase (suite* s, std::stringstream* ss); ~scoped_testcase()
{
auto const& name = ss_.str();
if(! name.empty())
suite_.runner_->testcase (name);
}
template <class T> scoped_testcase(suite& self, std::stringstream& ss)
scoped_testcase (suite* s, std::stringstream* ss, T const& t); : suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
}
scoped_testcase& operator= (scoped_testcase const&) = delete; template<class T>
scoped_testcase(suite& self,
std::stringstream& ss, T const& t)
: suite_(self)
, ss_(ss)
{
ss_.clear();
ss_.str({});
ss_ << t;
}
template <class T> template<class T>
scoped_testcase& scoped_testcase&
operator<< (T const& t); operator<<(T const& t)
{
ss_ << t;
return *this;
}
}; };
inline
suite::scoped_testcase::~scoped_testcase()
{
auto const& name (ss_->str());
if (! name.empty())
suite_->runner_->testcase (name);
}
inline
suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss)
: suite_ (s)
, ss_ (ss)
{
ss_->clear();
ss_->str({});
}
template <class T>
inline
suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss, T const& t)
: suite_ (s)
, ss_ (ss)
{
ss_->clear();
ss_->str({});
*ss_ << t;
}
template <class T>
inline
suite::scoped_testcase&
suite::scoped_testcase::operator<< (T const& t)
{
*ss_ << t;
return *this;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
inline inline
@@ -357,16 +336,16 @@ void
suite::testcase_t::operator() (std::string const& name, suite::testcase_t::operator() (std::string const& name,
abort_t abort) abort_t abort)
{ {
suite_->abort_ = abort == abort_on_fail; suite_.abort_ = abort == abort_on_fail;
suite_->runner_->testcase (name); suite_.runner_->testcase (name);
} }
inline inline
suite::scoped_testcase suite::scoped_testcase
suite::testcase_t::operator() (abort_t abort) suite::testcase_t::operator() (abort_t abort)
{ {
suite_->abort_ = abort == abort_on_fail; suite_.abort_ = abort == abort_on_fail;
return { suite_, &ss_ }; return { suite_, ss_ };
} }
template<class T> template<class T>
@@ -374,7 +353,7 @@ inline
suite::scoped_testcase suite::scoped_testcase
suite::testcase_t::operator<< (T const& t) suite::testcase_t::operator<< (T const& t)
{ {
return { suite_, &ss_, t }; return { suite_, ss_, t };
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -511,8 +490,6 @@ void
suite::run (runner& r) suite::run (runner& r)
{ {
runner_ = &r; runner_ = &r;
log.suite_ = this;
testcase.suite_ = this;
try try
{ {

View File

@@ -8,6 +8,7 @@
#ifndef BEAST_UNIT_TEST_SUITE_INFO_HPP #ifndef BEAST_UNIT_TEST_SUITE_INFO_HPP
#define BEAST_UNIT_TEST_SUITE_INFO_HPP #define BEAST_UNIT_TEST_SUITE_INFO_HPP
#include <cstring>
#include <functional> #include <functional>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -20,19 +21,28 @@ class runner;
/** Associates a unit test type with metadata. */ /** Associates a unit test type with metadata. */
class suite_info class suite_info
{ {
private: using run_type = std::function<void(runner&)>;
using run_type = std::function <void (runner&)>;
std::string name_; std::string name_;
std::string module_; std::string module_;
std::string library_; std::string library_;
bool m_manual; bool manual_;
run_type m_run; run_type run_;
public: public:
template <class = void> suite_info(
suite_info (std::string const& name, std::string const& module, std::string name,
std::string const& library, bool manual, run_type run); std::string module,
std::string library,
bool manual,
run_type run)
: name_(std::move(name))
, module_(std::move(module))
, library_(std::move(library))
, manual_(manual)
, run_(std::move(run))
{
}
std::string const& std::string const&
name() const name() const
@@ -52,61 +62,58 @@ public:
return library_; return library_;
} }
/** Returns `true` if this suite only runs manually. */ /// Returns `true` if this suite only runs manually.
bool bool
manual() const manual() const
{ {
return m_manual; return manual_;
} }
/** Return the canonical suite name as a string. */ /// Return the canonical suite name as a string.
template <class = void>
std::string std::string
full_name() const; full_name() const
/** Run a new instance of the associated test suite. */
void
run (runner& r) const
{ {
m_run (r); return library_ + "." + module_ + "." + name_;
}
/// Run a new instance of the associated test suite.
void
run(runner& r) const
{
run_ (r);
}
friend
bool
operator<(suite_info const& lhs, suite_info const& rhs)
{
return
std::tie(lhs.library_, lhs.module_, lhs.name_) <
std::tie(rhs.library_, rhs.module_, rhs.name_);
} }
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class> /// Convenience for producing suite_info for a given test type.
suite_info::suite_info (std::string const& name, std::string const& module, template<class Suite>
std::string const& library, bool manual, run_type run)
: name_ (name)
, module_ (module)
, library_ (library)
, m_manual (manual)
, m_run (std::move (run))
{
}
template <class>
std::string
suite_info::full_name() const
{
return library_ + "." + module_ + "." + name_;
}
inline
bool
operator< (suite_info const& lhs, suite_info const& rhs)
{
return lhs.full_name() < rhs.full_name();
}
/** Convenience for producing suite_info for a given test type. */
template <class Suite>
suite_info suite_info
make_suite_info (std::string const& name, std::string const& module, make_suite_info(
std::string const& library, bool manual) std::string name,
std::string module,
std::string library,
bool manual)
{ {
return suite_info(name, module, library, manual, return suite_info(
[](runner& r) { return Suite{}(r); }); std::move(name),
std::move(module),
std::move(library),
manual,
[](runner& r)
{
Suite{}(r);
}
);
} }
} // unit_test } // unit_test

View File

@@ -18,23 +18,27 @@
namespace beast { namespace beast {
namespace unit_test { namespace unit_test {
/** A container of test suites. */ /// A container of test suites.
class suite_list class suite_list
: public detail::const_container <std::set <suite_info>> : public detail::const_container <std::set <suite_info>>
{ {
private: private:
#ifndef NDEBUG #ifndef NDEBUG
std::unordered_set <std::string> names_; std::unordered_set<std::string> names_;
std::unordered_set <std::type_index> classes_; std::unordered_set<std::type_index> classes_;
#endif #endif
public: public:
/** Insert a suite into the set. /** Insert a suite into the set.
The suite must not already exist. The suite must not already exist.
*/ */
template <class Suite> template <class Suite>
void void
insert (char const* name, char const* module, char const* library, insert(
char const* name,
char const* module,
char const* library,
bool manual); bool manual);
}; };
@@ -42,7 +46,10 @@ public:
template <class Suite> template <class Suite>
void void
suite_list::insert (char const* name, char const* module, char const* library, suite_list::insert(
char const* name,
char const* module,
char const* library,
bool manual) bool manual)
{ {
#ifndef NDEBUG #ifndef NDEBUG
@@ -59,9 +66,8 @@ suite_list::insert (char const* name, char const* module, char const* library,
assert (result.second); // Duplicate type assert (result.second); // Duplicate type
} }
#endif #endif
cont().emplace(make_suite_info<Suite>(
cont().emplace (std::move (make_suite_info <Suite> ( name, module, library, manual));
name, module, library, manual)));
} }
} // unit_test } // unit_test

View File

@@ -1,73 +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_DETAIL_TEMP_DIR_H_INCLUDED
#define BEAST_DETAIL_TEMP_DIR_H_INCLUDED
#include <boost/filesystem.hpp>
#include <string>
namespace beast {
namespace detail {
/** RAII temporary directory.
The directory and all its contents are deleted when
the instance of `temp_dir` is destroyed.
*/
class temp_dir
{
boost::filesystem::path path_;
public:
#if ! GENERATING_DOCS
temp_dir(const temp_dir&) = delete;
temp_dir& operator=(const temp_dir&) = delete;
#endif
/// Construct a temporary directory.
temp_dir()
{
auto const dir =
boost::filesystem::temp_directory_path();
do
{
path_ =
dir / boost::filesystem::unique_path();
}
while(boost::filesystem::exists(path_));
boost::filesystem::create_directory (path_);
}
/// Destroy a temporary directory.
~temp_dir()
{
boost::filesystem::remove_all (path_);
}
/// Get the native path for the temporary directory
std::string
path() const
{
return path_.string();
}
/** Get the native path for the a file.
The file does not need to exist.
*/
std::string
file(std::string const& name) const
{
return (path_ / name).string();
}
};
} // detail
} // beast
#endif

View File

@@ -1,23 +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_DETAIL_UNIT_TEST_H_INCLUDED
#define BEAST_DETAIL_UNIT_TEST_H_INCLUDED
#include <beast/detail/unit_test/amount.h>
#include <beast/detail/unit_test/print.h>
#include <beast/detail/unit_test/global_suites.h>
#include <beast/detail/unit_test/match.h>
#include <beast/detail/unit_test/recorder.h>
#include <beast/detail/unit_test/reporter.h>
#include <beast/detail/unit_test/results.h>
#include <beast/detail/unit_test/runner.h>
#include <beast/detail/unit_test/suite.h>
#include <beast/detail/unit_test/suite_info.h>
#include <beast/detail/unit_test/suite_list.h>
#endif

View File

@@ -0,0 +1,156 @@
//
// 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 <beast/core/error.hpp>
#include <boost/asio/buffer.hpp>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
namespace detail {
template<class T>
class has_value_type
{
template<class U, class R =
typename U::value_type>
static std::true_type check(int);
template<class>
static std::false_type check(...);
using type = decltype(check<T>(0));
public:
static bool constexpr value = type::value;
};
template<class T, bool B = has_value_type<T>::value>
struct extract_value_type
{
using type = void;
};
template<class T>
struct extract_value_type<T, true>
{
using type = typename T::value_type;
};
template<class T>
class has_reader
{
template<class U, class R =
typename U::reader>
static std::true_type check(int);
template<class>
static std::false_type check(...);
public:
using type = decltype(check<T>(0));
};
template<class T>
class has_writer
{
template<class U, class R =
typename U::writer>
static std::true_type check(int);
template<class>
static std::false_type check(...);
public:
using type = decltype(check<T>(0));
};
template<class T>
struct is_Body
{
using type = std::integral_constant<bool,
has_value_type<T>::value &&
std::is_default_constructible<
typename extract_value_type<T>::type>::value
>;
};
template<class T>
class is_Parser
{
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().complete()),
bool>>
static R check1(int);
template<class>
static std::false_type check1(...);
using type1 = decltype(check1<T>(0));
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().write(
std::declval<boost::asio::const_buffer const&>(),
std::declval<error_code&>())),
std::size_t>>
static R check2(int);
template<class>
static std::false_type check2(...);
using type2 = decltype(check2<T>(0));
template<class U, class R = decltype(
std::declval<U>().write_eof(std::declval<error_code&>()),
std::true_type{})>
static R check3(int);
template<class>
static std::false_type check3(...);
using type3 = decltype(check3<T>(0));
public:
using type = std::integral_constant<bool,
type1::value &&
type2::value &&
type3::value
>;
};
} // detail
/// Determine if `T` meets the requirements of `Body`.
template<class T>
#if GENERATING_DOCS
struct is_Body : std::integral_constant<bool, ...>{};
#else
using is_Body = typename detail::is_Body<T>::type;
#endif
/// Determine if `T` meets the requirements of `ReadableBody`.
template<class T>
#if GENERATING_DOCS
struct is_ReadableBody : std::integral_constant<bool, ...>{};
#else
using is_ReadableBody = typename detail::has_reader<T>::type;
#endif
/// Determine if `T` meets the requirements of `WritableBody`.
template<class T>
#if GENERATING_DOCS
struct is_WritableBody : std::integral_constant<bool, ...>{};
#else
using is_WritableBody = typename detail::has_writer<T>::type;
#endif
/// Determine if `T` meets the requirements of `Parser`.
template<class T>
#if GENERATING_DOCS
struct is_Parser : std::integral_constant<bool, ...>{};
#else
using is_Parser = typename detail::is_Parser<T>::type;
#endif
} // http
} // beast
#endif

View File

@@ -34,20 +34,6 @@ struct empty_body
private: private:
#endif #endif
struct reader
{
template<bool isRequest, class Headers>
explicit
reader(message<isRequest, empty_body, Headers>&)
{
}
void
write(void const*, std::size_t, error_code&)
{
}
};
struct writer struct writer
{ {
writer(writer const&) = delete; writer(writer const&) = delete;

View File

@@ -463,8 +463,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_header_field: case s_header_field:
{ {
for(; p != end; ch = *++p) for(; p != end; ++p)
{ {
ch = *p;
auto c = to_field_char(ch); auto c = to_field_char(ch);
if(! c) if(! c)
break; break;
@@ -660,8 +661,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec)
case s_header_value_text: case s_header_value_text:
{ {
for(; p != end; ch = *++p) for(; p != end; ++p)
{ {
ch = *p;
if(ch == '\r') if(ch == '\r')
{ {
if(cb(nullptr)) if(cb(nullptr))

View File

@@ -105,33 +105,6 @@ prepare_content_length(prepare_info& pi,
} // detail } // detail
template<bool isRequest, class Body, class Headers>
void
prepare_connection(
message_v1<isRequest, Body, Headers>& 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< template<
bool isRequest, class Body, class Headers, bool isRequest, class Body, class Headers,
class... Options> class... Options>

View File

@@ -8,8 +8,8 @@
#ifndef BEAST_HTTP_IMPL_READ_IPP_HPP #ifndef BEAST_HTTP_IMPL_READ_IPP_HPP
#define BEAST_HTTP_IMPL_READ_IPP_HPP #define BEAST_HTTP_IMPL_READ_IPP_HPP
#include <beast/http/concepts.hpp>
#include <beast/http/parser_v1.hpp> #include <beast/http/parser_v1.hpp>
#include <beast/http/type_check.hpp>
#include <beast/core/bind_handler.hpp> #include <beast/core/bind_handler.hpp>
#include <beast/core/handler_alloc.hpp> #include <beast/core/handler_alloc.hpp>
#include <beast/core/stream_concepts.hpp> #include <beast/core/stream_concepts.hpp>
@@ -323,6 +323,12 @@ void
parse(SyncReadStream& stream, parse(SyncReadStream& stream,
Streambuf& streambuf, Parser& parser) Streambuf& streambuf, Parser& parser)
{ {
static_assert(is_SyncReadStream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met");
static_assert(is_Parser<Parser>::value,
"Parser requirements not met");
error_code ec; error_code ec;
parse(stream, streambuf, parser, ec); parse(stream, streambuf, parser, ec);
if(ec) if(ec)
@@ -403,6 +409,8 @@ read(SyncReadStream& stream, Streambuf& streambuf,
"SyncReadStream requirements not met"); "SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value, static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met"); "Streambuf requirements not met");
static_assert(is_ReadableBody<Body>::value,
"ReadableBody requirements not met");
error_code ec; error_code ec;
read(stream, streambuf, msg, ec); read(stream, streambuf, msg, ec);
if(ec) if(ec)
@@ -420,6 +428,8 @@ read(SyncReadStream& stream, Streambuf& streambuf,
"SyncReadStream requirements not met"); "SyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value, static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met"); "Streambuf requirements not met");
static_assert(is_ReadableBody<Body>::value,
"ReadableBody requirements not met");
parser_v1<isRequest, Body, Headers> p; parser_v1<isRequest, Body, Headers> p;
parse(stream, streambuf, p, ec); parse(stream, streambuf, p, ec);
if(ec) if(ec)
@@ -441,6 +451,8 @@ async_read(AsyncReadStream& stream, Streambuf& streambuf,
"AsyncReadStream requirements not met"); "AsyncReadStream requirements not met");
static_assert(is_Streambuf<Streambuf>::value, static_assert(is_Streambuf<Streambuf>::value,
"Streambuf requirements not met"); "Streambuf requirements not met");
static_assert(is_ReadableBody<Body>::value,
"ReadableBody requirements not met");
beast::async_completion<ReadHandler, beast::async_completion<ReadHandler,
void(error_code)> completion(handler); void(error_code)> completion(handler);
detail::read_op<AsyncReadStream, Streambuf, detail::read_op<AsyncReadStream, Streambuf,

View File

@@ -8,6 +8,7 @@
#ifndef BEAST_HTTP_IMPL_WRITE_IPP #ifndef BEAST_HTTP_IMPL_WRITE_IPP
#define BEAST_HTTP_IMPL_WRITE_IPP #define BEAST_HTTP_IMPL_WRITE_IPP
#include <beast/http/concepts.hpp>
#include <beast/http/resume_context.hpp> #include <beast/http/resume_context.hpp>
#include <beast/http/detail/chunk_encode.hpp> #include <beast/http/detail/chunk_encode.hpp>
#include <beast/http/detail/has_content_length.hpp> #include <beast/http/detail/has_content_length.hpp>
@@ -448,6 +449,8 @@ write(SyncWriteStream& stream,
{ {
static_assert(is_SyncWriteStream<SyncWriteStream>::value, static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met"); "SyncWriteStream requirements not met");
static_assert(is_WritableBody<Body>::value,
"WritableBody requirements not met");
error_code ec; error_code ec;
write(stream, msg, ec); write(stream, msg, ec);
if(ec) if(ec)
@@ -463,6 +466,8 @@ write(SyncWriteStream& stream,
{ {
static_assert(is_SyncWriteStream<SyncWriteStream>::value, static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met"); "SyncWriteStream requirements not met");
static_assert(is_WritableBody<Body>::value,
"WritableBody requirements not met");
detail::write_preparation<isRequest, Body, Headers> wp(msg); detail::write_preparation<isRequest, Body, Headers> wp(msg);
wp.init(ec); wp.init(ec);
if(ec) if(ec)
@@ -546,6 +551,8 @@ async_write(AsyncWriteStream& stream,
static_assert( static_assert(
is_AsyncWriteStream<AsyncWriteStream>::value, is_AsyncWriteStream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met"); "AsyncWriteStream requirements not met");
static_assert(is_WritableBody<Body>::value,
"WritableBody requirements not met");
beast::async_completion<WriteHandler, beast::async_completion<WriteHandler,
void(error_code)> completion(handler); void(error_code)> completion(handler);
detail::write_op<AsyncWriteStream, decltype(completion.handler), detail::write_op<AsyncWriteStream, decltype(completion.handler),
@@ -607,6 +614,8 @@ std::ostream&
operator<<(std::ostream& os, operator<<(std::ostream& os,
message_v1<isRequest, Body, Headers> const& msg) message_v1<isRequest, Body, Headers> const& msg)
{ {
static_assert(is_WritableBody<Body>::value,
"WritableBody requirements not met");
detail::ostream_SyncStream oss(os); detail::ostream_SyncStream oss(os);
error_code ec; error_code ec;
write(oss, msg, ec); write(oss, msg, ec);

View File

@@ -9,6 +9,7 @@
#define BEAST_HTTP_PARSER_V1_HPP #define BEAST_HTTP_PARSER_V1_HPP
#include <beast/http/basic_parser_v1.hpp> #include <beast/http/basic_parser_v1.hpp>
#include <beast/http/concepts.hpp>
#include <beast/http/message_v1.hpp> #include <beast/http/message_v1.hpp>
#include <beast/core/error.hpp> #include <beast/core/error.hpp>
#include <functional> #include <functional>
@@ -54,6 +55,9 @@ public:
message_v1<isRequest, Body, Headers>; message_v1<isRequest, Body, Headers>;
private: private:
static_assert(is_ReadableBody<Body>::value,
"ReadableBody requirements not met");
std::string field_; std::string field_;
std::string value_; std::string value_;
message_type m_; message_type m_;

View File

@@ -1,60 +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_TYPE_CHECK_HPP
#define BEAST_HTTP_TYPE_CHECK_HPP
#include <beast/core/error.hpp>
#include <boost/asio/buffer.hpp>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
/// Determine if `T` meets the requirements of `Parser`.
template<class T>
class is_Parser
{
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().complete()),
bool>>
static R check1(int);
template<class>
static std::false_type check1(...);
using type1 = decltype(check1<T>(0));
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().write(
std::declval<boost::asio::const_buffer const&>(),
std::declval<error_code&>())),
std::size_t>>
static R check2(int);
template<class>
static std::false_type check2(...);
using type2 = decltype(check2<T>(0));
template<class U, class R = decltype(
std::declval<U>().write_eof(std::declval<error_code&>()),
std::true_type{})>
static R check3(int);
template<class>
static std::false_type check3(...);
using type3 = decltype(check3<T>(0));
public:
/// `true` if `T` meets the requirements.
static bool const value =
type1::value && type2::value && type3::value;
};
} // http
} // beast
#endif

View File

@@ -16,6 +16,6 @@
// //
#define BEAST_VERSION 100000 #define BEAST_VERSION 100000
#define BEAST_VERSION_STRING "1.0.0-b4" #define BEAST_VERSION_STRING "1.0.0-b5"
#endif #endif

View File

@@ -23,6 +23,16 @@ namespace beast {
namespace websocket { namespace websocket {
namespace detail { namespace detail {
/// Identifies the role of a WebSockets stream.
enum class role_type
{
/// Stream is operating as a client.
client,
/// Stream is operating as a server.
server
};
// Contents of a WebSocket frame header // Contents of a WebSocket frame header
struct frame_header struct frame_header
{ {
@@ -286,8 +296,7 @@ read_fh2(frame_header& fh, Streambuf& sb,
// //
template<class Buffers> template<class Buffers>
void void
read(ping_payload_type& data, read(ping_data& data, Buffers const& bs)
Buffers const& bs, close_code::value& code)
{ {
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::buffer_size; using boost::asio::buffer_size;

View File

@@ -71,9 +71,9 @@ using maskgen = maskgen_t<std::mt19937>;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
//using prepared_key_type = std::size_t; using prepared_key_type =
using prepared_key_type = std::uint32_t; std::conditional<sizeof(void*) == 8,
//using prepared_key_type = std::uint64_t; std::uint64_t, std::uint32_t>::type;
inline inline
void void
@@ -93,19 +93,6 @@ prepare_key(std::uint64_t& prepared, std::uint32_t key)
template<class T> template<class T>
inline inline
typename std::enable_if<std::is_integral<T>::value, T>::type typename std::enable_if<std::is_integral<T>::value, T>::type
rol(T t, unsigned n = 1)
{
auto constexpr bits =
static_cast<unsigned>(
sizeof(T) * CHAR_BIT);
n &= bits-1;
return static_cast<T>((t << n) | (
static_cast<typename std::make_unsigned<T>::type>(t) >> (bits - n)));
}
template <class T>
inline
typename std::enable_if<std::is_integral<T>::value, T>::type
ror(T t, unsigned n = 1) ror(T t, unsigned n = 1)
{ {
auto constexpr bits = auto constexpr bits =
@@ -120,7 +107,7 @@ ror(T t, unsigned n = 1)
// //
template<class = void> template<class = void>
void void
mask_inplace_safe( mask_inplace_general(
boost::asio::mutable_buffer const& b, boost::asio::mutable_buffer const& b,
std::uint32_t& key) std::uint32_t& key)
{ {
@@ -151,7 +138,7 @@ mask_inplace_safe(
// //
template<class = void> template<class = void>
void void
mask_inplace_safe( mask_inplace_general(
boost::asio::mutable_buffer const& b, boost::asio::mutable_buffer const& b,
std::uint64_t& key) std::uint64_t& key)
{ {
@@ -186,164 +173,13 @@ mask_inplace_safe(
} }
} }
// 32-bit optimized
template<class = void>
void
mask_inplace_32(
boost::asio::mutable_buffer const& b,
std::uint32_t& key)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto n = buffer_size(b);
auto p = buffer_cast<std::uint8_t*>(b);
auto m = reinterpret_cast<
uintptr_t>(p) % sizeof(key);
switch(m)
{
case 1: *p ^= key ; ++p; --n;
case 2: *p ^= (key >> 8); ++p; --n;
case 3: *p ^= (key >>16); ++p; --n;
key = ror(key, m * 8);
case 0:
break;
}
for(auto i = n / sizeof(key); i; --i)
{
*reinterpret_cast<
std::uint32_t*>(p) ^= key;
p += sizeof(key);
}
n %= sizeof(key);
switch(n)
{
case 3: p[2] ^= (key >>16);
case 2: p[1] ^= (key >> 8);
case 1: p[0] ^= key;
key = ror(key, n*8);
default:
break;
}
}
// 64-bit optimized
//
template<class = void>
void
mask_inplace_64(
boost::asio::mutable_buffer const& b,
std::uint64_t& key)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto n = buffer_size(b);
auto p = buffer_cast<std::uint8_t*>(b);
auto m = reinterpret_cast<
uintptr_t>(p) % sizeof(key);
switch(m)
{
case 1: *p ^= key ; ++p; --n;
case 2: *p ^= (key >> 8); ++p; --n;
case 3: *p ^= (key >>16); ++p; --n;
case 4: *p ^= (key >>24); ++p; --n;
case 5: *p ^= (key >>32); ++p; --n;
case 6: *p ^= (key >>40); ++p; --n;
case 7: *p ^= (key >>48); ++p; --n;
key = ror(key, m * 8);
case 0:
break;
}
for(auto i = n / sizeof(key); i; --i)
{
*reinterpret_cast<
std::uint64_t*>(p) ^= key;
p += sizeof(key);
}
n %= sizeof(key);
switch(n)
{
case 3: p[2] ^= (key >>16);
case 2: p[1] ^= (key >> 8);
case 1: p[0] ^= key;
key = ror(key, n*8);
default:
break;
}
}
// 32-bit x86 optimized
//
template<class = void>
void
mask_inplace_x86(
boost::asio::mutable_buffer const& b,
std::uint32_t& key)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto n = buffer_size(b);
auto p = buffer_cast<std::uint8_t*>(b);
for(auto i = n / sizeof(key); i; --i)
{
*reinterpret_cast<
std::uint32_t*>(p) ^= key;
p += sizeof(key);
}
n %= sizeof(key);
switch(n)
{
case 3: p[2] ^= (key >>16);
case 2: p[1] ^= (key >> 8);
case 1: p[0] ^= key;
key = ror(key, n*8);
default:
break;
}
}
// 64-bit amd64 optimized
//
template<class = void>
void
mask_inplace_amd(
boost::asio::mutable_buffer const& b,
std::uint64_t& key)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
auto n = buffer_size(b);
auto p = buffer_cast<std::uint8_t*>(b);
for(auto i = n / sizeof(key); i; --i)
{
*reinterpret_cast<
std::uint64_t*>(p) ^= key;
p += sizeof(key);
}
n %= sizeof(key);
switch(n)
{
case 7: p[6] ^= (key >>16);
case 6: p[5] ^= (key >> 8);
case 5: p[4] ^= key;
case 4: p[3] ^= (key >>24);
case 3: p[2] ^= (key >>16);
case 2: p[1] ^= (key >> 8);
case 1: p[0] ^= key;
key = ror(key, n*8);
default:
break;
}
}
inline inline
void void
mask_inplace( mask_inplace(
boost::asio::mutable_buffer const& b, boost::asio::mutable_buffer const& b,
std::uint32_t& key) std::uint32_t& key)
{ {
mask_inplace_safe(b, key); mask_inplace_general(b, key);
//mask_inplace_32(b, key);
//mask_inplace_x86(b, key);
} }
inline inline
@@ -352,9 +188,7 @@ mask_inplace(
boost::asio::mutable_buffer const& b, boost::asio::mutable_buffer const& b,
std::uint64_t& key) std::uint64_t& key)
{ {
mask_inplace_safe(b, key); mask_inplace_general(b, key);
//mask_inplace_64(b, key);
//mask_inplace_amd(b, key);
} }
// Apply mask in place // Apply mask in place

View File

@@ -30,15 +30,6 @@ namespace beast {
namespace websocket { namespace websocket {
namespace detail { namespace detail {
template<class String>
inline
void
maybe_throw(error_code const& ec, String const&)
{
if(ec)
throw system_error{ec};
}
template<class UInt> template<class UInt>
static static
std::size_t std::size_t
@@ -59,6 +50,8 @@ clamp(UInt x, std::size_t limit)
return static_cast<std::size_t>(x); return static_cast<std::size_t>(x);
} }
using pong_cb = std::function<void(ping_data const&)>;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
struct stream_base struct stream_base
@@ -69,42 +62,46 @@ protected:
detail::maskgen maskgen_; // source of mask keys detail::maskgen maskgen_; // source of mask keys
decorator_type d_; // adorns http messages decorator_type d_; // adorns http messages
bool keep_alive_ = false; // close on failed upgrade bool keep_alive_ = false; // close on failed upgrade
role_type role_; // server or client
bool error_ = false; // non-zero ec was delivered
std::size_t rd_msg_max_ = std::size_t rd_msg_max_ =
16 * 1024 * 1024; // max message size 16 * 1024 * 1024; // max message size
std::size_t
wr_frag_size_ = 16 * 1024; // size of auto-fragments
std::size_t mask_buf_size_ = 4096; // mask buffer size
opcode wr_opcode_ = opcode::text; // outgoing message type
pong_cb pong_cb_; // pong callback
role_type role_; // server or client
bool failed_; // the connection failed
detail::frame_header rd_fh_; // current frame header detail::frame_header rd_fh_; // current frame header
detail::prepared_key_type rd_key_; // prepared masking key detail::prepared_key_type rd_key_; // prepared masking key
detail::utf8_checker rd_utf8_check_;// for current text msg detail::utf8_checker rd_utf8_check_;// for current text msg
std::uint64_t rd_size_; // size of the current message so far std::uint64_t rd_size_; // size of the current message so far
std::uint64_t rd_need_ = 0; // bytes left in msg frame payload std::uint64_t rd_need_ = 0; // bytes left in msg frame payload
opcode rd_opcode_; // opcode of current msg opcode rd_opcode_; // opcode of current msg
bool rd_cont_ = false; // expecting a continuation frame bool rd_cont_; // expecting a continuation frame
bool rd_close_ = false; // got close frame
op* rd_block_ = nullptr; // op currently reading
std::size_t bool wr_close_; // sent close frame
wr_frag_size_ = 16 * 1024; // size of auto-fragments bool wr_cont_; // next write is continuation frame
std::size_t wr_buf_size_ = 4096; // write buffer size op* wr_block_; // op currenly writing
opcode wr_opcode_ = opcode::text; // outgoing message type
bool wr_close_ = false; // sent close frame
bool wr_cont_ = false; // next write is continuation frame
op* wr_block_ = nullptr; // op currenly writing
ping_data* pong_data_; // where to put pong payload
invokable rd_op_; // invoked after write completes invokable rd_op_; // invoked after write completes
invokable wr_op_; // invoked after read completes invokable wr_op_; // invoked after read completes
close_reason cr_; // set from received close frame close_reason cr_; // set from received close frame
stream_base(stream_base&&) = default;
stream_base(stream_base const&) = delete;
stream_base& operator=(stream_base&&) = default;
stream_base& operator=(stream_base const&) = delete;
stream_base() stream_base()
: d_(new decorator<default_decorator>{}) : d_(new decorator<default_decorator>{})
{ {
} }
stream_base(stream_base&&) = default; template<class = void>
stream_base(stream_base const&) = delete; void
stream_base& operator=(stream_base&&) = default; open(role_type role);
stream_base& operator=(stream_base const&) = delete;
template<class = void> template<class = void>
void void
@@ -118,7 +115,7 @@ protected:
template<class Streambuf> template<class Streambuf>
void void
write_ping(Streambuf& sb, opcode op, write_ping(Streambuf& sb, opcode op,
ping_payload_type const& data); ping_data const& data);
}; };
} // detail } // detail

View File

@@ -33,7 +33,7 @@ class stream<NextLayer>::accept_op
struct data struct data
{ {
stream<NextLayer>& ws; stream<NextLayer>& ws;
http::request_v1<http::empty_body> req; http::request_v1<http::string_body> req;
Handler h; Handler h;
bool cont; bool cont;
int state = 0; int state = 0;
@@ -48,6 +48,7 @@ class stream<NextLayer>::accept_op
{ {
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::buffer_size; using boost::asio::buffer_size;
ws.reset();
ws.stream_.buffer().commit(buffer_copy( ws.stream_.buffer().commit(buffer_copy(
ws.stream_.buffer().prepare( ws.stream_.buffer().prepare(
buffer_size(buffers)), buffers)); buffer_size(buffers)), buffers));
@@ -133,8 +134,14 @@ operator()(error_code const& ec,
// got message // got message
case 1: case 1:
// respond to request // respond to request
#if 1
// VFALCO I have no idea why passing std::move(*this) crashes
d.state = 99;
d.ws.async_accept(d.req, *this);
#else
response_op<Handler>{ response_op<Handler>{
std::move(d.h), d.ws, d.req, true}; std::move(d.h), d.ws, d.req, true};
#endif
return; return;
} }
} }

View File

@@ -21,12 +21,9 @@ template<class NextLayer>
template<class Handler> template<class Handler>
class stream<NextLayer>::close_op class stream<NextLayer>::close_op
{ {
using alloc_type = using alloc_type = handler_alloc<char, Handler>;
handler_alloc<char, Handler>;
using fb_type = using fb_type = detail::frame_streambuf;
detail::frame_streambuf;
using fmb_type =
typename fb_type::mutable_buffers_type;
struct data : op struct data : op
{ {
@@ -64,24 +61,19 @@ public:
std::forward<DeducedHandler>(h), ws, std::forward<DeducedHandler>(h), ws,
std::forward<Args>(args)...)) std::forward<Args>(args)...))
{ {
(*this)(error_code{}, 0, false); (*this)(error_code{}, false);
} }
void operator()() void operator()()
{ {
auto& d = *d_; (*this)(error_code{});
d.cont = false;
(*this)(error_code{}, 0, false);
}
void operator()(error_code const& ec)
{
(*this)(ec, 0);
} }
void void
operator()(error_code ec, operator()(error_code ec, std::size_t);
std::size_t bytes_transferred, bool again = true);
void
operator()(error_code ec, bool again = true);
friend friend
void* asio_handler_allocate( void* asio_handler_allocate(
@@ -117,12 +109,26 @@ public:
template<class NextLayer> template<class NextLayer>
template<class Handler> template<class Handler>
void void
stream<NextLayer>::close_op<Handler>::operator()( stream<NextLayer>::close_op<Handler>::
error_code ec, std::size_t bytes_transferred, bool again) operator()(error_code ec, std::size_t)
{
auto& d = *d_;
if(ec)
d.ws.failed_ = true;
(*this)(ec);
}
template<class NextLayer>
template<class Handler>
void
stream<NextLayer>::close_op<Handler>::
operator()(error_code ec, bool again)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;
while(! ec && d.state != 99) if(ec)
goto upcall;
for(;;)
{ {
switch(d.state) switch(d.state)
{ {
@@ -130,49 +136,52 @@ stream<NextLayer>::close_op<Handler>::operator()(
if(d.ws.wr_block_) if(d.ws.wr_block_)
{ {
// suspend // suspend
d.state = 1; d.state = 2;
d.ws.rd_op_.template emplace< d.ws.wr_op_.template emplace<
close_op>(std::move(*this)); close_op>(std::move(*this));
return; return;
} }
if(d.ws.error_) if(d.ws.failed_ || d.ws.wr_close_)
{ {
// call handler // call handler
d.state = 99;
d.ws.get_io_service().post( d.ws.get_io_service().post(
bind_handler(std::move(*this), bind_handler(std::move(*this),
boost::asio::error::operation_aborted, 0)); boost::asio::error::operation_aborted));
return; return;
} }
d.state = 2; // fall through
break;
// resume
case 1: case 1:
if(d.ws.error_) // send close frame
{
// call handler
d.state = 99;
ec = boost::asio::error::operation_aborted;
break;
}
d.state = 2;
break;
case 2:
// send close
d.state = 99; d.state = 99;
assert(! d.ws.wr_close_);
d.ws.wr_close_ = true; d.ws.wr_close_ = true;
assert(! d.ws.wr_block_); assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d; d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_, boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this)); d.fb.data(), std::move(*this));
return; return;
case 2:
d.state = 3;
d.ws.get_io_service().post(
bind_handler(std::move(*this), ec));
return;
case 3:
if(d.ws.failed_ || d.ws.wr_close_)
{
// call handler
ec = boost::asio::error::operation_aborted;
goto upcall;
}
d.state = 1;
break;
case 99:
goto upcall;
} }
} }
if(ec) upcall:
d.ws.error_ = true;
if(d.ws.wr_block_ == &d) if(d.ws.wr_block_ == &d)
d.ws.wr_block_ = nullptr; d.ws.wr_block_ = nullptr;
d.ws.rd_op_.maybe_invoke(); d.ws.rd_op_.maybe_invoke();

View File

@@ -48,6 +48,7 @@ class stream<NextLayer>::handshake_op
, cont(boost_asio_handler_cont_helpers:: , cont(boost_asio_handler_cont_helpers::
is_continuation(h)) is_continuation(h))
{ {
ws.reset();
} }
}; };
@@ -64,16 +65,11 @@ public:
std::forward<DeducedHandler>(h), ws, std::forward<DeducedHandler>(h), ws,
std::forward<Args>(args)...)) std::forward<Args>(args)...))
{ {
(*this)(error_code{}, 0, false); (*this)(error_code{}, false);
} }
void operator()(error_code const& ec) void
{ operator()(error_code ec, bool again = true);
(*this)(ec, 0);
}
void operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
friend friend
void* asio_handler_allocate( void* asio_handler_allocate(
@@ -109,9 +105,8 @@ public:
template<class NextLayer> template<class NextLayer>
template<class Handler> template<class Handler>
void void
stream<NextLayer>::handshake_op< stream<NextLayer>::handshake_op<Handler>::
Handler>::operator()(error_code ec, operator()(error_code ec, bool again)
std::size_t bytes_transferred, bool again)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;

View File

@@ -0,0 +1,193 @@
//
// 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_WEBSOCKET_IMPL_PING_OP_HPP
#define BEAST_WEBSOCKET_IMPL_PING_OP_HPP
#include <beast/core/bind_handler.hpp>
#include <beast/core/handler_alloc.hpp>
#include <beast/websocket/detail/frame.hpp>
#include <memory>
namespace beast {
namespace websocket {
// write a ping frame
//
template<class NextLayer>
template<class Handler>
class stream<NextLayer>::ping_op
{
using alloc_type =
handler_alloc<char, Handler>;
struct data : op
{
stream<NextLayer>& ws;
Handler h;
detail::frame_streambuf fb;
bool cont;
int state = 0;
template<class DeducedHandler>
data(DeducedHandler&& h_, stream<NextLayer>& ws_,
ping_data const& payload)
: ws(ws_)
, h(std::forward<DeducedHandler>(h_))
, cont(boost_asio_handler_cont_helpers::
is_continuation(h))
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
ws.template write_ping<static_streambuf>(
fb, opcode::ping, payload);
}
};
std::shared_ptr<data> d_;
public:
ping_op(ping_op&&) = default;
ping_op(ping_op const&) = default;
template<class DeducedHandler, class... Args>
ping_op(DeducedHandler&& h,
stream<NextLayer>& ws, Args&&... args)
: d_(std::make_shared<data>(
std::forward<DeducedHandler>(h), ws,
std::forward<Args>(args)...))
{
(*this)(error_code{}, false);
}
void operator()()
{
(*this)(error_code{});
}
void operator()(error_code ec, std::size_t);
void operator()(error_code ec, bool again = true);
friend
void* asio_handler_allocate(
std::size_t size, ping_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_->h);
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, ping_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_->h);
}
friend
bool asio_handler_is_continuation(ping_op* op)
{
return op->d_->cont;
}
template <class Function>
friend
void asio_handler_invoke(Function&& f, ping_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_->h);
}
};
template<class NextLayer>
template<class Handler>
void
stream<NextLayer>::ping_op<Handler>::
operator()(error_code ec, std::size_t)
{
auto& d = *d_;
if(ec)
d.ws.failed_ = true;
(*this)(ec);
}
template<class NextLayer>
template<class Handler>
void
stream<NextLayer>::
ping_op<Handler>::
operator()(error_code ec, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
if(ec)
goto upcall;
for(;;)
{
switch(d.state)
{
case 0:
if(d.ws.wr_block_)
{
// suspend
d.state = 2;
d.ws.wr_op_.template emplace<
ping_op>(std::move(*this));
return;
}
if(d.ws.failed_ || d.ws.wr_close_)
{
// call handler
d.state = 99;
d.ws.get_io_service().post(
bind_handler(std::move(*this),
boost::asio::error::operation_aborted));
return;
}
// fall through
case 1:
// send ping frame
d.state = 99;
assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
case 2:
d.state = 3;
d.ws.get_io_service().post(
bind_handler(std::move(*this), ec));
return;
case 3:
if(d.ws.failed_ || d.ws.wr_close_)
{
// call handler
ec = boost::asio::error::operation_aborted;
goto upcall;
}
d.state = 1;
break;
case 99:
goto upcall;
}
}
upcall:
if(d.ws.wr_block_ == &d)
d.ws.wr_block_ = nullptr;
d.ws.rd_op_.maybe_invoke();
d.h(ec);
}
} // websocket
} // beast
#endif

View File

@@ -81,18 +81,19 @@ public:
void operator()() void operator()()
{ {
auto& d = *d_; (*this)(error_code{}, 0, true);
d.cont = false;
(*this)(error_code{}, 0, false);
} }
void operator()(error_code const& ec) void operator()(error_code const& ec)
{ {
(*this)(ec, 0); (*this)(ec, 0, true);
} }
void operator()(error_code ec, void operator()(error_code ec,
std::size_t bytes_transferred, bool again = true); std::size_t bytes_transferred);
void operator()(error_code ec,
std::size_t bytes_transferred, bool again);
friend friend
void* asio_handler_allocate( void* asio_handler_allocate(
@@ -129,373 +130,415 @@ template<class NextLayer>
template<class Buffers, class Handler> template<class Buffers, class Handler>
void void
stream<NextLayer>::read_frame_op<Buffers, Handler>:: stream<NextLayer>::read_frame_op<Buffers, Handler>::
operator()(error_code ec,std::size_t bytes_transferred, bool again) operator()(error_code ec, std::size_t bytes_transferred)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; if(ec)
close_code::value code = close_code::none; d.ws.failed_ = true;
while(! ec && d.state != 99) (*this)(ec, bytes_transferred, true);
}
template<class NextLayer>
template<class Buffers, class Handler>
void
stream<NextLayer>::read_frame_op<Buffers, Handler>::
operator()(error_code ec,std::size_t bytes_transferred, bool again)
{
enum
{ {
switch(d.state) do_start = 0,
do_read_payload = 1,
do_frame_done = 3,
do_read_fh = 4,
do_control_payload = 7,
do_control = 8,
do_pong_resume = 9,
do_pong = 11,
do_close_resume = 13,
do_close = 15,
do_fail = 18,
do_call_handler = 99
};
auto& d = *d_;
if(! ec)
{
d.cont = d.cont || again;
close_code::value code = close_code::none;
do
{ {
case 0: switch(d.state)
if(d.ws.error_)
{ {
case do_start:
if(d.ws.failed_)
{
d.state = do_call_handler;
d.ws.get_io_service().post(
bind_handler(std::move(*this),
boost::asio::error::operation_aborted, 0));
return;
}
d.state = d.ws.rd_need_ > 0 ?
do_read_payload : do_read_fh;
break;
//------------------------------------------------------------------
case do_read_payload:
d.state = do_read_payload + 1;
d.smb = d.sb.prepare(
detail::clamp(d.ws.rd_need_));
// receive payload data
d.ws.stream_.async_read_some(
*d.smb, std::move(*this));
return;
case do_read_payload + 1:
{
d.ws.rd_need_ -= bytes_transferred;
auto const pb = prepare_buffers(
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)
{
if(! d.ws.rd_utf8_check_.write(pb) ||
(d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin &&
! d.ws.rd_utf8_check_.finish()))
{
// invalid utf8
code = close_code::bad_payload;
d.state = do_fail;
break;
}
}
d.sb.commit(bytes_transferred);
if(d.ws.rd_need_ > 0)
{
d.state = do_read_payload;
break;
}
// fall through
}
//------------------------------------------------------------------
case do_frame_done:
// call handler // call handler
d.state = 99; d.fi.op = d.ws.rd_opcode_;
d.ws.get_io_service().post( d.fi.fin = d.ws.rd_fh_.fin &&
bind_handler(std::move(*this), d.ws.rd_need_ == 0;
boost::asio::error::operation_aborted, 0)); goto upcall;
//------------------------------------------------------------------
case do_read_fh:
d.state = do_read_fh + 1;
boost::asio::async_read(d.ws.stream_,
d.fb.prepare(2), std::move(*this));
return;
case do_read_fh + 1:
{
d.fb.commit(bytes_transferred);
code = close_code::none;
auto const n = detail::read_fh1(
d.ws.rd_fh_, d.fb, d.ws.role_, code);
if(code != close_code::none)
{
// protocol error
d.state = do_fail;
break;
}
d.state = do_read_fh + 2;
if (n == 0)
{
bytes_transferred = 0;
break;
}
// read variable header
boost::asio::async_read(d.ws.stream_,
d.fb.prepare(n), std::move(*this));
return; return;
} }
if(d.ws.rd_need_ > 0)
{
d.state = 1;
break;
}
d.state = 2;
break;
case 1: case do_read_fh + 2:
// read payload d.fb.commit(bytes_transferred);
d.state = 3;
d.smb = d.sb.prepare(
detail::clamp(d.ws.rd_need_));
d.ws.stream_.async_read_some(
*d.smb, std::move(*this));
return;
case 2:
// read fixed header
d.state = 5;
boost::asio::async_read(d.ws.stream_,
d.fb.prepare(2), std::move(*this));
return;
// got payload
case 3:
{
d.ws.rd_need_ -= bytes_transferred;
auto const pb = prepare_buffers(
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)
{
if(! d.ws.rd_utf8_check_.write(pb) ||
(d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin &&
! d.ws.rd_utf8_check_.finish()))
{
// invalid utf8
d.state = 16;
code = close_code::bad_payload;
break;
}
}
d.sb.commit(bytes_transferred);
d.state = 4;
break;
}
// call handler
case 4:
d.state = 99;
d.fi.op = d.ws.rd_opcode_;
d.fi.fin = d.ws.rd_fh_.fin &&
d.ws.rd_need_ == 0;
break;
// got fixed header
case 5:
{
d.fb.commit(bytes_transferred);
code = close_code::none;
auto const n = detail::read_fh1(
d.ws.rd_fh_, d.fb, d.ws.role_, code);
if(code != close_code::none)
{
// protocol error
d.state = 16;
break;
}
d.state = 6;
if (n == 0)
{
bytes_transferred = 0;
break;
}
// read variable header
boost::asio::async_read(d.ws.stream_,
d.fb.prepare(n), std::move(*this));
return;
}
// got variable header
case 6:
d.fb.commit(bytes_transferred);
code = close_code::none;
detail::read_fh2(d.ws.rd_fh_,
d.fb, d.ws.role_, code);
if(code == close_code::none)
d.ws.prepare_fh(code);
if(code != close_code::none)
{
// protocol error
d.state = 16;
break;
}
if(detail::is_control(d.ws.rd_fh_.op))
{
if(d.ws.rd_fh_.len > 0)
{
// read control payload
d.state = 7;
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));
return;
}
d.state = 8;
break;
}
if(d.ws.rd_need_ > 0)
{
d.state = 1;
break;
}
if(! d.ws.rd_fh_.fin)
{
d.state = 2;
break;
}
// empty frame with fin
d.state = 4;
break;
// got control payload
case 7:
if(d.ws.rd_fh_.mask)
detail::mask_inplace(
*d.fmb, d.ws.rd_key_);
d.fb.commit(bytes_transferred);
d.state = 8;
break;
// do control
case 8:
if(d.ws.rd_fh_.op == opcode::ping)
{
code = close_code::none; code = close_code::none;
ping_payload_type data; detail::read_fh2(d.ws.rd_fh_,
detail::read(data, d.fb.data(), code); d.fb, d.ws.role_, code);
if(code == close_code::none)
d.ws.prepare_fh(code);
if(code != close_code::none) if(code != close_code::none)
{ {
// protocol error // protocol error
d.state = 16; d.state = do_fail;
break; break;
} }
d.fb.reset(); if(detail::is_control(d.ws.rd_fh_.op))
if(d.ws.wr_close_)
{ {
d.state = 2; if(d.ws.rd_fh_.len > 0)
{
// read control payload
d.state = do_control_payload;
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));
return;
}
d.state = do_control;
break; break;
} }
d.ws.template write_ping<static_streambuf>( if(d.ws.rd_need_ > 0)
d.fb, opcode::pong, data);
if(d.ws.wr_block_)
{ {
assert(d.ws.wr_block_ != &d); d.state = do_read_payload;
// suspend break;
d.state = 13;
d.ws.rd_op_.template emplace<
read_frame_op>(std::move(*this));
return;
} }
d.state = 14; // empty frame
d.state = do_frame_done;
break; break;
}
else if(d.ws.rd_fh_.op == opcode::pong) //------------------------------------------------------------------
{
code = close_code::none; case do_control_payload:
ping_payload_type data; if(d.ws.rd_fh_.mask)
detail::read(data, d.fb.data(), code); detail::mask_inplace(
if(code != close_code::none) *d.fmb, d.ws.rd_key_);
{ d.fb.commit(bytes_transferred);
// protocol error d.state = do_control; // VFALCO fall through?
d.state = 16;
break;
}
d.fb.reset();
// VFALCO TODO maybe_invoke an async pong handler
// For now just ignore the pong.
d.state = 2;
break; break;
}
assert(d.ws.rd_fh_.op == opcode::close); //------------------------------------------------------------------
{
detail::read(d.ws.cr_, d.fb.data(), code); case do_control:
if(code != close_code::none) if(d.ws.rd_fh_.op == opcode::ping)
{ {
d.state = 16; ping_data data;
break; detail::read(data, d.fb.data());
}
if(! d.ws.wr_close_)
{
auto cr = d.ws.cr_;
if(cr.code == close_code::none)
cr.code = close_code::normal;
cr.reason = "";
d.fb.reset(); d.fb.reset();
d.ws.template write_close< if(d.ws.wr_close_)
static_streambuf>(d.fb, cr); {
// ignore ping when closing
d.state = do_read_fh;
break;
}
d.ws.template write_ping<static_streambuf>(
d.fb, opcode::pong, data);
if(d.ws.wr_block_) if(d.ws.wr_block_)
{ {
// suspend // suspend
d.state = 9; d.state = do_pong_resume;
assert(d.ws.wr_block_ != &d);
d.ws.rd_op_.template emplace< d.ws.rd_op_.template emplace<
read_frame_op>(std::move(*this)); read_frame_op>(std::move(*this));
return; return;
} }
d.state = 10; d.state = do_pong;
break; break;
} }
// call handler; else if(d.ws.rd_fh_.op == opcode::pong)
d.state = 99; {
ec = error::closed; code = close_code::none;
break; ping_data payload;
} detail::read(payload, d.fb.data());
if(d.ws.pong_cb_)
d.ws.pong_cb_(payload);
d.fb.reset();
d.state = do_read_fh;
break;
}
assert(d.ws.rd_fh_.op == opcode::close);
{
detail::read(d.ws.cr_, d.fb.data(), code);
if(code != close_code::none)
{
// protocol error
d.state = do_fail;
break;
}
if(! d.ws.wr_close_)
{
auto cr = d.ws.cr_;
if(cr.code == close_code::none)
cr.code = close_code::normal;
cr.reason = "";
d.fb.reset();
d.ws.template write_close<
static_streambuf>(d.fb, cr);
if(d.ws.wr_block_)
{
// suspend
d.state = do_close_resume;
d.ws.rd_op_.template emplace<
read_frame_op>(std::move(*this));
return;
}
d.state = do_close;
break;
}
// call handler;
ec = error::closed;
goto upcall;
}
// resume //------------------------------------------------------------------
case 9:
if(d.ws.error_)
{
// call handler
d.state = 99;
ec = boost::asio::error::operation_aborted;
break;
}
if(d.ws.wr_close_)
{
// call handler
d.state = 99;
ec = error::closed;
break;
}
d.state = 10;
break;
// send close case do_pong_resume:
case 10: d.state = do_pong_resume + 1;
d.state = 11; d.ws.get_io_service().post(bind_handler(
assert(! d.ws.wr_block_); std::move(*this), ec, bytes_transferred));
d.ws.wr_block_ = &d; return;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;;
// teardown case do_pong_resume + 1:
case 11: if(d.ws.failed_)
d.state = 12; {
websocket_helpers::call_async_teardown( // call handler
d.ws.next_layer(), std::move(*this)); ec = boost::asio::error::operation_aborted;
return; goto upcall;
}
d.state = do_pong;
break; // VFALCO fall through?
case 12: //------------------------------------------------------------------
// call handler
d.state = 99;
ec = error::closed;
break;
// resume case do_pong:
case 13: if(d.ws.wr_close_)
if(d.ws.error_) {
{ // ignore ping when closing
// call handler d.fb.reset();
d.state = 99; d.state = do_read_fh;
ec = boost::asio::error::operation_aborted; break;
break; }
} // send pong
if(d.ws.wr_close_) d.state = do_pong + 1;
{ assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
case do_pong + 1:
d.fb.reset(); d.fb.reset();
d.state = 2; d.state = do_read_fh;
d.ws.wr_block_ = nullptr;
break; break;
}
d.state = 14;
break;
case 14: //------------------------------------------------------------------
// write ping/pong
d.state = 15;
assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
// sent ping/pong case do_close_resume:
case 15: d.state = do_close_resume + 1;
d.fb.reset(); d.ws.get_io_service().post(bind_handler(
d.state = 2; std::move(*this), ec, bytes_transferred));
d.ws.wr_block_ = nullptr; return;
break;
// fail the connection case do_close_resume + 1:
case 16: if(d.ws.failed_)
if(! d.ws.wr_close_) {
{ // call handler
d.state = do_call_handler;
ec = boost::asio::error::operation_aborted;
break;
}
if(d.ws.wr_close_)
{
// call handler
ec = error::closed;
goto upcall;
}
d.state = do_close;
break;
//------------------------------------------------------------------
case do_close:
d.state = do_close + 1;
d.ws.wr_close_ = true;
assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
case do_close + 1:
d.state = do_close + 2;
websocket_helpers::call_async_teardown(
d.ws.next_layer(), std::move(*this));
return;
case do_close + 2:
// call handler
ec = error::closed;
goto upcall;
//------------------------------------------------------------------
case do_fail:
if(d.ws.wr_close_)
{
d.state = do_fail + 4;
break;
}
d.fb.reset(); d.fb.reset();
d.ws.template write_close< d.ws.template write_close<
static_streambuf>(d.fb, code); static_streambuf>(d.fb, code);
if(d.ws.wr_block_) if(d.ws.wr_block_)
{ {
// suspend // suspend
d.state = 17; d.state = do_fail + 2;
d.ws.rd_op_.template emplace< d.ws.rd_op_.template emplace<
read_frame_op>(std::move(*this)); read_frame_op>(std::move(*this));
return; return;
} }
d.state = 18; // fall through
case do_fail + 1:
d.ws.failed_ = true;
// send close frame
d.state = do_fail + 4;
d.ws.wr_close_ = true;
assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
case do_fail + 2:
d.state = do_fail + 3;
d.ws.get_io_service().post(bind_handler(
std::move(*this), ec, bytes_transferred));
return;
case do_fail + 3:
if(d.ws.failed_)
{
d.state = do_fail + 5;
break;
}
d.state = do_fail + 1;
break; break;
case do_fail + 4:
d.state = do_fail + 5;
websocket_helpers::call_async_teardown(
d.ws.next_layer(), std::move(*this));
return;
case do_fail + 5:
// call handler
ec = error::failed;
goto upcall;
//------------------------------------------------------------------
case do_call_handler:
goto upcall;
} }
// resume
case 17:
if(d.ws.wr_close_)
{
d.state = 19;
break;
}
d.state = 18;
break;
case 18:
// send close
d.state = 19;
d.ws.wr_close_ = true;
assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_,
d.fb.data(), std::move(*this));
return;
// teardown
case 19:
d.state = 20;
websocket_helpers::call_async_teardown(
d.ws.next_layer(), std::move(*this));
return;
case 20:
// call handler
d.state = 99;
ec = error::failed;
break;
} }
while(! ec);
} }
if(ec) upcall:
d.ws.error_ = true;
if(d.ws.wr_block_ == &d) if(d.ws.wr_block_ == &d)
d.ws.wr_block_ = nullptr; d.ws.wr_block_ = nullptr;
d.ws.wr_op_.maybe_invoke(); d.ws.wr_op_.maybe_invoke();

View File

@@ -105,24 +105,34 @@ operator()(error_code const& ec, bool again)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;
while(! ec && d.state != 99) while(! ec)
{ {
switch(d.state) switch(d.state)
{ {
case 0: case 0:
// read payload // read payload
d.state = 1; d.state = 1;
#if 0
// VFALCO This causes dereference of null, because
// the handler is moved from the data block
// before asio_handler_deallocate is called.
d.ws.async_read_frame( d.ws.async_read_frame(
d.fi, d.sb, std::move(*this)); d.fi, d.sb, std::move(*this));
#else
d.ws.async_read_frame(d.fi, d.sb, *this);
#endif
return; return;
// got payload // got payload
case 1: case 1:
d.op = d.fi.op; d.op = d.fi.op;
d.state = d.fi.fin ? 99 : 0; if(d.fi.fin)
goto upcall;
d.state = 0;
break; break;
} }
} }
upcall:
d.h(ec); d.h(ec);
} }

View File

@@ -44,6 +44,9 @@ class stream<NextLayer>::response_op
, h(std::forward<DeducedHandler>(h_)) , h(std::forward<DeducedHandler>(h_))
, cont(cont_) , cont(cont_)
{ {
// can't call stream::reset() here
// otherwise accept_op will malfunction
//
if(resp.status != 101) if(resp.status != 101)
final_ec = error::handshake_failed; final_ec = error::handshake_failed;
} }
@@ -123,7 +126,7 @@ operator()(error_code ec, bool again)
d.state = 99; d.state = 99;
ec = d.final_ec; ec = d.final_ec;
if(! ec) if(! ec)
d.ws.role_ = role_type::server; d.ws.open(detail::role_type::server);
break; break;
} }
} }

View File

@@ -13,6 +13,7 @@
#include <beast/websocket/impl/accept_op.ipp> #include <beast/websocket/impl/accept_op.ipp>
#include <beast/websocket/impl/close_op.ipp> #include <beast/websocket/impl/close_op.ipp>
#include <beast/websocket/impl/handshake_op.ipp> #include <beast/websocket/impl/handshake_op.ipp>
#include <beast/websocket/impl/ping_op.ipp>
#include <beast/websocket/impl/read_op.ipp> #include <beast/websocket/impl/read_op.ipp>
#include <beast/websocket/impl/read_frame_op.ipp> #include <beast/websocket/impl/read_frame_op.ipp>
#include <beast/websocket/impl/response_op.ipp> #include <beast/websocket/impl/response_op.ipp>
@@ -40,6 +41,13 @@ namespace websocket {
namespace detail { namespace detail {
template<class _>
void
stream_base::open(role_type role)
{
role_ = role;
}
template<class _> template<class _>
void void
stream_base::prepare_fh(close_code::value& code) stream_base::prepare_fh(close_code::value& code)
@@ -76,7 +84,7 @@ stream_base::prepare_fh(close_code::value& code)
} }
rd_size_ += rd_fh_.len; rd_size_ += rd_fh_.len;
} }
if(rd_size_ > rd_msg_max_) if(rd_msg_max_ && rd_size_ > rd_msg_max_)
{ {
code = close_code::too_big; code = close_code::too_big;
return; return;
@@ -100,7 +108,7 @@ stream_base::write_close(
fh.rsv3 = false; fh.rsv3 = false;
fh.len = cr.code == close_code::none ? fh.len = cr.code == close_code::none ?
0 : 2 + cr.reason.size(); 0 : 2 + cr.reason.size();
fh.mask = role_ == role_type::client; fh.mask = role_ == detail::role_type::client;
if(fh.mask) if(fh.mask)
fh.key = maskgen_(); fh.key = maskgen_();
detail::write(sb, fh); detail::write(sb, fh);
@@ -136,7 +144,7 @@ stream_base::write_close(
template<class Streambuf> template<class Streambuf>
void void
stream_base::write_ping(Streambuf& sb, stream_base::write_ping(Streambuf& sb,
opcode op, ping_payload_type const& data) opcode op, ping_data const& data)
{ {
frame_header fh; frame_header fh;
fh.op = op; fh.op = op;
@@ -184,7 +192,8 @@ accept()
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
accept(boost::asio::null_buffers{}, ec); accept(boost::asio::null_buffers{}, ec);
detail::maybe_throw(ec, "accept"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -223,7 +232,8 @@ accept(ConstBufferSequence const& buffers)
"ConstBufferSequence requirements not met"); "ConstBufferSequence requirements not met");
error_code ec; error_code ec;
accept(buffers, ec); accept(buffers, ec);
detail::maybe_throw(ec, "accept"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -239,10 +249,11 @@ accept(ConstBufferSequence const& buffers, error_code& ec)
"ConstBufferSequence requirements not met"); "ConstBufferSequence requirements not met");
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::buffer_size; using boost::asio::buffer_size;
reset();
stream_.buffer().commit(buffer_copy( stream_.buffer().commit(buffer_copy(
stream_.buffer().prepare( stream_.buffer().prepare(
buffer_size(buffers)), buffers)); buffer_size(buffers)), buffers));
http::request_v1<http::empty_body> m; http::request_v1<http::string_body> m;
http::read(next_layer(), stream_.buffer(), m, ec); http::read(next_layer(), stream_.buffer(), m, ec);
if(ec) if(ec)
return; return;
@@ -279,7 +290,8 @@ accept(http::request_v1<Body, Headers> const& request)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
accept(request, ec); accept(request, ec);
detail::maybe_throw(ec, "accept"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -291,8 +303,11 @@ accept(http::request_v1<Body, Headers> const& req,
{ {
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
reset();
auto const res = build_response(req); auto const res = build_response(req);
http::write(stream_, res, ec); http::write(stream_, res, ec);
if(ec)
return;
if(res.status != 101) if(res.status != 101)
{ {
ec = error::handshake_failed; ec = error::handshake_failed;
@@ -300,7 +315,7 @@ accept(http::request_v1<Body, Headers> const& req,
// teardown if Connection: close. // teardown if Connection: close.
return; return;
} }
role_ = role_type::server; open(detail::role_type::server);
} }
template<class NextLayer> template<class NextLayer>
@@ -316,6 +331,7 @@ async_accept(http::request_v1<Body, Headers> const& req,
beast::async_completion< beast::async_completion<
AcceptHandler, void(error_code) AcceptHandler, void(error_code)
> completion(handler); > completion(handler);
reset();
response_op<decltype(completion.handler)>{ response_op<decltype(completion.handler)>{
completion.handler, *this, req, completion.handler, *this, req,
boost_asio_handler_cont_helpers:: boost_asio_handler_cont_helpers::
@@ -333,7 +349,8 @@ handshake(boost::string_ref const& host,
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
handshake(host, resource, ec); handshake(host, resource, ec);
detail::maybe_throw(ec, "upgrade"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -344,6 +361,7 @@ handshake(boost::string_ref const& host,
{ {
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
reset();
std::string key; std::string key;
http::write(stream_, http::write(stream_,
build_request(host, resource, key), ec); build_request(host, resource, key), ec);
@@ -383,7 +401,8 @@ close(close_reason const& cr)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
close(cr, ec); close(cr, ec);
detail::maybe_throw(ec, "close"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -398,7 +417,7 @@ close(close_reason const& cr, error_code& ec)
detail::frame_streambuf fb; detail::frame_streambuf fb;
write_close<static_streambuf>(fb, cr); write_close<static_streambuf>(fb, cr);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
error_ = ec != 0; failed_ = ec != 0;
} }
template<class NextLayer> template<class NextLayer>
@@ -418,6 +437,45 @@ async_close(close_reason const& cr, CloseHandler&& handler)
return completion.result.get(); return completion.result.get();
} }
template<class NextLayer>
void
stream<NextLayer>::
ping(ping_data const& payload)
{
error_code ec;
ping(payload, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer>
void
stream<NextLayer>::
ping(ping_data const& payload, error_code& ec)
{
detail::frame_streambuf sb;
write_ping<static_streambuf>(
sb, opcode::ping, payload);
boost::asio::write(stream_, sb.data(), ec);
}
template<class NextLayer>
template<class PingHandler>
typename async_completion<
PingHandler, void(error_code)>::result_type
stream<NextLayer>::
async_ping(ping_data const& payload, PingHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements requirements not met");
beast::async_completion<
PingHandler, void(error_code)
> completion(handler);
ping_op<decltype(completion.handler)>{
completion.handler, *this, payload};
return completion.result.get();
}
template<class NextLayer> template<class NextLayer>
template<class Streambuf> template<class Streambuf>
void void
@@ -428,7 +486,8 @@ read(opcode& op, Streambuf& streambuf)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
read(op, streambuf, ec); read(op, streambuf, ec);
detail::maybe_throw(ec, "read"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -481,7 +540,8 @@ read_frame(frame_info& fi, Streambuf& streambuf)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
read_frame(fi, streambuf, ec); read_frame(fi, streambuf, ec);
detail::maybe_throw(ec, "read_some"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -500,8 +560,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
// read header // read header
detail::frame_streambuf fb; detail::frame_streambuf fb;
do_read_fh(fb, code, ec); do_read_fh(fb, code, ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
if(code != close_code::none) if(code != close_code::none)
break; break;
@@ -513,8 +573,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
auto const mb = fb.prepare( auto const mb = fb.prepare(
static_cast<std::size_t>(rd_fh_.len)); static_cast<std::size_t>(rd_fh_.len));
fb.commit(boost::asio::read(stream_, mb, ec)); fb.commit(boost::asio::read(stream_, mb, ec));
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
if(rd_fh_.mask) if(rd_fh_.mask)
detail::mask_inplace(mb, rd_key_); detail::mask_inplace(mb, rd_key_);
@@ -522,27 +582,23 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
} }
if(rd_fh_.op == opcode::ping) if(rd_fh_.op == opcode::ping)
{ {
ping_payload_type data; ping_data data;
detail::read(data, fb.data(), code); detail::read(data, fb.data());
if(code != close_code::none)
break;
fb.reset(); fb.reset();
write_ping<static_streambuf>( write_ping<static_streambuf>(
fb, opcode::pong, data); fb, opcode::pong, data);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
continue; continue;
} }
else if(rd_fh_.op == opcode::pong) else if(rd_fh_.op == opcode::pong)
{ {
ping_payload_type data; ping_data payload;
detail::read(data, fb.data(), code); detail::read(payload, fb.data());
if(code != close_code::none) if(pong_cb_)
break; pong_cb_(payload);
// VFALCO How to notify callers using
// the synchronous interface?
continue; continue;
} }
assert(rd_fh_.op == opcode::close); assert(rd_fh_.op == opcode::close);
@@ -560,8 +616,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
wr_close_ = true; wr_close_ = true;
write_close<static_streambuf>(fb, cr); write_close<static_streambuf>(fb, cr);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
} }
break; break;
@@ -578,8 +634,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
detail::clamp(rd_need_)); detail::clamp(rd_need_));
auto const bytes_transferred = auto const bytes_transferred =
stream_.read_some(smb, ec); stream_.read_some(smb, ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
rd_need_ -= bytes_transferred; rd_need_ -= bytes_transferred;
auto const pb = prepare_buffers( auto const pb = prepare_buffers(
@@ -610,23 +666,23 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec)
detail::frame_streambuf fb; detail::frame_streambuf fb;
write_close<static_streambuf>(fb, code); write_close<static_streambuf>(fb, code);
boost::asio::write(stream_, fb.data(), ec); boost::asio::write(stream_, fb.data(), ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
} }
websocket_helpers::call_teardown(next_layer(), ec); websocket_helpers::call_teardown(next_layer(), ec);
error_ = ec != 0; failed_ = ec != 0;
if(error_) if(failed_)
return; return;
ec = error::failed; ec = error::failed;
error_ = true; failed_ = true;
return; return;
} }
if(! ec) if(! ec)
websocket_helpers::call_teardown(next_layer(), ec); websocket_helpers::call_teardown(next_layer(), ec);
if(! ec) if(! ec)
ec = error::closed; ec = error::closed;
error_ = ec != 0; failed_ = ec != 0;
} }
template<class NextLayer> template<class NextLayer>
@@ -658,7 +714,8 @@ write(ConstBufferSequence const& buffers)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
write(buffers, ec); write(buffers, ec);
detail::maybe_throw(ec, "write"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -719,7 +776,8 @@ write_frame(bool fin, ConstBufferSequence const& buffers)
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
write_frame(fin, buffers, ec); write_frame(fin, buffers, ec);
detail::maybe_throw(ec, "write"); if(ec)
throw system_error{ec};
} }
template<class NextLayer> template<class NextLayer>
@@ -744,7 +802,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec)
fh.rsv2 = false; fh.rsv2 = false;
fh.rsv3 = false; fh.rsv3 = false;
fh.len = buffer_size(bs); fh.len = buffer_size(bs);
fh.mask = role_ == role_type::client; fh.mask = role_ == detail::role_type::client;
if(fh.mask) if(fh.mask)
fh.key = maskgen_(); fh.key = maskgen_();
detail::fh_streambuf fh_buf; detail::fh_streambuf fh_buf;
@@ -754,22 +812,21 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec)
// send header and payload // send header and payload
boost::asio::write(stream_, boost::asio::write(stream_,
buffer_cat(fh_buf.data(), bs), ec); buffer_cat(fh_buf.data(), bs), ec);
error_ = ec != 0; failed_ = ec != 0;
return; return;
} }
detail::prepared_key_type key; detail::prepared_key_type key;
detail::prepare_key(key, fh.key); detail::prepare_key(key, fh.key);
auto const tmp_size = detail::clamp( auto const tmp_size =
fh.len, wr_buf_size_); detail::clamp(fh.len, mask_buf_size_);
std::unique_ptr<std::uint8_t[]> up( std::unique_ptr<std::uint8_t[]> up(
new std::uint8_t[tmp_size]); new std::uint8_t[tmp_size]);
auto const tmp = up.get();
std::uint64_t remain = fh.len; std::uint64_t remain = fh.len;
consuming_buffers<ConstBufferSequence> cb(bs); consuming_buffers<ConstBufferSequence> cb(bs);
{ {
auto const n = auto const n =
detail::clamp(remain, tmp_size); detail::clamp(remain, tmp_size);
mutable_buffers_1 mb{tmp, n}; mutable_buffers_1 mb{up.get(), n};
buffer_copy(mb, cb); buffer_copy(mb, cb);
cb.consume(n); cb.consume(n);
remain -= n; remain -= n;
@@ -779,7 +836,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec)
buffer_cat(fh_buf.data(), mb), ec); buffer_cat(fh_buf.data(), mb), ec);
if(ec) if(ec)
{ {
error_ = ec != 0; failed_ = ec != 0;
return; return;
} }
} }
@@ -787,7 +844,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec)
{ {
auto const n = auto const n =
detail::clamp(remain, tmp_size); detail::clamp(remain, tmp_size);
mutable_buffers_1 mb{tmp, n}; mutable_buffers_1 mb{up.get(), n};
buffer_copy(mb, cb); buffer_copy(mb, cb);
cb.consume(n); cb.consume(n);
remain -= n; remain -= n;
@@ -796,7 +853,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec)
boost::asio::write(stream_, mb, ec); boost::asio::write(stream_, mb, ec);
if(ec) if(ec)
{ {
error_ = ec != 0; failed_ = ec != 0;
return; return;
} }
} }
@@ -826,6 +883,23 @@ async_write_frame(bool fin,
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template<class NextLayer>
void
stream<NextLayer>::
reset()
{
failed_ = false;
rd_need_ = 0;
rd_cont_ = false;
wr_close_ = false;
wr_cont_ = false;
wr_block_ = nullptr; // should be nullptr on close anyway
pong_data_ = nullptr; // should be nullptr on close anyway
stream_.buffer().consume(
stream_.buffer().size());
}
template<class NextLayer> template<class NextLayer>
http::request_v1<http::empty_body> http::request_v1<http::empty_body>
stream<NextLayer>:: stream<NextLayer>::
@@ -860,8 +934,11 @@ build_response(http::request_v1<Body, Headers> const& req)
res.reason = http::reason_string(res.status); res.reason = http::reason_string(res.status);
res.version = req.version; res.version = req.version;
res.body = text; res.body = text;
// VFALCO TODO respect keep-alive here (*d_)(res);
prepare(res); prepare(res,
(is_keep_alive(req) && keep_alive_) ?
http::connection::keep_alive :
http::connection::close);
return res; return res;
}; };
if(req.version < 11) if(req.version < 11)
@@ -874,17 +951,28 @@ build_response(http::request_v1<Body, Headers> const& req)
return err("Missing Host"); return err("Missing Host");
if(! req.headers.exists("Sec-WebSocket-Key")) if(! req.headers.exists("Sec-WebSocket-Key"))
return err("Missing Sec-WebSocket-Key"); return err("Missing Sec-WebSocket-Key");
if(! rfc2616::token_in_list(
req.headers["Upgrade"], "websocket"))
return err("Missing websocket Upgrade token");
{ {
auto const version = auto const version =
req.headers["Sec-WebSocket-Version"]; req.headers["Sec-WebSocket-Version"];
if(version.empty()) if(version.empty())
return err("Missing Sec-WebSocket-Version"); return err("Missing Sec-WebSocket-Version");
if(version != "13") if(version != "13")
return err("Unsupported Sec-WebSocket-Version"); {
http::response_v1<http::string_body> res;
res.status = 426;
res.reason = http::reason_string(res.status);
res.version = req.version;
res.headers.insert("Sec-WebSocket-Version", "13");
prepare(res,
(is_keep_alive(req) && keep_alive_) ?
http::connection::keep_alive :
http::connection::close);
return res;
}
} }
if(! rfc2616::token_in_list(
req.headers["Upgrade"], "websocket"))
return err("Missing websocket Upgrade token");
http::response_v1<http::string_body> res; http::response_v1<http::string_body> res;
res.status = 101; res.status = 101;
res.reason = http::reason_string(res.status); res.reason = http::reason_string(res.status);
@@ -893,7 +981,6 @@ build_response(http::request_v1<Body, Headers> const& req)
{ {
auto const key = auto const key =
req.headers["Sec-WebSocket-Key"]; req.headers["Sec-WebSocket-Key"];
res.headers.insert("Sec-WebSocket-Key", key);
res.headers.insert("Sec-WebSocket-Accept", res.headers.insert("Sec-WebSocket-Accept",
detail::make_sec_ws_accept(key)); detail::make_sec_ws_accept(key));
} }
@@ -912,6 +999,8 @@ do_response(http::response_v1<Body, Headers> const& res,
{ {
// VFALCO Review these error codes // VFALCO Review these error codes
auto fail = [&]{ ec = error::response_failed; }; auto fail = [&]{ ec = error::response_failed; };
if(res.version < 11)
return fail();
if(res.status != 101) if(res.status != 101)
return fail(); return fail();
if(! is_upgrade(res)) if(! is_upgrade(res))
@@ -924,7 +1013,7 @@ do_response(http::response_v1<Body, Headers> const& res,
if(res.headers["Sec-WebSocket-Accept"] != if(res.headers["Sec-WebSocket-Accept"] !=
detail::make_sec_ws_accept(key)) detail::make_sec_ws_accept(key))
return fail(); return fail();
role_ = role_type::client; open(detail::role_type::client);
} }
template<class NextLayer> template<class NextLayer>

View File

@@ -56,8 +56,7 @@ public:
} }
void void
operator()( operator()(error_code ec, std::size_t, bool again = true);
error_code ec, std::size_t, bool again = true);
friend friend
void* asio_handler_allocate(std::size_t size, void* asio_handler_allocate(std::size_t size,

View File

@@ -57,17 +57,17 @@ class stream<NextLayer>::write_frame_op
opcode::cont : ws.wr_opcode_; opcode::cont : ws.wr_opcode_;
ws.wr_cont_ = ! fin; ws.wr_cont_ = ! fin;
fh.fin = fin; fh.fin = fin;
fh.rsv1 = 0; fh.rsv1 = false;
fh.rsv2 = 0; fh.rsv2 = false;
fh.rsv3 = 0; fh.rsv3 = false;
fh.len = boost::asio::buffer_size(cb); fh.len = boost::asio::buffer_size(cb);
fh.mask = ws.role_ == role_type::client; fh.mask = ws.role_ == detail::role_type::client;
if(fh.mask) if(fh.mask)
{ {
fh.key = ws.maskgen_(); fh.key = ws.maskgen_();
detail::prepare_key(key, fh.key); detail::prepare_key(key, fh.key);
tmp_size = detail::clamp( tmp_size = detail::clamp(
fh.len, ws.wr_buf_size_); fh.len, ws.mask_buf_size_);
tmp = boost_asio_handler_alloc_helpers:: tmp = boost_asio_handler_alloc_helpers::
allocate(tmp_size, h); allocate(tmp_size, h);
remain = fh.len; remain = fh.len;
@@ -100,18 +100,17 @@ public:
std::forward<DeducedHandler>(h), ws, std::forward<DeducedHandler>(h), ws,
std::forward<Args>(args)...)) std::forward<Args>(args)...))
{ {
(*this)(error_code{}, 0, false); (*this)(error_code{}, false);
} }
void operator()() void operator()()
{ {
auto& d = *d_; (*this)(error_code{});
d.cont = false;
(*this)(error_code{}, 0, false);
} }
void operator()(error_code ec, void operator()(error_code ec, std::size_t);
std::size_t bytes_transferred, bool again = true);
void operator()(error_code ec, bool again = true);
friend friend
void* asio_handler_allocate( void* asio_handler_allocate(
@@ -144,19 +143,33 @@ public:
} }
}; };
template<class NextLayer>
template<class Buffers, class Handler>
void
stream<NextLayer>::
write_frame_op<Buffers, Handler>::
operator()(error_code ec, std::size_t)
{
auto& d = *d_;
if(ec)
d.ws.failed_ = true;
(*this)(ec);
}
template<class NextLayer> template<class NextLayer>
template<class Buffers, class Handler> template<class Buffers, class Handler>
void void
stream<NextLayer>:: stream<NextLayer>::
write_frame_op<Buffers, Handler>:: write_frame_op<Buffers, Handler>::
operator()( operator()(error_code ec, bool again)
error_code ec, std::size_t bytes_transferred, bool again)
{ {
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::mutable_buffers_1; using boost::asio::mutable_buffers_1;
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;
while(! ec && d.state != 99) if(ec)
goto upcall;
for(;;)
{ {
switch(d.state) switch(d.state)
{ {
@@ -164,41 +177,27 @@ operator()(
if(d.ws.wr_block_) if(d.ws.wr_block_)
{ {
// suspend // suspend
d.state = 1; d.state = 3;
d.ws.wr_op_.template emplace< d.ws.wr_op_.template emplace<
write_frame_op>(std::move(*this)); write_frame_op>(std::move(*this));
return; return;
} }
if(d.ws.error_) if(d.ws.failed_ || d.ws.wr_close_)
{ {
// call handler // call handler
d.state = 99; d.state = 99;
d.ws.get_io_service().post( d.ws.get_io_service().post(
bind_handler(std::move(*this), bind_handler(std::move(*this),
boost::asio::error::operation_aborted, 0)); boost::asio::error::operation_aborted));
return; return;
} }
assert(! d.ws.wr_close_); // fall through
d.state = 2;
break;
// resume
case 1: case 1:
if(d.ws.error_)
{
// call handler
d.state = 99;
ec = boost::asio::error::operation_aborted;
break;
}
d.state = 2;
break;
case 2:
{ {
if(! d.fh.mask) if(! d.fh.mask)
{ {
// send header and payload // send header and entire payload
d.state = 99; d.state = 99;
assert(! d.ws.wr_block_); assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d; d.ws.wr_block_ = &d;
@@ -215,7 +214,7 @@ operator()(
d.remain -= n; d.remain -= n;
detail::mask_inplace(mb, d.key); detail::mask_inplace(mb, d.key);
// send header and payload // send header and payload
d.state = d.remain > 0 ? 3 : 99; d.state = d.remain > 0 ? 2 : 99;
assert(! d.ws.wr_block_); assert(! d.ws.wr_block_);
d.ws.wr_block_ = &d; d.ws.wr_block_ = &d;
boost::asio::async_write(d.ws.stream_, boost::asio::async_write(d.ws.stream_,
@@ -225,7 +224,7 @@ operator()(
} }
// sent masked payload // sent masked payload
case 3: case 2:
{ {
auto const n = auto const n =
detail::clamp(d.remain, d.tmp_size); detail::clamp(d.remain, d.tmp_size);
@@ -238,24 +237,41 @@ operator()(
// send payload // send payload
if(d.remain == 0) if(d.remain == 0)
d.state = 99; d.state = 99;
assert(! d.ws.wr_block_); assert(d.ws.wr_block_ == &d);
d.ws.wr_block_ = &d;
boost::asio::async_write( boost::asio::async_write(
d.ws.stream_, mb, std::move(*this)); d.ws.stream_, mb, std::move(*this));
return; return;
} }
case 3:
d.state = 4;
d.ws.get_io_service().post(bind_handler(
std::move(*this), ec));
return;
case 4:
if(d.ws.failed_ || d.ws.wr_close_)
{
// call handler
ec = boost::asio::error::operation_aborted;
goto upcall;
}
d.state = 1;
break;
case 99:
goto upcall;
} }
} }
if(ec) upcall:
d.ws.error_ = true;
if(d.ws.wr_block_ == &d)
d.ws.wr_block_ = nullptr;
if(d.tmp) if(d.tmp)
{ {
boost_asio_handler_alloc_helpers:: boost_asio_handler_alloc_helpers::
deallocate(d.tmp, d.tmp_size, d.h); deallocate(d.tmp, d.tmp_size, d.h);
d.tmp = nullptr; d.tmp = nullptr;
} }
if(d.ws.wr_block_ == &d)
d.ws.wr_block_ = nullptr;
d.ws.rd_op_.maybe_invoke(); d.ws.rd_op_.maybe_invoke();
d.h(ec); d.h(ec);
} }

View File

@@ -109,7 +109,7 @@ operator()(error_code ec, bool again)
{ {
auto& d = *d_; auto& d = *d_;
d.cont = d.cont || again; d.cont = d.cont || again;
while(! ec && d.state != 99) if(! ec)
{ {
switch(d.state) switch(d.state)
{ {
@@ -126,6 +126,9 @@ operator()(error_code ec, bool again)
d.ws.async_write_frame(fin, pb, std::move(*this)); d.ws.async_write_frame(fin, pb, std::move(*this));
return; return;
} }
case 99:
break;
} }
} }
d.h(ec); d.h(ec);

View File

@@ -8,11 +8,13 @@
#ifndef BEAST_WEBSOCKET_OPTION_HPP #ifndef BEAST_WEBSOCKET_OPTION_HPP
#define BEAST_WEBSOCKET_OPTION_HPP #define BEAST_WEBSOCKET_OPTION_HPP
#include <beast/websocket/rfc6455.hpp>
#include <beast/websocket/detail/stream_base.hpp> #include <beast/websocket/detail/stream_base.hpp>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <stdexcept> #include <stdexcept>
#include <type_traits> #include <type_traits>
#include <utility>
namespace beast { namespace beast {
namespace websocket { namespace websocket {
@@ -147,6 +149,45 @@ struct keep_alive
}; };
#endif #endif
/** Mask buffer size option.
Sets the size of the buffer allocated when the implementation
must allocate memory to apply the mask to a payload. Only affects
streams operating in the client role, since only clients send
masked frames. Lowering the size of the buffer can decrease the
memory requirements for each connection, while increasing the size
of the buffer can reduce the number of calls made to the next
layer to write masked data.
The default setting is 4096. The minimum value is 1.
@note Objects of this type are passed to @ref stream::set_option.
@par Example
Setting the write buffer size.
@code
...
websocket::stream<ip::tcp::socket> ws(ios);
ws.set_option(mask_buffer_size{8192});
@endcode
*/
#if GENERATING_DOCS
using mask_buffer_size = implementation_defined;
#else
struct mask_buffer_size
{
std::size_t value;
explicit
mask_buffer_size(std::size_t n)
: value(n)
{
if(n == 0)
throw std::domain_error("invalid mask buffer size");
}
};
#endif
/** Message type option. /** Message type option.
This controls the opcode set for outgoing messages. Valid This controls the opcode set for outgoing messages. Valid
@@ -184,6 +225,50 @@ struct message_type
}; };
#endif #endif
/** Pong callback option.
Sets the callback to be invoked whenever a pong is received
during a call to @ref read, @ref read_frame, @ref async_read,
or @ref async_read_frame.
Unlike completion handlers, the callback will be invoked for
each received pong during a call to any synchronous or
asynchronous read function. The operation is passive, with
no associated error code, and triggered by reads.
The signature of the callback must be:
@code
void callback(
ping_data const& payload // Payload of the pong frame
);
@endcode
If the read operation receiving a pong frame is an asynchronous
operation, the callback will be invoked using the same method as
that used to invoke the final handler.
@note To remove the pong callback, construct the option with
no parameters: `set_option(pong_callback{})`
*/
#if GENERATING_DOCS
using pong_callback = implementation_defined;
#else
struct pong_callback
{
detail::pong_cb value;
pong_callback() = default;
pong_callback(pong_callback&&) = default;
pong_callback(pong_callback const&) = default;
explicit
pong_callback(detail::pong_cb f)
: value(std::move(f))
{
}
};
#endif
/** Read buffer size option. /** Read buffer size option.
Sets the number of bytes allocated to the socket's read buffer. Sets the number of bytes allocated to the socket's read buffer.
@@ -224,7 +309,8 @@ struct read_buffer_size
frame headers indicating a size that would bring the total frame headers indicating a size that would bring the total
message size over this limit will cause a protocol failure. message size over this limit will cause a protocol failure.
The default setting is 16 megabytes. The default setting is 16 megabytes. A value of zero indicates
a limit of `std::numeric_limits<std::uint64_t>::max()`.
@note Objects of this type are passed to @ref stream::set_option. @note Objects of this type are passed to @ref stream::set_option.
@@ -251,44 +337,6 @@ struct read_message_max
}; };
#endif #endif
/** Write buffer size option.
Sets the number of bytes allocated to the socket's write buffer.
This buffer is used to hold masked frame payload data. Lowering
the size of the buffer can decrease the memory requirements for
each connection, at the cost of an increased number of calls to
perform socket writes.
This setting does not affect connections operating in the server
role, since servers do not apply a masking key to frame payloads.
The default setting is 4096. The minimum value is 1024.
@note Objects of this type are passed to @ref stream::set_option.
@par Example
Setting the write buffer size.
@code
...
websocket::stream<ip::tcp::socket> ws(ios);
ws.set_option(write_buffer_size{8192});
@endcode
*/
#if GENERATING_DOCS
using write_buffer_size = implementation_defined;
#else
struct write_buffer_size
{
std::size_t value;
explicit
write_buffer_size(std::size_t n)
: value(n)
{
}
};
#endif
} // websocket } // websocket
} // beast } // beast

View File

@@ -60,6 +60,13 @@ enum
protocol_error = 1002, protocol_error = 1002,
unknown_data = 1003, unknown_data = 1003,
/// Indicates a received close frame has no close code
//no_code = 1005, // TODO
/// Indicates the connection was closed without receiving a close frame
no_close = 1006,
bad_payload = 1007, bad_payload = 1007,
policy_error = 1008, policy_error = 1008,
too_big = 1009, too_big = 1009,
@@ -80,12 +87,11 @@ enum
} // close_code } // close_code
#endif #endif
#if ! GENERATING_DOCS /// The type representing the reason string in a close frame.
using reason_string_type = using reason_string = static_string<123, char>;
static_string<123, char>;
using ping_payload_type = /// The type representing the payload of ping and pong messages.
static_string<125, char>; using ping_data = static_string<125, char>;
#endif
/** Description of the close reason. /** Description of the close reason.
@@ -98,7 +104,7 @@ struct close_reason
close_code::value code = close_code::none; close_code::value code = close_code::none;
/// The optional utf8-encoded reason string. /// The optional utf8-encoded reason string.
reason_string_type reason; reason_string reason;
/** Default constructor. /** Default constructor.
@@ -114,17 +120,17 @@ struct close_reason
} }
/// Construct from a reason. code is close_code::normal. /// Construct from a reason. code is close_code::normal.
template<class CharT> template<std::size_t N>
close_reason(CharT const* reason_) close_reason(char const (&reason_)[N])
: code(close_code::normal) : code(close_code::normal)
, reason(reason_) , reason(reason_)
{ {
} }
/// Construct from a code and reason. /// Construct from a code and reason.
template<class CharT> template<std::size_t N>
close_reason(close_code::value code_, close_reason(close_code::value code_,
CharT const* reason_) char const (&reason_)[N])
: code(code_) : code(code_)
, reason(reason_) , reason(reason_)
{ {
@@ -137,20 +143,6 @@ struct close_reason
} }
}; };
#if ! GENERATING_DOCS
/// Identifies the role of a WebSockets stream.
enum class role_type
{
/// Stream is operating as a client.
client,
/// Stream is operating as a server.
server
};
#endif
} // websocket } // websocket
} // beast } // beast

View File

@@ -69,8 +69,8 @@ struct frame_info
@tparam NextLayer The type representing the next layer, to which @tparam NextLayer The type representing the next layer, to which
data will be read and written during operations. For synchronous data will be read and written during operations. For synchronous
operations, the type must support the @b `SyncStream` concept. operations, the type must support the @b `SyncStream` concept.
For asynchronous operations, the type must support the @b `AsyncStream` For asynchronous operations, the type must support the
concept. @b `AsyncStream` concept.
@note A stream object must not be destroyed while there are @note A stream object must not be destroyed while there are
pending asynchronous operations associated with it. pending asynchronous operations associated with it.
@@ -84,7 +84,7 @@ struct frame_info
template<class NextLayer> template<class NextLayer>
class stream : public detail::stream_base class stream : public detail::stream_base
{ {
friend class ws_test; friend class stream_test;
streambuf_readstream<NextLayer, streambuf> stream_; streambuf_readstream<NextLayer, streambuf> stream_;
@@ -124,12 +124,12 @@ public:
/** Construct a WebSocket stream. /** Construct a WebSocket stream.
This constructor creates a websocket stream and initialises This constructor creates a websocket stream and initializes
the next layer object. the next layer object.
@throws Any exceptions thrown by the NextLayer constructor. @throws Any exceptions thrown by the NextLayer constructor.
@param args The arguments to be passed to initialise the @param args The arguments to be passed to initialize the
next layer object. The arguments are forwarded to the next next layer object. The arguments are forwarded to the next
layer's constructor. layer's constructor.
*/ */
@@ -198,6 +198,13 @@ public:
wr_opcode_ = o.value; wr_opcode_ = o.value;
} }
/// Set the pong callback
void
set_option(pong_callback o)
{
pong_cb_ = std::move(o.value);
}
/// Set the read buffer size /// Set the read buffer size
void void
set_option(read_buffer_size const& o) set_option(read_buffer_size const& o)
@@ -212,11 +219,11 @@ public:
rd_msg_max_ = o.value; rd_msg_max_ = o.value;
} }
/// Set the size of the write buffer /// Set the size of the mask buffer
void void
set_option(write_buffer_size const& o) set_option(mask_buffer_size const& o)
{ {
wr_buf_size_ = std::max<std::size_t>(o.value, 1024); mask_buf_size_ = o.value;
stream_.capacity(o.value); stream_.capacity(o.value);
} }
@@ -788,7 +795,7 @@ public:
/** Send a WebSocket close frame. /** Send a WebSocket close frame.
This function is used to sycnhronously send a close frame on This function is used to synchronously send a close frame on
the stream. The call blocks until one of the following is true: the stream. The call blocks until one of the following is true:
@li The close frame finishes sending. @li The close frame finishes sending.
@@ -817,7 +824,7 @@ public:
/** Send a WebSocket close frame. /** Send a WebSocket close frame.
This function is used to sycnhronously send a close frame on This function is used to synchronously send a close frame on
the stream. The call blocks until one of the following is true: the stream. The call blocks until one of the following is true:
@li The close frame finishes sending. @li The close frame finishes sending.
@@ -844,7 +851,7 @@ public:
void void
close(close_reason const& cr, error_code& ec); close(close_reason const& cr, error_code& ec);
/** Start an asycnhronous operation to send a WebSocket close frame. /** Start an asynchronous operation to send a WebSocket close frame.
This function is used to asynchronously send a close frame on This function is used to asynchronously send a close frame on
the stream. This function call always returns immediately. The the stream. This function call always returns immediately. The
@@ -858,7 +865,7 @@ public:
This operation is implemented in terms of one or more calls to the This operation is implemented in terms of one or more calls to the
next layer's `async_write_some` functions, and is known as a next layer's `async_write_some` functions, and is known as a
<em>composed operation</em>. The program must ensure that the <em>composed operation</em>. The program must ensure that the
stream performs no other write operations (such as stream performs no other write operations (such as @ref async_ping,
@ref stream::async_write, @ref stream::async_write_frame, or @ref stream::async_write, @ref stream::async_write_frame, or
@ref stream::async_close) until this operation completes. @ref stream::async_close) until this operation completes.
@@ -896,6 +903,84 @@ public:
#endif #endif
async_close(close_reason const& cr, CloseHandler&& handler); async_close(close_reason const& cr, CloseHandler&& handler);
/** Send a WebSocket ping frame.
This function is used to synchronously send a ping frame on
the stream. The call blocks until one of the following is true:
@li The ping frame finishes sending.
@li An error occurs on the stream.
This function is implemented in terms of one or more calls to the
next layer's `write_some` functions.
@param payload The payload of the ping message, which may be empty.
@throws boost::system::system_error Thrown on failure.
*/
void
ping(ping_data const& payload);
/** Send a WebSocket ping frame.
This function is used to synchronously send a ping frame on
the stream. The call blocks until one of the following is true:
@li The ping frame finishes sending.
@li An error occurs on the stream.
This function is implemented in terms of one or more calls to the
next layer's `write_some` functions.
@param payload The payload of the ping message, which may be empty.
@param ec Set to indicate what error occurred, if any.
*/
void
ping(ping_data const& payload, error_code& ec);
/** Start an asynchronous operation to send a WebSocket ping frame.
This function is used to asynchronously send a ping frame to
the stream. The function call always returns immediately. The
asynchronous operation will continue until one of the following
is true:
@li The entire ping frame is sent.
@li An error occurs on the stream.
This operation is implemented in terms of one or more calls to the
next layer's `async_write_some` functions, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other writes until this operation completes.
@param payload The payload of the ping message, which may be empty.
@param handler The handler to be called when the read operation
completes. Copies will be made of the handler as required. The
function signature of the handler must be:
@code
void handler(
error_code const& error // Result of operation
);
@endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<class PingHandler>
#if GENERATING_DOCS
void_or_deduced
#else
typename async_completion<
PingHandler, void(error_code)>::result_type
#endif
async_ping(ping_data const& payload, PingHandler&& handler);
/** Read a message from the stream. /** Read a message from the stream.
This function is used to synchronously read a message from This function is used to synchronously read a message from
@@ -969,7 +1054,7 @@ public:
/** Start an asynchronous operation to read a message from the stream. /** Start an asynchronous operation to read a message from the stream.
This function is used to asychronously read a message from This function is used to asynchronously read a message from
the stream. The function call always returns immediately. The the stream. The function call always returns immediately. The
asynchronous operation will continue until one of the following asynchronous operation will continue until one of the following
is true: is true:
@@ -981,7 +1066,7 @@ public:
This operation is implemented in terms of one or more calls to the This operation is implemented in terms of one or more calls to the
next layer's `async_read_some` and `async_write_some` functions, next layer's `async_read_some` and `async_write_some` functions,
and is known as a <em>composed operation</em>. The program must and is known as a <em>composed operation</em>. The program must
ensure that the stream performs no other until this operation ensure that the stream performs no other reads until this operation
completes. completes.
Upon a success, op is set to either binary or text depending on Upon a success, op is set to either binary or text depending on
@@ -989,15 +1074,14 @@ public:
hold all the message payload bytes (which may be zero in length). hold all the message payload bytes (which may be zero in length).
Control frames encountered while reading frame or message data Control frames encountered while reading frame or message data
are handled automatically. Pings are replied to, pongs are noted, are handled automatically. Pings are replied to, pongs cause
and close frames initiate the WebSocket close procedure. When a an outstanding call to `async_ping` to complete, and close
close frame is received, this call will eventually return frames initiate the WebSocket close procedure. When a close
@ref error::closed. Because of the need to handle control frames, frame is received, this call will eventually return
read operations can cause writes to take place. These writes are @ref error::closed. Because of the need to handle control
managed transparently; callers can still have one active frames, these read operations can cause writes to take place.
asynchronous read and asynchronous write operation pending Despite this, calls to `async_read` and `async_read_frame`
simultaneously (a user initiated call to @ref async_close only count as read operations.
counts as a write).
@param op A value to receive the message type. @param op A value to receive the message type.
This object must remain valid until the handler is called. This object must remain valid until the handler is called.
@@ -1104,7 +1188,7 @@ public:
/** Start an asynchronous operation to read a message frame from the stream. /** Start an asynchronous operation to read a message frame from the stream.
This function is used to asychronously read a single message This function is used to asynchronously read a single message
frame from the websocket. The function call always returns frame from the websocket. The function call always returns
immediately. The asynchronous operation will continue until immediately. The asynchronous operation will continue until
one of the following conditions is true: one of the following conditions is true:
@@ -1116,7 +1200,7 @@ public:
This operation is implemented in terms of one or more calls to the This operation is implemented in terms of one or more calls to the
next layer's `async_read_some` and `async_write_some` functions, next layer's `async_read_some` and `async_write_some` functions,
and is known as a <em>composed operation</em>. The program must and is known as a <em>composed operation</em>. The program must
ensure that the stream performs no other until this operation ensure that the stream performs no other reads until this operation
completes. completes.
Upon a successful completion, fi is filled out to reflect the Upon a successful completion, fi is filled out to reflect the
@@ -1242,7 +1326,7 @@ public:
/** Start an asynchronous operation to write a message to the stream. /** Start an asynchronous operation to write a message to the stream.
This function is used to asychronously write a message to This function is used to asynchronously write a message to
the stream. The function call always returns immediately. the stream. The function call always returns immediately.
The asynchronous operation will continue until one of the The asynchronous operation will continue until one of the
following conditions is true: following conditions is true:
@@ -1407,11 +1491,15 @@ private:
template<class Handler> class accept_op; template<class Handler> class accept_op;
template<class Handler> class close_op; template<class Handler> class close_op;
template<class Handler> class handshake_op; template<class Handler> class handshake_op;
template<class Handler> class ping_op;
template<class Handler> class response_op; template<class Handler> class response_op;
template<class Streambuf, class Handler> class read_op;
template<class Streambuf, class Handler> class read_frame_op;
template<class Buffers, class Handler> class write_op; template<class Buffers, class Handler> class write_op;
template<class Buffers, class Handler> class write_frame_op; template<class Buffers, class Handler> class write_frame_op;
template<class Streambuf, class Handler> class read_op;
template<class Streambuf, class Handler> class read_frame_op;
void
reset();
http::request_v1<http::empty_body> http::request_v1<http::empty_body>
build_request(boost::string_ref const& host, build_request(boost::string_ref const& host,

View File

@@ -6,7 +6,16 @@ set -ex
################################## ENVIRONMENT ################################# ################################## ENVIRONMENT #################################
export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH # If not CI, then set some defaults
if [[ "${CI:-}" == "" ]]; then
TRAVIS_BRANCH=${TRAVIS_BRANCH:-feature}
CC=${CC:-gcc}
ADDRESS_MODEL=${ADDRESS_MODEL:-64}
VARIANT=${VARIANT:-debug}
# If running locally we assume we have lcov/valgrind on PATH
else
export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH
fi
echo "using toolset: $CC" echo "using toolset: $CC"
echo "using variant: $VARIANT" echo "using variant: $VARIANT"
@@ -19,10 +28,25 @@ function run_tests_with_gdb {
for x in bin/**/*-tests; do scripts/run-with-gdb.sh "$x"; done for x in bin/**/*-tests; do scripts/run-with-gdb.sh "$x"; done
} }
function run_tests {
for x in bin/**/*-tests; do "$x"; done
}
num_procs=1
if [[ $(uname) == "Darwin" ]]; then
num_procs=$(sysctl -n hw.ncpu)
elif [[ $(uname -s) == "Linux" ]]; then
num_procs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) # physical cores
virt_num_procs=$(nproc) # CircleCI returns 32 phys procs, but 1 virt proc
if (("$virt_num_procs" < "$num_procs")); then
num_procs=$virt_num_procs
fi
fi
function build_beast { function build_beast {
$BOOST_ROOT/bjam toolset=$CC \ $BOOST_ROOT/bjam toolset=$CC \
variant=$VARIANT \ variant=$VARIANT \
address-model=$ADDRESS_MODEL address-model=$ADDRESS_MODEL -j${num_procs}
} }
##################################### BUILD #################################### ##################################### BUILD ####################################
@@ -38,24 +62,24 @@ if [[ $VARIANT == "coverage" ]]; then
lcov --no-external -c -i -d . -o baseline.info > /dev/null lcov --no-external -c -i -d . -o baseline.info > /dev/null
# Perform test # Perform test
run_tests_with_gdb run_tests
# Run autobahn tests # Run autobahn tests
export SERVER=`find . -name "websocket-echo"` export SERVER=$(find . -name "websocket-echo")
nohup scripts/run-with-gdb.sh $SERVER& nohup $SERVER&
# We need to wait a while so wstest can connect! # We need to wait a while so wstest can connect!
sleep 5 sleep 5
# cd scripts && wstest -m fuzzingclient cd scripts && wstest -m fuzzingclient
# cd .. cd ..
# Show the output # Show the output
cat nohup.out cat nohup.out
rm nohup.out rm nohup.out
jobs jobs
sleep 5
# Kill it gracefully # Kill it gracefully
kill -INT %1 kill -INT %1
sleep 1 wait
kill -INT %1 || echo "Dead already"
# Create test coverage data file # Create test coverage data file
lcov --no-external -c -d . -o testrun.info > /dev/null lcov --no-external -c -d . -o testrun.info > /dev/null

View File

@@ -9,7 +9,7 @@ do
test -x $( type -p ${g}-$GCC_VER ) test -x $( type -p ${g}-$GCC_VER )
ln -sv $(type -p ${g}-$GCC_VER) $HOME/bin/${g} ln -sv $(type -p ${g}-$GCC_VER) $HOME/bin/${g}
done done
for c in clang clang++ for c in clang clang++ llvm-symbolizer
do do
test -x $( type -p ${c}-$CLANG_VER ) test -x $( type -p ${c}-$CLANG_VER )
ln -sv $(type -p ${c}-$CLANG_VER) $HOME/bin/${c} ln -sv $(type -p ${c}-$CLANG_VER) $HOME/bin/${c}

View File

@@ -35,6 +35,7 @@ unit-test core-tests :
core/write_streambuf.cpp core/write_streambuf.cpp
core/detail/base64.cpp core/detail/base64.cpp
core/detail/empty_base_optimization.cpp core/detail/empty_base_optimization.cpp
core/detail/get_lowest_layer.cpp
core/detail/sha1.cpp core/detail/sha1.cpp
; ;
@@ -43,6 +44,7 @@ unit-test http-tests :
http/basic_headers.cpp http/basic_headers.cpp
http/basic_parser_v1.cpp http/basic_parser_v1.cpp
http/body_type.cpp http/body_type.cpp
http/concepts.cpp
http/empty_body.cpp http/empty_body.cpp
http/headers.cpp http/headers.cpp
http/message.cpp http/message.cpp
@@ -57,7 +59,6 @@ unit-test http-tests :
http/status.cpp http/status.cpp
http/streambuf_body.cpp http/streambuf_body.cpp
http/string_body.cpp http/string_body.cpp
http/type_check.cpp
http/write.cpp http/write.cpp
http/detail/chunk_encode.cpp http/detail/chunk_encode.cpp
; ;
@@ -77,6 +78,7 @@ unit-test websocket-tests :
websocket/teardown.cpp websocket/teardown.cpp
websocket/detail/frame.cpp websocket/detail/frame.cpp
websocket/detail/mask.cpp websocket/detail/mask.cpp
websocket/detail/stream_base.cpp
websocket/detail/utf8_checker.cpp websocket/detail/utf8_checker.cpp
; ;

View File

@@ -29,6 +29,7 @@ add_executable (core-tests
write_streambuf.cpp write_streambuf.cpp
detail/base64.cpp detail/base64.cpp
detail/empty_base_optimization.cpp detail/empty_base_optimization.cpp
detail/get_lowest_layer.cpp
detail/sha1.cpp detail/sha1.cpp
) )

View File

@@ -66,6 +66,14 @@ public:
expect(buffer_size(buffer_cat( expect(buffer_size(buffer_cat(
sb1.data(), sb2.data())) == 12); sb1.data(), sb2.data())) == 12);
} }
for(auto it = bs.begin(); it != bs.end(); ++it)
{
decltype(bs)::const_iterator copy;
copy = it;
expect(copy == it);
copy = copy;
expect(copy == it);
}
} }
void testIterators() void testIterators()

View File

@@ -0,0 +1,88 @@
//
// 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/core/detail/get_lowest_layer.hpp>
#include <beast/unit_test/suite.hpp>
#include <type_traits>
namespace beast {
namespace detail {
class get_lowest_layer_test
: public beast::unit_test::suite
{
public:
struct F1
{
};
struct F2
{
};
template<class F>
struct F3
{
using next_layer_type =
typename std::remove_reference<F>::type;
using lowest_layer_type = typename
get_lowest_layer<next_layer_type>::type;
};
template<class F>
struct F4
{
using next_layer_type =
typename std::remove_reference<F>::type;
using lowest_layer_type = typename
get_lowest_layer<next_layer_type>::type;
};
void
run()
{
static_assert(! has_lowest_layer<F1>::value, "");
static_assert(! has_lowest_layer<F2>::value, "");
static_assert(has_lowest_layer<F3<F1>>::value, "");
static_assert(has_lowest_layer<F4<F3<F2>>>::value, "");
static_assert(std::is_same<
get_lowest_layer<F1>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F2>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F3<F1>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F3<F2>>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F1>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F2>>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F3<F1>>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F3<F2>>>::type, F2>::value, "");
pass();
}
};
BEAST_DEFINE_TESTSUITE(get_lowest_layer,core,beast);
} // detail
} // beast

View File

@@ -89,6 +89,7 @@ public:
void testIterator() void testIterator()
{ {
using boost::asio::buffer_size;
using boost::asio::const_buffer; using boost::asio::const_buffer;
char b[3]; char b[3];
std::array<const_buffer, 3> bs{{ std::array<const_buffer, 3> bs{{
@@ -98,7 +99,12 @@ public:
auto pb = prepare_buffers(2, bs); auto pb = prepare_buffers(2, bs);
std::size_t n = 0; std::size_t n = 0;
for(auto it = pb.end(); it != pb.begin(); --it) for(auto it = pb.end(); it != pb.begin(); --it)
{
decltype(pb)::const_iterator it2(std::move(it));
expect(buffer_size(*it2) == 1);
it = std::move(it2);
++n; ++n;
}
expect(n == 2); expect(n == 2);
} }

View File

@@ -10,6 +10,7 @@ add_executable (http-tests
basic_headers.cpp basic_headers.cpp
basic_parser_v1.cpp basic_parser_v1.cpp
body_type.cpp body_type.cpp
concepts.cpp
empty_body.cpp empty_body.cpp
headers.cpp headers.cpp
message.cpp message.cpp
@@ -24,7 +25,6 @@ add_executable (http-tests
status.cpp status.cpp
streambuf_body.cpp streambuf_body.cpp
string_body.cpp string_body.cpp
type_check.cpp
write.cpp write.cpp
detail/chunk_encode.cpp detail/chunk_encode.cpp
) )

View File

@@ -532,6 +532,7 @@ public:
void testInvalidMatrix() void testInvalidMatrix()
{ {
using boost::asio::buffer; using boost::asio::buffer;
using boost::asio::buffer_copy;
std::string s; std::string s;
for(std::size_t n = 0;; ++n) for(std::size_t n = 0;; ++n)
@@ -549,15 +550,24 @@ public:
s[n] = 0; s[n] = 0;
for(std::size_t m = 1; m < len - 1; ++m) for(std::size_t m = 1; m < len - 1; ++m)
{ {
// Use separately allocated buffers so
// address sanitizer has something to chew on.
//
std::unique_ptr<char[]> p1(new char[m]);
std::unique_ptr<char[]> p2(new char[len - m]);
auto const b1 = buffer(p1.get(), m);
auto const b2 = buffer(p2.get(), len - m);
buffer_copy(b1, buffer(s.data(), m));
buffer_copy(b2, buffer(s.data() + m, len - m));
null_parser<true> p; null_parser<true> p;
error_code ec; error_code ec;
p.write(buffer(s.data(), m), ec); p.write(b1, ec);
if(ec) if(ec)
{ {
pass(); pass();
continue; continue;
} }
p.write(buffer(s.data() + m, len - m), ec); p.write(b2, ec);
expect(ec); expect(ec);
} }
} }

View File

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

View File

@@ -59,6 +59,7 @@ public:
} }
catch(std::exception const&) catch(std::exception const&)
{ {
pass();
} }
m.headers.erase("Content-Length"); m.headers.erase("Content-Length");
m.headers.insert("Connection", "keep-alive"); m.headers.insert("Connection", "keep-alive");
@@ -69,7 +70,12 @@ public:
} }
catch(std::exception const&) catch(std::exception const&)
{ {
pass();
} }
m.version = 11;
m.headers.erase("Connection");
m.headers.insert("Connection", "close");
expect(! is_keep_alive(m));
} }
void run() override void run() override

View File

@@ -85,7 +85,7 @@ public:
{ {
using namespace std::chrono; using namespace std::chrono;
using clock_type = std::chrono::high_resolution_clock; using clock_type = std::chrono::high_resolution_clock;
log << name; log << name << std::endl;
for(std::size_t trial = 1; trial <= repeat; ++trial) for(std::size_t trial = 1; trial <= repeat; ++trial)
{ {
auto const t0 = clock_type::now(); auto const t0 = clock_type::now();
@@ -93,7 +93,7 @@ public:
auto const elapsed = clock_type::now() - t0; auto const elapsed = clock_type::now() - t0;
log << log <<
"Trial " << trial << ": " << "Trial " << trial << ": " <<
duration_cast<milliseconds>(elapsed).count() << " ms"; duration_cast<milliseconds>(elapsed).count() << " ms" << std::endl;
} }
} }
@@ -109,10 +109,10 @@ public:
static std::size_t constexpr Repeat = 50; static std::size_t constexpr Repeat = 50;
log << "sizeof(request parser) == " << log << "sizeof(request parser) == " <<
sizeof(basic_parser_v1<true, null_parser<true>>); sizeof(basic_parser_v1<true, null_parser<true>>) << '\n';
log << "sizeof(response parser) == " << log << "sizeof(response parser) == " <<
sizeof(basic_parser_v1<false, null_parser<true>>); sizeof(basic_parser_v1<false, null_parser<true>>)<< '\n';
testcase << "Parser speed test, " << testcase << "Parser speed test, " <<
((Repeat * size_ + 512) / 1024) << "KB in " << ((Repeat * size_ + 512) / 1024) << "KB in " <<

View File

@@ -12,8 +12,10 @@
#include <beast/http/headers.hpp> #include <beast/http/headers.hpp>
#include <beast/http/parser_v1.hpp> #include <beast/http/parser_v1.hpp>
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include <beast/http/write.hpp>
#include <beast/test/string_stream.hpp> #include <beast/test/string_stream.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
#include <boost/lexical_cast.hpp>
namespace beast { namespace beast {
namespace http { namespace http {
@@ -25,16 +27,18 @@ class streambuf_body_test : public beast::unit_test::suite
public: public:
void run() override void run() override
{ {
test::string_stream ss(ios_, std::string const s =
"HTTP/1.1 200 OK\r\n" "HTTP/1.1 200 OK\r\n"
"Server: test\r\n" "Server: test\r\n"
"Content-Length: 3\r\n" "Content-Length: 3\r\n"
"\r\n" "\r\n"
"xyz"); "xyz";
test::string_stream ss(ios_, s);
parser_v1<false, streambuf_body, headers> p; parser_v1<false, streambuf_body, headers> p;
streambuf sb; streambuf sb;
parse(ss, sb, p); parse(ss, sb, p);
expect(to_string(p.get().body.data()) == "xyz"); expect(to_string(p.get().body.data()) == "xyz");
expect(boost::lexical_cast<std::string>(p.get()) == s);
} }
}; };

View File

@@ -16,6 +16,7 @@ add_executable (websocket-tests
teardown.cpp teardown.cpp
detail/frame.cpp detail/frame.cpp
detail/mask.cpp detail/mask.cpp
detail/stream_base.cpp
detail/utf8_checker.cpp detail/utf8_checker.cpp
) )

View File

@@ -225,7 +225,6 @@ public:
testCloseCodes(); testCloseCodes();
testFrameHeader(); testFrameHeader();
testBadFrameHeaders(); testBadFrameHeaders();
pass();
} }
}; };

View File

@@ -5,7 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // 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/websocket/detail/mask.hpp> #include <beast/websocket/detail/mask.hpp>
#include <beast/unit_test/suite.hpp> #include <beast/unit_test/suite.hpp>
namespace beast { namespace beast {

View File

@@ -0,0 +1,40 @@
//
// 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/websocket/detail/stream_base.hpp>
#include <beast/unit_test/suite.hpp>
#include <initializer_list>
#include <climits>
namespace beast {
namespace websocket {
namespace detail {
class stream_base_test : public beast::unit_test::suite
{
public:
void testClamp()
{
expect(detail::clamp(
std::numeric_limits<std::uint64_t>::max()) ==
std::numeric_limits<std::size_t>::max());
}
void run() override
{
testClamp();
}
};
BEAST_DEFINE_TESTSUITE(stream_base,websocket,beast);
} // detail
} // websocket
} // beast

File diff suppressed because it is too large Load Diff

View File

@@ -91,14 +91,16 @@ private:
bool log; bool log;
int state = 0; int state = 0;
boost::optional<endpoint_type> ep; boost::optional<endpoint_type> ep;
websocket::stream<socket_type> ws; stream<socket_type> ws;
websocket::opcode op; boost::asio::io_service::strand strand;
opcode op;
beast::streambuf sb; beast::streambuf sb;
int id; int id;
data(bool log_, socket_type&& sock_) data(bool log_, socket_type&& sock_)
: log(log_) : log(log_)
, ws(std::move(sock_)) , ws(std::move(sock_))
, strand(ws.get_io_service())
, id([] , id([]
{ {
static int n = 0; static int n = 0;
@@ -112,6 +114,7 @@ private:
: log(log_) : log(log_)
, ep(ep_) , ep(ep_)
, ws(std::move(sock_)) , ws(std::move(sock_))
, strand(ws.get_io_service())
, id([] , id([]
{ {
static int n = 0; static int n = 0;
@@ -174,8 +177,34 @@ private:
} }
} }
template<class Streambuf, std::size_t N>
static
bool
match(Streambuf& sb, char const(&s)[N])
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
if(sb.size() < N-1)
return false;
static_string<N-1> t;
t.resize(N-1);
buffer_copy(buffer(t.data(), t.size()),
sb.data());
if(t != s)
return false;
sb.consume(N-1);
return true;
}
void operator()(error_code ec, std::size_t)
{
(*this)(ec);
}
void operator()(error_code ec) void operator()(error_code ec)
{ {
using boost::asio::buffer;
using boost::asio::buffer_copy;
auto& d = *d_; auto& d = *d_;
switch(d.state) switch(d.state)
{ {
@@ -191,19 +220,54 @@ private:
d.sb.consume(d.sb.size()); d.sb.consume(d.sb.size());
// read message // read message
d.state = 2; d.state = 2;
d.ws.async_read(d.op, d.sb, std::move(*this)); d.ws.async_read(d.op, d.sb,
d.strand.wrap(std::move(*this)));
return; return;
// got message // got message
case 2: case 2:
if(ec == websocket::error::closed) if(ec == error::closed)
return; return;
if(ec) if(ec)
return fail(ec, "async_read"); return fail(ec, "async_read");
if(match(d.sb, "RAW"))
{
d.state = 1;
boost::asio::async_write(d.ws.next_layer(),
d.sb.data(), d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "TEXT"))
{
d.state = 1;
d.ws.set_option(message_type{opcode::text});
d.ws.async_write(
d.sb.data(), d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "PING"))
{
ping_data payload;
d.sb.consume(buffer_copy(
buffer(payload.data(), payload.size()),
d.sb.data()));
d.state = 1;
d.ws.async_ping(payload,
d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "CLOSE"))
{
d.state = 1;
d.ws.async_close({},
d.strand.wrap(std::move(*this)));
return;
}
// write message // write message
d.state = 1; d.state = 1;
d.ws.set_option(websocket::message_type(d.op)); d.ws.set_option(message_type(d.op));
d.ws.async_write(d.sb.data(), std::move(*this)); d.ws.async_write(d.sb.data(),
d.strand.wrap(std::move(*this)));
return; return;
// connected // connected
@@ -214,7 +278,7 @@ private:
d.ws.async_handshake( d.ws.async_handshake(
d.ep->address().to_string() + ":" + d.ep->address().to_string() + ":" +
std::to_string(d.ep->port()), std::to_string(d.ep->port()),
"/", std::move(*this)); "/", d.strand.wrap(std::move(*this)));
return; return;
} }
} }
@@ -226,7 +290,7 @@ private:
auto& d = *d_; auto& d = *d_;
if(d.log) if(d.log)
{ {
if(ec != websocket::error::closed) if(ec != error::closed)
std::cerr << "#" << d_->id << " " << std::cerr << "#" << d_->id << " " <<
what << ": " << ec.message() << std::endl; what << ": " << ec.message() << std::endl;
} }
@@ -256,6 +320,8 @@ private:
{ {
if(! acceptor_.is_open()) if(! acceptor_.is_open())
return; return;
if(ec == boost::asio::error::operation_aborted)
return;
maybe_throw(ec, "accept"); maybe_throw(ec, "accept");
socket_type sock(std::move(sock_)); socket_type sock(std::move(sock_));
acceptor_.async_accept(sock_, acceptor_.async_accept(sock_,

View File

@@ -101,15 +101,17 @@ private:
{ {
int id; int id;
sync_echo_peer& self; sync_echo_peer& self;
socket_type sock;
boost::asio::io_service::work work; boost::asio::io_service::work work;
// Must be destroyed before work otherwise the
// io_service could be destroyed before the socket.
socket_type sock;
lambda(int id_, sync_echo_peer& self_, lambda(int id_, sync_echo_peer& self_,
socket_type&& sock_) socket_type&& sock_)
: id(id_) : id(id_)
, self(self_) , self(self_)
, work(sock_.get_io_service())
, sock(std::move(sock_)) , sock(std::move(sock_))
, work(sock.get_io_service())
{ {
} }
@@ -149,10 +151,31 @@ private:
} }
}; };
template<class Streambuf, std::size_t N>
static
bool
match(Streambuf& sb, char const(&s)[N])
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
if(sb.size() < N-1)
return false;
static_string<N-1> t;
t.resize(N-1);
buffer_copy(buffer(t.data(), t.size()),
sb.data());
if(t != s)
return false;
sb.consume(N-1);
return true;
}
void void
do_peer(int id, socket_type&& sock) do_peer(int id, socket_type&& sock)
{ {
websocket::stream<socket_type> ws(std::move(sock)); using boost::asio::buffer;
using boost::asio::buffer_copy;
stream<socket_type> ws(std::move(sock));
ws.set_option(decorate(identity{})); ws.set_option(decorate(identity{}));
ws.set_option(read_message_max(64 * 1024 * 1024)); ws.set_option(read_message_max(64 * 1024 * 1024));
error_code ec; error_code ec;
@@ -164,17 +187,45 @@ private:
} }
for(;;) for(;;)
{ {
websocket::opcode op; opcode op;
beast::streambuf sb; beast::streambuf sb;
ws.read(op, sb, ec); ws.read(op, sb, ec);
if(ec) if(ec)
{
auto const s = ec.message();
break; break;
ws.set_option(websocket::message_type(op)); }
ws.write(sb.data(), ec); ws.set_option(message_type(op));
if(match(sb, "RAW"))
{
boost::asio::write(
ws.next_layer(), sb.data(), ec);
}
else if(match(sb, "TEXT"))
{
ws.set_option(message_type{opcode::text});
ws.write(sb.data(), ec);
}
else if(match(sb, "PING"))
{
ping_data payload;
sb.consume(buffer_copy(
buffer(payload.data(), payload.size()),
sb.data()));
ws.ping(payload, ec);
}
else if(match(sb, "CLOSE"))
{
ws.close({}, ec);
}
else
{
ws.write(sb.data(), ec);
}
if(ec) if(ec)
break; break;
} }
if(ec && ec != websocket::error::closed) if(ec && ec != error::closed)
{ {
fail(id, ec, "read"); fail(id, ec, "read");
} }