112 lines
4.8 KiB
Plaintext
112 lines
4.8 KiB
Plaintext
[/
|
|
Copyright (c) 2016-2019 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)
|
|
|
|
Official repository: https://github.com/boostorg/beast
|
|
]
|
|
|
|
[section:detect_ssl Detect SSL __example__]
|
|
|
|
In this example we will build a simple function to detect the presence of the
|
|
[@https://tools.ietf.org/html/rfc2246#section-7.4 TLS client handshake]
|
|
given an input buffer sequence. Then we build on the example by adding a
|
|
synchronous stream algorithm. Finally, we implement an asynchronous
|
|
detection function using a composed operation. This SSL detector may
|
|
be used to allow a server to accept both TLS and plain (unencrypted)
|
|
connections at the same port.
|
|
|
|
Here is the declaration for a function template to detect the SSL client
|
|
handshake. The function accepts any object whose type meets the requirements
|
|
of __ConstBufferSequence__. This gives callers flexibility to use a buffer
|
|
object whose behavior is appropriate to the task.
|
|
|
|
[example_core_detect_ssl_1]
|
|
|
|
The algorithm examines the buffer starting from the beginning, and
|
|
performs a series of qualifying checks against the TLS specification.
|
|
When not enough data exists to be certain, the returned value of
|
|
`boost::indeterminate` informs the caller to read more data into the buffer.
|
|
The function definition for the declaration above follows:
|
|
|
|
[example_core_detect_ssl_2]
|
|
|
|
The detection function above is suitably generic and targeted in
|
|
focus that it may be used as a building block to create higher level
|
|
abstractions. Our goal is to create a ['stream algorithm]: a function
|
|
which is invoked with a stream, that reads or writes (or both) to achieve
|
|
a purpose. In this case, to detect the TLS client handshake. Stream
|
|
algorithms may be synchronous or asynchronous. Because synchronous algorithms
|
|
are easier to write, we start there. Then we build the asynchronous version,
|
|
trying to model it similarly to make reasoning about it easier.
|
|
|
|
The synchronous version is implemented thusly:
|
|
|
|
[example_core_detect_ssl_3]
|
|
|
|
Now that we have the synchronous version, we can attempt to model the
|
|
asynchronous version similarly. A function which launches an asynchronous
|
|
operation is called an ['initiating function]. While the synchronous
|
|
version above produces an error code through an output parameter, the
|
|
asynchronous version delivers the error code to a completion handler
|
|
or other custom mechanism defined by the completion token. The signature
|
|
of the initiating function reflects these differences.
|
|
|
|
First we declare the initiating function and document the requirements,
|
|
parameters, preconditions, and effects:
|
|
|
|
[example_core_detect_ssl_4]
|
|
|
|
There are two additional components required to implement the initiating
|
|
function:
|
|
|
|
* An intermediate completion handler, called the "composed operation"
|
|
object, which holds the state of the operation while it is in progress,
|
|
and also holds the user's completion handler to be invoked when the
|
|
opeartion completes, and
|
|
|
|
* An "initiation" function object which when invoked with parameters
|
|
captured at the call site of the initiating function, constructs the
|
|
composed operation with the captured arguments and launches it.
|
|
|
|
Here we forward declare the composed operation type, and provide the
|
|
definition of the initiation function object. They are placed in the
|
|
`detail` namespace since they should not be public:
|
|
|
|
[example_core_detect_ssl_5]
|
|
|
|
The initiating function definition itself is straightforward. We perform
|
|
type checking on the parameters, and then let `net::async_initiate`
|
|
capture the parameter list along with a copy of our initiation function
|
|
object. Depending on the specialization of `async_result` for the type
|
|
of `CompletionToken`, the initiation function may be invoked immediately.
|
|
Alternatively, it may be invoked later, after the initiating function
|
|
returns. This is known as "lazy execution," and allows efficient and
|
|
expressive abstractions to be written.
|
|
|
|
[example_core_detect_ssl_6]
|
|
|
|
Now we will declare our composed operation. There is a considerable
|
|
amount of necessary boilerplate to get this right, but the result
|
|
is worth the effort.
|
|
|
|
[example_core_detect_ssl_7]
|
|
|
|
The boilerplate is all done, and now we need to implement the function
|
|
call operator that turns this composed operation a completion handler
|
|
with the signature `void(error_code, std::size_t)` which is exactly
|
|
the signature needed when performing asynchronous reads. This function
|
|
is a transformation of the synchronous version of `detect_ssl` above,
|
|
but with the inversion of flow that characterizes code written in the
|
|
callback style:
|
|
|
|
[example_core_detect_ssl_8]
|
|
|
|
The examples
|
|
[path_link example/advanced/server/advanced_server.cpp advanced-server] and
|
|
[path_link example/advanced/server-flex/advanced_server_flex.cpp advanced-server-flex]
|
|
use this SSL detection function.
|
|
|
|
[endsect]
|