HTTP experimental

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(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(tcp_accept_socket fd) for running the server on already configured TCP server socket.

  • 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.

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(F callback) installs a callback function that CAF calls if an error occurs while starting the server.

  • max_connections(size_t value) configures how many clients the server may allow to connect before blocking further connections attempts.

  • 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(ActorHandle hdl) configures the server to monitor an actor and automatically stop the server when the actor terminates.

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:

.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.

int caf_main(caf::actor_system& sys, const config& cfg) {
  namespace http = caf::net::http;
  namespace ssl = caf::net::ssl;
  // Read the configuration.
  auto port = caf::get_or(cfg, "port", default_port);
  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");
  auto max_connections = caf::get_or(cfg, "max-connections",
                                     default_max_connections);
  if (!key_file != !cert_file) {
    std::cerr << "*** inconsistent TLS config: declare neither file or both\n";
    return EXIT_FAILURE;
  }
  // Open up a TCP port for incoming connections and start the server.
  auto server
    = http::with(sys)
        // Optionally enable TLS.
        .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)))
        // Bind to the user-defined port.
        .accept(port)
        // Limit how many clients may be connected at any given time.
        .max_connections(max_connections)
        // Provide the time at '/'.
        .route("/", http::method::get,
               [](http::responder& res) {
                 auto str = caf::deep_to_string(caf::make_timestamp());
                 res.respond(http::status::ok, "text/plain", str);
               })
        // Launch the server.
        .start();
  // Report any error to the user.
  if (!server) {
    std::cerr << "*** unable to run at port " << port << ": "
              << to_string(server.error()) << '\n';
    return EXIT_FAILURE;
  }
  // Note: the actor system will only wait for actors on default. Since we don't
  // start actors, we need to block on something else.
  std::cout << "Server is up and running. Press <enter> to shut down."
            << std::endl;
  getchar();
  std::cout << "Terminating.\n";
  return EXIT_SUCCESS;
}

Note

For details on ssl::context::enable, please see 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 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.

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 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.

For the full class interface, please refer to the Doxygen documentation.