Response builder

Let’s consider that we are at the point when the response on a particular request is ready to be created and send. The key here is to use a given connection handle and response_builder_t that is created by this connection handle:

Basic example of default response builder:

// Construct response builder.
auto resp = req->create_response(); // Default status line: 200 OK

// Set header fields:
resp.append_header( restinio::http_field::server, "RESTinio server" );
resp.append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );

// Set body:
resp.set_body( "Hello world!" );

// Response is ready, send it:

Currently, there are three types of response builders. Each builder type is a specialization of a template class response_builder_t<Output> with a specific output-type:

  • Output restinio_controlled_output_t. Simple standard response builder.
  • Output user_controlled_output_t. User controlled response output builder.
  • Output chunked_output_t. Chunked transfer encoding output builder.

RESTinio controlled output response builder

Requires user to set header and body. Content length is automatically calculated. Once the data is ready, the user calls done() method and the resulting response is scheduled for sending.

handler =
 []( auto req ) {
   if( restinio::http_method_get() == req->header().method() &&
     req->header().request_target() == "/" )
       .append_header( restinio::http_field::server, "RESTinio hello world server" )
       .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
       .set_body( "Hello world!")

     return restinio::request_accepted();

   return restinio::request_rejected();

User controlled output response builder

This type of output allows the user to send body divided into parts. But it is up to a user to set the correct Content-Length field.

handler =
 []( restinio::request_handle_t req ){
   using output_type_t = restinio::user_controlled_output_t;
   auto resp = req->create_response< output_type_t >();

   resp.append_header( restinio::http_field::server, "RESTinio" )
     .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
     .set_content_length( req->body().size() );

   resp.flush(); // Send only header

   for( const char c : req->body() )
     resp.append_body( std::string{ 1, c } );
     if( '\n' == c )

   return resp.done();

Chunked transfer encoding output builder

This type of output sets transfer-encoding to chunked and expects the user to set body using chunks of data.

handler =
 []( restinio::request_handle_t req ){
   using output_type_t = restinio::chunked_output_t;
   auto resp = req->create_response< output_type_t >();

   resp.append_header( restinio::http_field::server, "RESTinio" )
     .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );

   resp.flush(); // Send only header

   for( const char c : req->body() )
     resp.append_chunk( std::string{ 1, c } );
     if( '\n' == c )

   return resp.done();


Since v.0.4.8 RESTinio support after-write status notificators. Each time a piece of data is passed to internal connection object with either flush() or done() methods of a response builder it is possible to get the status of write operation of a given data. Response builder flush() or done() methods accept a callback of type std::function< void (const asio::error_code& ) > (or std::function< void (const boost::system::error_code &) > if Boost::ASIO is used).

A simple example using notificators:

handler =
 []( restinio::request_handle_t req ){
     .append_header( restinio::http_field::server, "RESTinio" )
     .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );
   return resp.done([](const auto & ec ){
     /* Handle write status here: */
   } );

If ec passed to after-write notificator callback manifests OK status it means that data was successfully written to a buffer controlled by OS for a socket behind a given connection. So it doesn’t guarantee that the data would be received by peer. Otherwise, if the real error happened it means that data is either wasn’t received by the peer or not all the data was passed to peer.

For user controlled output or chunked output response builders after-write notificators can be passed to flush(). As flush() doesn’t completes the response it is possible to implement continuation logic based on flush().

Let’s assume one is going to serve a response with lots of data in it. It might be the case that data is received from database then formatted properly and then streamed as a response. If served data size is big than serving a plenty of such requests in parallel might cause problems as application must build the whole response body before sending it to client. As a solution for this issue one can use chunked output response builder and continuation approach using after-write notification callback.

A simplified example of this approach using std::vector<std::string> as a datasource can be found here.

Notificator invocation logic

Once a notificator is passed to either flush() or done() methods a couple of things arises necessary to know and understand for a better using and relying on notificator mechanics.

  1. Invocation guarantees

    If flush() or done() methods complete without exception then RESTinio would do its best to invoke a provided notificator reflecting the status of a write operation or an error status happened before even trying to send a particular bunch of data. A group of buffers and sendfile-operations referred to as a write group. Once a flush() or done() happens e new write group is formed. If an after-write notificator is provided then a write group is linked with a given notificator. And there are several cases when a notificator would be called:

    • The process of sending data of a given write group was started, so when it will be complete or failed, RESTinio would be able to provide original error_code obtained from ASIO (success or error statuses).

    Before describing other cases, let’s consider RESTinio error codes restinio::asio_convertible_error_t and a error category that can be obtained via restinio::restinio_err_category() (see more on error codes and error category ) This entities are used to define error codes for the following cases:

    • A connection error happened before a write group linked with the notificator even started to be sent. In this case RESTinio would invoke a notificator with special error status {asio_convertible_error_t::write_was_not_executed , restinio_err_category()}.
    • ASIO io_context object was stopped with stop() method. In this case RESTinio might have no opportunity to do anything except destructors. So there is no other option except to invoke notificators in destructor called for internal objects where notificators are stored. In this case a special error status would be {asio_convertible_error_t::write_group_destroyed_passively , restinio_err_category()}.
  2. Exceptions

    If after-write notificator throws, it is considered as connection error. After it, the connection would be closed and error would be outputted into log.

  3. Note on flushing data on response builder without data

    There is a corner case with response builder using flush() method and having no data (current accumulated data size is 0). If no callback is passed to flush() method then nothing is realy happens as there is nothing available to to send to client, so no interaction with connection context initiated (see RESTinio context entities running on asio::io_context). But if the callback is passed there appears a reason to pass something to underlying connection context. Well, it is not the data, but an action to perform. It is handy to assume that the data of zero size is passed to connection and a given notificator must be invoked after the write operation completes. It means the usual invocation logic applies to a given notificator.

  4. Be aware of circular links

    After-write notificators are stored internaly as std::function<...> object. Internaly in RESTinio an instance of connection context lives as a std::shared_ptr to a corresponding class. And inside a response builder object there lives a shared pointer to a target connection. So if you put a reponse builder into a after-write callback, you create a circular link since a connection context would finaly store an entity that would have a shared pointer to the connection context itself.

Status line

In general a status line must be set when starting a response (e.g. 200 OK, 404 Not Found etc). So response builder factory expects an object of type http_status_line_t as an argument (that is set to {200, "OK"} by default). Class http_status_line_t has the following constructor:

  http_status_code_t sc,
  std::string reason_phrase );

Here http_status_code_t is a strong typedef for HTTP response status code. But using instantiation of http_status_line_t is necessary in rare cases where a non-standard status code or a reason phrase is needed. In most of the cases, it is better to use a helper function restinio::status_*() returning a proper http_status_line_t object.

For example:

// 404 Not Found.
auto resp = req->create_response( restinio::status_not_found() ) ;
// ...

RFC 2616 statuses (413, 414 from RFC 7231)

restinio::status_* function Status line
status_continue() 100 Continue
status_switching_protocols() 101 Switching Protocols
status_ok() 200 OK
status_created() 201 Created
status_accepted() 202 Accepted
status_non_authoritative_information() 203 Non-Authoritative Information
status_no_content() 204 No Content
status_reset_content() 205 Reset Content
status_partial_content() 206 Partial Content
status_multiple_choices() 300 Multiple Choices
status_moved_permanently() 301 Moved Permanently
status_found() 302 Found
status_see_other() 303 See Other
status_not_modified() 304 Not Modified
status_use_proxy() 305 Use Proxy
status_temporary_redirect() 307 Temporary Redirect
status_bad_request() 400 Bad Request
status_unauthorized() 401 Unauthorized
status_payment_required() 402 Payment Required
status_forbidden() 403 Forbidden
status_not_found() 404 Not Found
status_method_not_allowed() 405 Method Not Allowed
status_not_acceptable() 406 Not Acceptable
status_proxy_authentication_required() 407 Proxy Authentication Required
status_request_time_out() 408 Request Timeout
status_conflict() 409 Conflict
status_gone() 410 Gone
status_length_required() 411 Length Required
status_precondition_failed() 412 Precondition Failed
status_payload_too_large() 413 Payload Too Large
status_uri_too_long() 414 URI Too Long
status_unsupported_media_type() 415 Unsupported Media Type
status_requested_range_not_satisfiable() 416 Requested Range Not Satisfiable
status_expectation_failed() 417 Expectation Failed
status_internal_server_error() 500 Internal Server Error
status_not_implemented() 501 Not Implemented
status_bad_gateway() 502 Bad Gateway
status_service_unavailable() 503 Service Unavailable
status_gateway_time_out() 504 Gateway Timeout
status_http_version_not_supported() 505 HTTP Version not supported

RFC 2518 statuses

restinio::status_* function Status line
status_processing() 102 Processing
status_multi_status() 207 Multi-Status
status_unprocessable_entity() 422 Unprocessable Entity
status_locked() 423 Locked
status_failed_dependency() 424 Failed Dependency
status_insufficient_storage() 507 Insufficient Storage

RFC 6585 statuses

restinio::status_* function Status line
status_precondition_required() 428 Precondition Required
status_too_many_requests() 429 Too Many Requests
status_request_header_fields_too_large() 431 Request Header Fields Too Large
status_network_authentication_required() 511 Network Authentication Required

RFC 7538 statuses

restinio::status_* function Status line
status_permanent_redirect() 308 Permanent Redirect