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_date_field();
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:
resp.done();
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() == "/" )
{
req->create_response()
.append_header( restinio::http_field::server, "RESTinio hello world server" )
.append_header_date_field()
.append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" )
.set_body( "Hello world!")
.done();
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_date_field()
.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 )
{
resp.flush();
}
}
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_date_field()
.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 )
{
resp.flush();
}
}
return resp.done();
}
Notificators
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 ){
req->create_response<>()
.append_header( restinio::http_field::server, "RESTinio" )
.append_header_date_field()
.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.
Invocation guarantees
If
flush()
ordone()
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 aflush()
ordone()
happens the 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 viarestinio::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 withstop()
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()}
.
- 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
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.
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 toflush()
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.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 astd::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_line_t(
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 |