Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
2c9adcaf
Commit
2c9adcaf
authored
Mar 31, 2023
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add documentation for the high-level caf-net APIs
parent
c6c05de4
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
589 additions
and
107 deletions
+589
-107
.gitignore
.gitignore
+1
-0
CHANGELOG.md
CHANGELOG.md
+7
-0
examples/http/time-server.cpp
examples/http/time-server.cpp
+2
-0
examples/web_socket/echo.cpp
examples/web_socket/echo.cpp
+5
-3
examples/web_socket/hello-client.cpp
examples/web_socket/hello-client.cpp
+5
-3
examples/web_socket/quote-server.cpp
examples/web_socket/quote-server.cpp
+2
-0
libcaf_net/caf/net/web_socket/default_trait.hpp
libcaf_net/caf/net/web_socket/default_trait.hpp
+14
-0
manual/Brokers.rst
manual/Brokers.rst
+2
-2
manual/conf.py
manual/conf.py
+2
-1
manual/index.rst
manual/index.rst
+2
-0
manual/net/ActorShell.rst
manual/net/ActorShell.rst
+18
-2
manual/net/HTTP.rst
manual/net/HTTP.rst
+107
-2
manual/net/LengthPrefixFraming.rst
manual/net/LengthPrefixFraming.rst
+39
-49
manual/net/Overview.rst
manual/net/Overview.rst
+21
-14
manual/net/Prometheus.rst
manual/net/Prometheus.rst
+17
-31
manual/net/SSL.rst
manual/net/SSL.rst
+77
-0
manual/net/WebSocket.rst
manual/net/WebSocket.rst
+193
-0
manual/net/vars.rst
manual/net/vars.rst
+75
-0
No files found.
.gitignore
View file @
2c9adcaf
...
...
@@ -10,5 +10,6 @@ manual/examples
manual/html/*
manual/libcaf_core
manual/libcaf_io
manual/libcaf_net
manual/libcaf_openssl
release.txt
CHANGELOG.md
View file @
2c9adcaf
...
...
@@ -27,6 +27,13 @@ is based on [Keep a Changelog](https://keepachangelog.com).
-
The class
`expected`
now implements the monadic member functions from C++23
`std::expected`
as well as
`value_or`
.
### Changed
-
After collecting experience and feedback on the new HTTP and WebSocket APIs
introduced with 0.19.0-rc.1, we decided to completely overhaul the
user-facing, high-level APIs. Please consult the manual for the new DSL to
start servers.
### Fixed
-
The SPSC buffer now makes sure that subscribers get informed of a producer has
...
...
examples/http/time-server.cpp
View file @
2c9adcaf
...
...
@@ -34,6 +34,7 @@ struct config : caf::actor_system_config {
// -- main ---------------------------------------------------------------------
// --(rst-main-begin)--
int
caf_main
(
caf
::
actor_system
&
sys
,
const
config
&
cfg
)
{
namespace
http
=
caf
::
net
::
http
;
namespace
ssl
=
caf
::
net
::
ssl
;
...
...
@@ -81,5 +82,6 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
std
::
cout
<<
"Terminating.
\n
"
;
return
EXIT_SUCCESS
;
}
// --(rst-main-end)--
CAF_MAIN
(
caf
::
net
::
middleman
)
examples/web_socket/echo.cpp
View file @
2c9adcaf
...
...
@@ -32,6 +32,7 @@ struct config : caf::actor_system_config {
// -- main ---------------------------------------------------------------------
// --(rst-main-begin)--
int
caf_main
(
caf
::
actor_system
&
sys
,
const
config
&
cfg
)
{
namespace
http
=
caf
::
net
::
http
;
namespace
ssl
=
caf
::
net
::
ssl
;
...
...
@@ -62,8 +63,8 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
.
max_connections
(
max_connections
)
// Accept only requests for path "/".
.
on_request
([](
ws
::
acceptor
<>&
acc
)
{
// The h
dr parameter is a dictionary with fields from the WebSocket
//
handshake such as the path
.
// The h
eader parameter contains fields from the WebSocket handshake
//
such as the path and HTTP header fields.
.
auto
path
=
acc
.
header
().
path
();
std
::
cout
<<
"*** new client request for path "
<<
path
<<
'\n'
;
// Accept the WebSocket connection only if the path is "/".
...
...
@@ -73,7 +74,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
acc
.
accept
();
}
else
{
// Calling `reject` aborts the connection with HTTP status code 400
// (Bad Request). The error gets converted to a string and sen
d
to
// (Bad Request). The error gets converted to a string and sen
t
to
// the client to give some indication to the client why the request
// was rejected.
auto
err
=
caf
::
make_error
(
caf
::
sec
::
invalid_argument
,
...
...
@@ -117,5 +118,6 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
// workers are still alive.
return
EXIT_SUCCESS
;
}
// --(rst-main-end)--
CAF_MAIN
(
caf
::
net
::
middleman
)
examples/web_socket/hello-client.cpp
View file @
2c9adcaf
...
...
@@ -22,6 +22,7 @@ struct config : caf::actor_system_config {
}
};
// --(rst-main-begin)--
int
caf_main
(
caf
::
actor_system
&
sys
,
const
config
&
cfg
)
{
namespace
ws
=
caf
::
net
::
web_socket
;
// Sanity checking.
...
...
@@ -30,7 +31,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
std
::
cerr
<<
"*** mandatory argument server missing or invalid
\n
"
;
return
EXIT_FAILURE
;
}
// Ask user for the hello message.
// Ask
the
user for the hello message.
std
::
string
hello
;
std
::
cout
<<
"Please enter a hello message for the server: "
<<
std
::
flush
;
std
::
getline
(
std
::
cin
,
hello
);
...
...
@@ -42,7 +43,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
// If we don't succeed at first, try up to 10 times with 1s delay.
.
retry_delay
(
1s
)
.
max_retry_count
(
9
)
//
After connecting, spin up a worker to manage it
.
//
On success, spin up a worker to manage the connection
.
.
start
([
&
sys
,
hello
](
auto
pull
,
auto
push
)
{
sys
.
spawn
([
hello
,
pull
,
push
](
caf
::
event_based_actor
*
self
)
{
// Open the pull handle.
...
...
@@ -54,7 +55,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
<<
to_string
(
what
)
<<
'\n'
;
})
// Restrict how many messages we receive if the user configured
// it.
//
a lim
it.
.
compose
([
self
](
auto
in
)
{
if
(
auto
limit
=
caf
::
get_as
<
size_t
>
(
self
->
config
(),
"max"
))
{
return
std
::
move
(
in
).
take
(
*
limit
).
as_observable
();
...
...
@@ -93,5 +94,6 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
// workers are still alive.
return
EXIT_SUCCESS
;
}
// --(rst-main-end)--
CAF_MAIN
(
caf
::
net
::
middleman
)
examples/web_socket/quote-server.cpp
View file @
2c9adcaf
...
...
@@ -161,6 +161,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
res
.
respond
(
http
::
status
::
ok
,
"text/plain"
,
f
(
quotes
));
}
})
// --(rst-switch_protocol-begin)--
// On "/ws/quotes/<arg>", we switch the protocol to WebSocket.
.
route
(
"/ws/quotes/<arg>"
,
http
::
method
::
get
,
ws
::
switch_protocol
()
...
...
@@ -202,6 +203,7 @@ int caf_main(caf::actor_system& sys, const config& cfg) {
});
});
}))
// --(rst-switch_protocol-end)--
// Run with the configured routes.
.
start
();
// Report any error to the user.
...
...
libcaf_net/caf/net/web_socket/default_trait.hpp
View file @
2c9adcaf
...
...
@@ -15,6 +15,9 @@
namespace
caf
::
net
::
web_socket
{
// --(rst-class-begin)--
/// Configures a WebSocket server or client to operate on the granularity of
/// regular WebSocket frames.
class
CAF_NET_EXPORT
default_trait
{
public:
/// The input type of the application, i.e., what that flows from the
...
...
@@ -39,17 +42,28 @@ public:
template
<
class
...
Ts
>
using
acceptor_resource
=
async
::
consumer_resource
<
accept_event
<
Ts
...
>>
;
/// Queries whether `x` should be serialized as binary frame (`true`) or text
/// frame (`false`).
bool
converts_to_binary
(
const
output_type
&
x
);
/// Serializes an output to raw bytes for sending a binary frame
/// (`converts_to_binary` returned `true`).
bool
convert
(
const
output_type
&
x
,
byte_buffer
&
bytes
);
/// Serializes an output to ASCII (or UTF-8) characters for sending a text
/// frame (`converts_to_binary` returned `false`).
bool
convert
(
const
output_type
&
x
,
std
::
vector
<
char
>&
text
);
/// Converts the raw bytes from a binary frame to the input type.
bool
convert
(
const_byte_span
bytes
,
input_type
&
x
);
/// Converts the characters from a text frame to the input type.
bool
convert
(
std
::
string_view
text
,
input_type
&
x
);
/// Returns a description of the error if any of the `convert` functions
/// returned `false`.
error
last_error
();
};
// --(rst-class-end)--
}
// namespace caf::net::web_socket
manual/Brokers.rst
View file @
2c9adcaf
...
...
@@ -6,10 +6,10 @@ Network I/O with Brokers
.. note::
We no longer recommend the Brokers from the I/O module for new code. Please
consider using Networking Module instead: :ref:`net
_
overview`. The new caf-net
consider using Networking Module instead: :ref:`net
-
overview`. The new caf-net
module offers higher-level abstractions that interface with flows (see
:ref:`data_flows`). Message-based communication is also available with an
:ref:`
actor_
shell`.
:ref:`
net-actor-
shell`.
When communicating to other services in the network, sometimes low-level socket
I/O is inevitable. For this reason, CAF provides *brokers*. A broker is
...
...
manual/conf.py
View file @
2c9adcaf
...
...
@@ -60,7 +60,8 @@ else:
# -- Enable Sphinx to find the literal includes -------------------------------
for
dirname
in
[
"examples"
,
"libcaf_core"
,
"libcaf_io"
,
"libcaf_openssl"
]:
for
dirname
in
[
"examples"
,
"libcaf_core"
,
"libcaf_io"
,
"libcaf_openssl"
,
"libcaf_net"
]:
dest_dir
=
os
.
path
.
join
(
conf_dir
,
dirname
)
if
not
os
.
path
.
isdir
(
dest_dir
):
os
.
symlink
(
os
.
path
.
join
(
root_dir
,
dirname
),
dest_dir
)
...
...
manual/index.rst
View file @
2c9adcaf
...
...
@@ -39,10 +39,12 @@ Contents
:caption: Networking Library
net/Overview
net/SSL
net/ActorShell
net/HTTP
net/LengthPrefixFraming
net/Prometheus
net/WebSocket
.. toctree::
:maxdepth: 2
...
...
manual/net/ActorShell.rst
View file @
2c9adcaf
.. _actor_shell:
.. include:: vars.rst
.. _net-actor-shell:
Actor Shell
===========
TODO
Actor shells allow socket managers to interface with regular actors. Much like
regular actors, actor shells come in two flavors: with dynamic typing
(``actor_shell``) or with static typing (``typed_actor_shell``).
Actor shells can be embedded into a protocol instance to turn messages on the
network to actor messages and vice versa. The primary use case in CAF at the
moment is to allow servers to send a request message to an actor and then use
the response message to generate an output on the network. Please see
``examples/http/rest.cpp`` as a reference for this use case.
Unlike a "regular" actor, an actor shell has no own control loop. Users can
define a behavior with ``set_behavior``, but are responsible for embedding the
shell into some sort of control loop.
|see-doxygen|
manual/net/HTTP.rst
View file @
2c9adcaf
.. _net_http:
.. include:: vars.rst
.. _net-http:
HTTP :sup:`experimental`
========================
TODO
Servers
-------
.. note::
For this API, include ``caf/net/http/with.hpp``.
HTTP is an essential protocol for the world wide web as well as for micro
services. In CAF, starting an HTTP server with the declarative API uses the
entry point ``caf::net::http::with`` that takes an ``actor_system`` as argument.
On the result factory object, we can optionally call ``context`` to set an SSL
context for using HTTPS instead of plain HTTP connections. Once we call
``accept``, we enter the second phase of the setup. The ``accept`` function has
multiple overloads:
- |accept-port|
- |accept-socket|
- |accept-ssl-conn|
After calling ``accept``, we enter the second step of configuring the server.
Here, we can (optionally) fine-tune the server with these member functions:
- |do-on-error|
- |max-conn|
- |reuse-addr|
- |monitor|
At this step, we may also defines *routes* on the HTTP server. A route binds a
callback to an HTTP path on the server. On each HTTP request, the server
iterates over all routes and selects the first matching route to process the
request.
When defining a route, we pass an absolute path on the server, optionally the
HTTP method for the route and the handler. In the path, we can use ``<arg>``
placeholders. Each argument defined in this way maps to an argument of the
callback. The callback always must take ``http::responder&`` as the first
argument, followed by one argument for each ``<arg>`` placeholder in the path.
For example, the following route would forward any HTTP request on
``/user/<arg>`` with the HTTP method GET to the custom handler:
.. code-block:: C++
.route("/user/<arg>", http::method::get, [](http::responder& res, int id) {
// ...
})
CAF evaluates the signature of the callback to automatically deduce the argument
types. On a mismatch, for example if a user accesses ``/user/foo``, the
conversion to ``int`` would fail and CAF would refuse the request with an error.
The ``responder`` encapsulates the state for responding to an HTTP request (see
`Responders`_) and allows our handler to send a response message either
immediately or at some later time.
To start an HTTP server, we have two overloads for ``start`` available.
The first ``start`` overload takes no arguments. Use this overload after
configuring at least one ``route`` to start an HTTP server that only dispatches
to its predefined routes, as shown in the example below. Calling this overload
without defining at least one route prior is an error.
.. literalinclude:: /examples/http/time-server.cpp
:language: C++
:start-after: --(rst-main-begin)--
:end-before: --(rst-main-end)--
.. note::
For details on ``ssl::context::enable``, please see :ref:`net-ssl`.
The second ``start`` overload takes one argument: a function object that takes
an asynchronous resource for processing ``http::request`` objects. This class
also allows to respond to HTTP requests, but is always asynchronous. Internally,
the class ``http::request`` is connected to the HTTP server with a future.
|after-start|
Responders
----------
The responder holds a pointer back to the HTTP server as well as pointers to the
HTTP headers and the payload.
The most commonly used member functions are as follows:
- ``const request_header& header() const noexcept`` to access the HTTP header
fields.
- ``const_byte_span payload() const noexcept`` to access the bytes of the HTTP
body (payload).
- ``actor_shell* self()`` for allowing the responder to interface with regular
actors. See :ref:`net-actor-shell`.
- ``void respond(...)`` (multiple overloads) for sending a response message to
the client.
The class also supports conversion to an asynchronous ``http::request`` via
``to_request`` or to create a promise on the server via ``to_promise``. The
promise is bound to the server and may not be used in another thread. Usually,
the promises are used in conjunction with ``self()->request(...)`` to generate
an HTTP response from an actor's response. Please look at the example under
``examples/http/rest.cpp`` as a reference.
|see-doxygen|
manual/net/LengthPrefixFraming.rst
View file @
2c9adcaf
.. _length_prefix_framing:
.. include:: vars.rst
.. _net-lpf:
Length-prefix Framing :sup:`experimental`
=========================================
Length-prefix framing is a simple protocol for encoding variable-length messages
on the network. Each message is preceded by a fixed-length value (32 bit) that
indicates the length of the message
,
in bytes.
indicates the length of the message in bytes.
When a sender wants to transmit a message, it first encodes the message into a
sequence of bytes. It then calculates the length of the byte sequence and
prefixes the message with the length. The receiver then reads the length prefix
to determine the length of the message before reading the bytes for the message.
Starting a Server
-----------------
The simplest way to start a length-prefix framing server is by using the
high-level factory API.
First, include the necessary header files:
.. note::
.. code-block:: cpp
For the high-level API, include ``caf/net/lp/with.hpp``.
#include <caf/net/lp/with.hpp>
Servers
-------
Then, create a new length-prefix factory using the ``caf::net::lp::with``
function:
The simplest way to start a length-prefix framing server is by using the
high-level factory DSL. The entry point for this API is calling the
``caf::net::lp::with`` function:
.. code-block:: cpp
caf::net::lp::with(sys)
Optionally, you can also provide a custom trait type by calling
``caf::net::lp::with<my_trait>(sys)`` instead.
``caf::net::lp::with<my_trait>(sys)`` instead. The default trait class
``caf::net::lp::default_trait`` configures the transport to exchange
``caf::net::lp::frame`` objects with the application, which essentially wraps
raw bytes.
Once you have set up the factory, you can call the ``accept`` function on it to
start accepting incoming connections. The ``accept`` function
can be called
with
:
start accepting incoming connections. The ``accept`` function
has multiple
overloads
:
- A port number, a bind address (which defaults to the empty string, allowing
connections from any host), and a boolean indicating whether to create the
socket with ``SO_REUSEADDR`` (which defaults to ``true``).
- An ``ssl::context`` object (if you want to use SSL/TLS encryption), a port
number, a bind address, and a boolean indicating whether to create the socket
with ``SO_REUSEADDR`` (with the same defaults as before).
- A ``tcp_accept_socket`` object for accepting incoming connections.
- An ``ssl::acceptor`` object for accepting incoming connections over SSL/TLS.
- |accept-port|
- |accept-socket|
- |accept-ssl-conn|
After setting up the factory and calling ``accept``, you can use the following
functions on the returned object:
-
``max_connections(size_t)`` to limit the maximum number of concurrent
connections on the server.
-
``do_on_error(function<void(const caf::error&)>)`` to set an error callback.
-
|do-on-error|
- |max-conn|
-
|reuse-addr|
- ``start(OnStart)`` to initialize the server and run it in the background. The
``OnStart`` callback takes an argument of type ``trait::acceptor_resource``.
This is a consumer resource for receiving accept events. Each accept event
consists of an input resource and an output resource for reading from and
writing to the new connection.
Starting a Client
-----------------
|after-start|
Clients
-------
Starting a client also uses the ``with`` factory. Calling ``connect`` instead of
``accept`` creates a factory for clients. The ``connect`` function may be called
with:
- A remote address (such as a hostname or IP address) plus a port number.
- An ``url`` object using the ``tcp`` schema, i.e., ``tcp://<host>:<port>``.
- A ``stream_socket`` object for running the length-prefix framing.
- An ``ssl::connection`` object for running the length-prefix framing.
- |connect-host|
- |connect-socket|
- |connect-ssl|
After setting up the factory and calling ``connect``, you can use the following
functions on the returned object:
After calling connect, we can configure various parameters:
- ``do_on_error(function<void(const caf::error&)>)`` to set an error callback.
- ``start(OnStart)`` to initialize the server and run it in the background. The
- ``retry_delay(timespan)`` to set the delay between connection retries when a
connection attempt fails.
- ``connection_timeout(timespan)`` to set the maximum amount of time to wait for
a connection attempt to succeed before giving up.
- ``max_retry_count(size_t)`` to set the maximum number of times to retry a
connection attempt before giving up.
- ``OnStart`` callback takes two arguments: the input resource and the output
resource for reading from and writing to the new connection.
Note that ``retry_delay``, ``connection_timeout(timespan)`` and
``max_retry_count(size_t)`` have no effect when constructing the factory object
from a socket or SSL connection.
- |do-on-error|
- |retry-delay|
- |conn-timeout|
- |max-retry|
Finally, we call ``start`` to launch the client. The function expects an
``OnStart`` callback takes two arguments: the input resource and the output
resource for reading from and writing to the new connection.
manual/net/Overview.rst
View file @
2c9adcaf
.. _net
_
overview:
.. _net
-
overview:
Overview
========
...
...
@@ -54,25 +54,25 @@ Starting a server uses ``accept`` instead:
PROTOCOL::with(CONTEXT).accept(WHERE).start(ON_ACCEPT);
CAF also includes some self-contained services that users may simply start in
the background such as a Prometheus endpoint. These services take no callback in
``start``. For example:
The ``ON_ACCEPT`` handler may be optional and some protocols do not support it
at all. For example, the HTTP server accepts callbacks for individual routes and
users may call ``start`` without arguments to simply launch the server and
dispatch to the configured routes.
.. code-block:: C++
prometheus::with(sys).accept(8008).start();
In all cases, CAF returns ``expected<disposable>``. The ``disposble`` allows
users to cancel the activity at any time. Note: when canceling a server, it only
disposes the accept socket itself. Previously established connections remain
unaffected.
In all cases, CAF returns ``expected<disposable>``. The ``disposable`` allows
users to cancel the background activity at any time. Note: when canceling a
server, this only disposes the server itself. Previously established connections
remain unaffected.
For error reporting, most factories also allow setting a callback with
``do_on_error``. This can be useful for ignoring the result value but still
reporting errors.
Many protocols also accept additional configuration parameters. For example, the
``connect`` API also allows to configure multiple connection attempts.
``connect`` API may also allows to configure multiple connection attempts.
Please refer to the sections for the individual protocols for a more detailed
description of available options.
Protocol Layering
-----------------
...
...
@@ -85,7 +85,7 @@ naturally with any number of other protocols. To enable this key property,
Please note that the protocol layering is meant for adding new networking
capabilities to CAF, but we do not recommend using the protocol stacks directly
in application code. The protocol stacks are meant as building blocks for
higher-level
the declarative, high-level
APIs.
higher-level
, declarative
APIs.
A *protocol layer* is a class that implements a single processing step in a
communication pipeline. Multiple layers are organized in a *protocol stack*.
...
...
@@ -136,3 +136,10 @@ When instantiating a protocol stack, each layer is represented by a concrete
object and we build the pipeline from top to bottom, i.e., we create the highest
layer first and then pass the last layer to the next lower layer until arriving
at the socket manager.
The layering API is generally structured into upper and lower layers. For
example, the upper layer for HTTP consumes the requests while the lower layer
can be used to send responses. Since the layering API is quite low level, we
recommend consulting the Doxygen documentation for the class interfaces and
looking at existing protocols such as the length-prefix framing as basis for
implementing custom protocols. In the manual, we focus on the high-level APIs.
manual/net/Prometheus.rst
View file @
2c9adcaf
.. _net
_
prometheus:
.. _net
-
prometheus:
Prometheus
:sup:`experimental`
==========
====================
Prometheus
==========
CAF ships a Prometheus server implementation that allows a scraper to collect
metrics from the actor system. The Prometheus server sits on top of an HTTP
layer.
CAF ships a Prometheus scraper implementation that allows exposing metrics from
the actor system. User can create an instance of the scraper with the factory
function ``caf::net::prometheus::scraper`` (include ``caf/net/prometheus.hpp``).
The scraper is designed to work with an HTTP server.
Usually, users can simply use the configuration options of the actor system to
export metrics to scrapers: :ref:`metrics_export`. When setting these options,
CAF uses this implementation to start the Prometheus server in the background.
Starting a Prometheus Server
----------------------------
The Prometheus server always binds to the actor system, because it needs the
multiplexer and the metrics registry. Hence, the entry point is always:
A minimal example for starting an HTTP server (see :ref:`net-http`) at port 8081
that responds to GET requests on ``/metrics`` could look like this:
.. code-block:: C++
caf::net::prometheus::with(sys)
From here, users can call ``accept`` with:
- A port, a bind address (defaults to the empty string for *any host*) and
whether to create the socket with ``SO_REUSEADDR`` (defaults to ``true``).
- An ``ssl::context``, a port, a bind address and whether to create the socket
with ``SO_REUSEADDR`` (with the same defaults as before).
- A ``tcp_accept_socket`` for accepting incoming connections.
- An ``ssl::acceptor`` for accepting incoming connections.
auto server = net::http::with(sys)
.accept(8081)
.route("/metrics", net::prometheus::scraper(sys))
.start();
After setting up the factory, users may call these functions on the object
:
.. note:
:
- ``max_connections(size_t)`` for limiting the amount of concurrent connections
on the server.
- ``do_on_error(function<void(const caf::error&)>)`` for setting an error
callback.
- ``start()`` for initializing the server and running it in the background.
Usually, users can simply use the configuration options of the actor system
to export metrics: :ref:`metrics_export`. When setting these options, CAF uses
this implementation to start the Prometheus server in the background.
manual/net/SSL.rst
0 → 100644
View file @
2c9adcaf
.. include:: vars.rst
.. _net-ssl:
SSL
===
Protocols such as HTTP and WebSocket have a *secure* version that requires an
SSL/TLS layer. CAF bundles functions and classes for implementing secure
communication in the namespace ``caf::net::ssl``. It is worth mentioning that
classes and functions in this namespace only provide convenient access to SSL
capabilities by wrapping an actual SSL implementation (usually OpenSSL).
Context
-------
CAF users usually only need to care about the class ``context``. This is the
central state of an SSL-enabled server or client. On a server, all incoming
connection will use the configuration from this ``context``.
To create a new context, we can use one of these factory member functions:
- ``enable(bool flag)``
- ``make(tls min_version, tls max_version = tls::any)``
- ``make_server(tls min_version, tls max_version = tls::any)``
- ``make_client(tls min_version, tls max_version = tls::any)``
- ``make(dtls min_version, dtls max_version = dtls::any)``
- ``make_server(dtls min_version, dtls max_version = dtls::any)``
- ``make_client(dtls min_version, dtls max_version = dtls::any)``
All ``make*`` functions return an ``caf::expected<caf::net::ssl::context>``.
However, the factory function ``enable`` returns ``caf::expected<void>``
instead. This function is meant as an entry point for creating a ``context``
through a series of chained ``and_then`` invocations on the ``expected``. Here
is an example to illustrate how it works in practice:
.. code-block:: C++
namespace ws = caf::net::web_socket;
auto pem = ssl::format::pem;
auto key_file = caf::get_as<std::string>(cfg, "tls.key-file");
auto cert_file = caf::get_as<std::string>(cfg, "tls.cert-file");
if (!key_file != !cert_file) {
std::cerr << "*** inconsistent TLS config: declare neither file or both\n";
return EXIT_FAILURE;
}
auto server
= ws::with(sys)
.context(ssl::context::enable(key_file && cert_file)
.and_then(ssl::emplace_server(ssl::tls::v1_2))
.and_then(ssl::use_private_key_file(key_file, pem))
.and_then(ssl::use_certificate_file(cert_file, pem)))
// ...
Passing ``false`` to ``ssl::context::enable`` returns an ``expected`` with a
default-constructed ``caf::error``. Since the ``expected`` contains an
``error``, all subsequent ``and_then`` calls turn into no-ops. However, a
default-constructed ``error`` means "no error". Hence, CAF continues without an
SSL context in the example and the server will use plain (unencrypted)
WebSocket communication.
Passing ``true`` to ``ssl::context::enable`` returns an "empty"
``expected<void>``. The next call to ``and_then`` will call the function object
(with no arguments) and return a new ``expected``. In the example, we use
``emplace_server`` to create a function object that will call
``context::make_server(min, max)`` when called without arguments. Hence, we
convert an ``expected<void>`` to an ``expected<ssl::context>`` with this step.
The functions ``ssl::use_private_key_file`` and ``ssl::use_certificate_file``
return functions objects (lambdas) that take an ``ssl::context`` as argument and
return ``expected<ssl::context>`` again. In this way, we can chain functions
that modify a ``context`` with ``and_then`` to describe the setup for building
up an SSL context in a declarative way. If all of the steps for building the SSL
context succeed, the server will use WebSocket over SSL to encrypt communication
to clients. If any of the steps produces an actual error (for example if the key
file does not exist), CAF will produce an error and not start the server at all.
|see-doxygen|
manual/net/WebSocket.rst
0 → 100644
View file @
2c9adcaf
.. include:: vars.rst
.. _net-web-socket:
WebSocket :sup:`experimental`
=============================
Servers
-------
There are two ways to implement a WebSocket server with CAF. The first option is
to create a standalone server that only accepts incoming WebSocket clients. The
second option is to start a regular HTTP server and then use the
``caf::net::web_socket::switch_protocol`` factory to create a route for
WebSocket clients.
In both cases, the server runs in the background and communicates asynchronously
with the application logic over asynchronous buffer resource. Usually, this
resource is used to create an observable on some user-defined actor that
implements the server logic. The server passes connection events over this
buffer, whereas each event then consists of two new buffer resources: one for
receiving and one for sending messages from/to the WebSocket client.
.. note::
Closing either of the asynchronous resources will close the WebSocket
connection. When only reading from a WebSocket connection, we can subscribe
the output channel to a ``never`` observable. Likewise, we can pass
``std::ignore`` to the input observable for applications that are only
interested in writing to the WebSocket connection.
Standalone Server
~~~~~~~~~~~~~~~~~
.. note::
For this API, include ``caf/net/web_socket/with.hpp``.
Starting a WebSocket server has three distinct steps.
In the first step, we bind the server to an actor system and optionally
configure SSL. The entry point is always calling the free function
``caf::net::web_socket::with`` that takes an ``actor_system`` as argument.
Optionally, users may set the ``Trait`` template parameter (see `Traits`_) of
the function. When not setting this parameter, it defaults to
``caf::net::web_socket::default_trait``. With this policy, the WebSocket sends
and receives ``caf::net::web_socket::frame`` objects (see `Frames`_).
On the result factory object, we can optionally call ``context`` to set an SSL
context. Once we call ``accept``, we enter the second phase of the setup.
The ``accept`` function has multiple overloads:
- |accept-port|
- |accept-socket|
- |accept-ssl-conn|
After calling ``accept``, we enter the second step of configuring the server.
Here, we can (optionally) fine-tune the server with these member functions:
- |do-on-error|
- |max-conn|
- |reuse-addr|
The second step is completed when calling ``on_request``. This function require
one argument: a function object that takes a
``net::web_socket::acceptor<Ts...>&`` argument, whereas the template parameter
pack ``Ts...`` is freely chosen and may be empty. The types we select here allow
us to pass any number of arguments to the connection event later on.
The third and final step is to call ``start`` with a function object that takes
an asynchronous resource for processing connect events. The type is usually
obtained from the configured trait class via
``Trait::acceptor_resource<Ts...>``, whereas ``Ts...`` must be the same set of
types we have used previously for the ``on_request`` handler.
|after-start|
This example illustrates how the API looks when putting all the pieces together:
.. literalinclude:: /examples/web_socket/echo.cpp
:language: C++
:start-after: --(rst-main-begin)--
:end-before: --(rst-main-end)--
.. note::
For details on ``ssl::context::enable``, please see :ref:`net-ssl`.
HTTP Server with a WebSocket Route
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
For this API, include ``caf/net/web_socket/switch_protocol.hpp``.
Sometimes, we want a server to accept WebSocket connections only on a specific
path and otherwise act as regular HTTP server. This use case is covered by
``caf::net::web_socket::switch_protocol``. The function is similar to ``with``
in that it takes a template parameter for the trait, but it requires no
arguments. In this version, we skip the ``accept`` step (since the HTTP server
takes care of accepting connections) and call ``on_request`` as the first step.
Then, we call ``on_start`` (instead of ``start``) to add the WebSocket server
logic when starting up the parent server.
The following snippet showcases the setup for adding a WebSocket route to an
HTTP server (you can find the full example under
``examples/web_socket/quote-server.cpp``):
.. literalinclude:: /examples/web_socket/quote-server.cpp
:language: C++
:start-after: --(rst-switch_protocol-begin)--
:end-before: --(rst-switch_protocol-end)--
Clients
-------
Clients use the same entry point as standalone servers, i.e.,
``caf::net::web_socket::with``. However, instead of calling ``accept``, we call
``connect``. Like ``accept``, CAF offers multiple overloads for this function:
- |connect-host|
- |connect-uri|
- |connect-maybe-uri|
- |connect-socket|
- |connect-ssl|
.. note::
When configuring an SSL context prior to calling ``connect``, the URI scheme
*must* be ``wss``. When not configuring an SSL prior to calling ``connect``
and passing an URI with scheme ``wss``, CAF will automatically create a SSL
context for the connection.
After calling connect, we can configure various parameters:
- |do-on-error|
- |retry-delay|
- |conn-timeout|
- |max-retry|
Finally, we call ``start`` to launch the client. The function takes a function
object that must take two parameters: the input and output resources for reading
from and writing to the WebSocket connection. The types may be obtained from the
trait class (``caf::net::web_socket::default_trait`` unless passing a different
type to ``with``) via ``input_resource`` and ``output_resource``. However, the
function object (lambda) may also take the two parameters as ``auto`` for
brevity, as shown in the example below.
.. literalinclude:: /examples/web_socket/hello-client.cpp
:language: C++
:start-after: --(rst-main-begin)--
:end-before: --(rst-main-end)--
Frames
------
The WebSocket protocol operates on so-called *frames*. A frame contains a single
text or binary message. The class ``caf::net::web_socket::frame`` is an
implicitly-shared handle type that represents a single WebSocket frame (binary
or text).
The most commonly used member functions are as follows:
- ``size_t size() const noexcept`` returns the size of the frame in bytes.
- ``bool empty() const noexcept`` queries whether the frame has a size of 0.
- ``bool is_binary() const noexcept`` queries whether the frame contains raw
Bytes.
- ``bool is_text() const noexcept`` queries whether the frame contains a text
message.
- ``const_byte_span as_binary() const noexcept`` accesses the bytes of a binary
message.
- ``std::string_view as_text() const noexcept`` accesses the characters of a
text message.
For the full class interface, please refer to the Doxygen documentation.
Traits
------
A trait translates between text or binary frames on the network and the
application by defining C++ types for reading and writing from/to the WebSocket
connection. The trait class also binds these types to the asynchronous resources
that connect the WebSocket in the background to the application logic.
The interface of the default looks as follows:
.. literalinclude:: /libcaf_net/caf/net/web_socket/default_trait.hpp
:language: C++
:start-after: --(rst-class-begin)--
:end-before: --(rst-class-end)--
Users may implement custom trait types by providing the same member functions
and type aliases.
manual/net/vars.rst
0 → 100644
View file @
2c9adcaf
.. |accept-port| replace:: \
``accept(uint16_t port, std::string bind_address = "")`` for opening a new \
``port``. The ``bind_address`` optionally restricts which IP addresses may \
connect to the server. Passing an empty string (default) allows any client.
.. |accept-socket| replace:: \
``accept(tcp_accept_socket fd)`` for running the server on already \
configured TCP server socket.
.. |accept-ssl-conn| replace:: \
``accept(ssl::tcp_acceptor acc)`` for running the server on already \
configured TCP server socket with an SSL context. Using this overload \
after configuring an SSL context is an error, since CAF cannot use two SSL \
contexts on one server.
.. |connect-host| replace:: \
``connect(std::string host, uint16_t port)`` for connecting to the server \
at ``host`` on the given ``port``.
.. |connect-uri| replace:: \
``connect(uri endpoint)`` for connecting to the given server. The URI \
scheme must be either ``ws`` or ``wss``.
.. |connect-maybe-uri| replace:: \
``connect(expected<uri> endpoint)`` like the previous overload. Forwards \
the error in case ``endpoint`` does not represent an actual value. Useful \
for passing the result of ``make_uri`` to ``connect`` directly without \
having to check the result first.
.. |connect-socket| replace:: \
``connect(stream_socket fd)`` for establishing a WebSocket connection on an \
already connected TCP socket.
.. |connect-ssl| replace:: \
``connect(ssl::tcp_acceptor acc)`` for establishing a WebSocket connection \
on an already established SSL connection.
.. |do-on-error| replace:: \
``do_on_error(F callback)`` installs a callback function that CAF calls if \
an error occurs while starting the server.
.. |max-conn| replace:: \
``max_connections(size_t value)`` configures how many clients the server \
may allow to connect before blocking further connections attempts.
.. |reuse-addr| replace:: \
``reuse_address(bool value)`` configures whether we create the server \
socket with ``SO_REUSEADDR``. Has no effect if we have passed a socket or \
SSL acceptor to ``accept``.
.. |monitor| replace:: \
``monitor(ActorHandle hdl)`` configures the server to monitor an actor and \
automatically stop the server when the actor terminates.
.. |retry-delay| replace:: \
``retry_delay(timespan)`` to set the delay between connection retries when \
a connection attempt fails.
.. |conn-timeout| replace:: \
``connection_timeout(timespan)`` to set the maximum amount of time to wait \
for a connection attempt to succeed before giving up.
.. |max-retry| replace:: \
``max_retry_count(size_t)`` to set the maximum number of times to retry a \
connection attempt before giving up.
.. |after-start| replace:: \
After calling ``start``, CAF returns an ``expected<disposble>``. On \
success, the ``disposable`` is a handle to the launched server and calling \
``dispose`` on it stops the server. On error, the result contains a \
human-readable description of what went wrong.
.. |see-doxygen| replace:: \
For the full class interface, please refer to the \
`Doxygen documentation <https://www.actor-framework.org/doxygen>`__.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment