diff --git a/examples/asio-sv.cc b/examples/asio-sv.cc index bba048fa..8d88ddcb 100644 --- a/examples/asio-sv.cc +++ b/examples/asio-sv.cc @@ -45,23 +45,23 @@ using namespace nghttp2::asio_http2::server; int main(int argc, char *argv[]) { try { // Check command line arguments. - if (argc < 3) { - std::cerr << "Usage: asio-sv " - << "\n"; + if (argc < 4) { + std::cerr + << "Usage: asio-sv
[ " + << "]\n"; return 1; } - uint16_t port = std::stoi(argv[1]); - std::size_t num_threads = std::stoi(argv[2]); + boost::system::error_code ec; + + std::string addr = argv[1]; + std::string port = argv[2]; + std::size_t num_threads = std::stoi(argv[3]); http2 server; server.num_threads(num_threads); - if (argc >= 5) { - server.tls(argv[3], argv[4]); - } - server.handle("/", [](const request &req, const response &res) { res.write_head(200, {{"foo", {"bar"}}}); res.end("hello, world\n"); @@ -101,8 +101,22 @@ int main(int argc, char *argv[]) { res.end("finally!\n"); }); }); - server.listen_and_serve("*", port); + if (argc >= 6) { + boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23); + tls.use_private_key_file(argv[4], boost::asio::ssl::context::pem); + tls.use_certificate_chain_file(argv[5]); + + configure_tls_context_easy(ec, tls); + + if (server.listen_and_serve(ec, tls, addr, port)) { + std::cerr << "error: " << ec.message() << std::endl; + } + } else { + if (server.listen_and_serve(ec, addr, port)) { + std::cerr << "error: " << ec.message() << std::endl; + } + } } catch (std::exception &e) { std::cerr << "exception: " << e.what() << "\n"; } diff --git a/examples/asio-sv2.cc b/examples/asio-sv2.cc index a756e9e0..6cc2d5c0 100644 --- a/examples/asio-sv2.cc +++ b/examples/asio-sv2.cc @@ -48,24 +48,23 @@ using namespace nghttp2::asio_http2::server; int main(int argc, char *argv[]) { try { // Check command line arguments. - if (argc < 4) { - std::cerr << "Usage: asio-sv2 " - << " \n"; + if (argc < 5) { + std::cerr << "Usage: asio-sv2
" + << "[ ]\n"; return 1; } - uint16_t port = std::stoi(argv[1]); - std::size_t num_threads = std::stoi(argv[2]); - std::string docroot = argv[3]; + boost::system::error_code ec; + + std::string addr = argv[1]; + std::string port = argv[2]; + std::size_t num_threads = std::stoi(argv[3]); + std::string docroot = argv[4]; http2 server; server.num_threads(num_threads); - if (argc >= 6) { - server.tls(argv[4], argv[5]); - } - server.handle("/", [&docroot](const request &req, const response &res) { auto path = percent_decode(req.uri().path); if (!check_path(path)) { @@ -99,8 +98,21 @@ int main(int argc, char *argv[]) { res.end(file_generator_from_fd(fd)); }); - server.listen_and_serve("*", port); + if (argc >= 7) { + boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23); + tls.use_private_key_file(argv[5], boost::asio::ssl::context::pem); + tls.use_certificate_chain_file(argv[6]); + configure_tls_context_easy(ec, tls); + + if (server.listen_and_serve(ec, tls, addr, port)) { + std::cerr << "error: " << ec.message() << std::endl; + } + } else { + if (server.listen_and_serve(ec, addr, port)) { + std::cerr << "error: " << ec.message() << std::endl; + } + } } catch (std::exception &e) { std::cerr << "exception: " << e.what() << "\n"; } diff --git a/src/Makefile.am b/src/Makefile.am index 43103ade..7e6e5bcb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -189,6 +189,7 @@ libnghttp2_asio_la_SOURCES = \ asio_server_stream.cc asio_server_stream.h \ asio_server_serve_mux.cc asio_server_serve_mux.h \ asio_server_request_handler.cc asio_server_request_handler.h \ + asio_server_tls_context.cc asio_server_tls_context.h \ asio_client_session.cc \ asio_client_session_impl.cc asio_client_session_impl.h \ asio_client_session_tcp_impl.cc asio_client_session_tcp_impl.h \ diff --git a/src/asio_server.cc b/src/asio_server.cc index e19435d2..c0a7b8bf 100644 --- a/src/asio_server.cc +++ b/src/asio_server.cc @@ -45,14 +45,11 @@ namespace nghttp2 { namespace asio_http2 { namespace server { -server::server(const std::string &address, uint16_t port, - std::size_t io_service_pool_size, serve_mux &mux, - std::unique_ptr ssl_ctx, int backlog) +server::server(std::size_t io_service_pool_size) : io_service_pool_(io_service_pool_size), signals_(io_service_pool_.get_io_service()), tick_timer_(io_service_pool_.get_io_service(), - boost::posix_time::seconds(1)), - ssl_ctx_(std::move(ssl_ctx)), mux_(mux) { + boost::posix_time::seconds(1)) { // Register to handle the signals that indicate when the server should exit. // It is safe to register for the same signal multiple times in a program, // provided all registration for the specified signal is made through Asio. @@ -63,47 +60,85 @@ server::server(const std::string &address, uint16_t port, #endif // defined(SIGQUIT) signals_.async_wait([this](const boost::system::error_code &error, int signal_number) { io_service_pool_.stop(); }); +} - // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). - boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service()); - boost::asio::ip::tcp::resolver::query query(address, std::to_string(port)); +boost::system::error_code +server::listen_and_serve(boost::system::error_code &ec, + boost::asio::ssl::context *tls_context, + const std::string &address, const std::string &port, + int backlog, serve_mux &mux) { + ec.clear(); - for (auto itr = resolver.resolve(query); - itr != boost::asio::ip::tcp::resolver::iterator(); ++itr) { - boost::asio::ip::tcp::endpoint endpoint = *itr; - auto acceptor = - boost::asio::ip::tcp::acceptor(io_service_pool_.get_io_service()); - - acceptor.open(endpoint.protocol()); - acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - acceptor.bind(endpoint); - if (backlog == -1) { - acceptor.listen(); - } else { - acceptor.listen(backlog); - } - acceptors_.push_back(std::move(acceptor)); + if (bind_and_listen(ec, address, port, backlog)) { + return ec; } for (auto &acceptor : acceptors_) { - start_accept(acceptor); + if (tls_context) { + start_accept(*tls_context, acceptor, mux); + } else { + start_accept(acceptor, mux); + } } start_timer(); + + io_service_pool_.run(); + + return ec; } -void server::run() { io_service_pool_.run(); } +boost::system::error_code server::bind_and_listen(boost::system::error_code &ec, + const std::string &address, + const std::string &port, + int backlog) { + // Open the acceptor with the option to reuse the address (i.e. + // SO_REUSEADDR). + tcp::resolver resolver(io_service_pool_.get_io_service()); + tcp::resolver::query query(address, port); + auto it = resolver.resolve(query, ec); + if (ec) { + return ec; + } + + for (; it != tcp::resolver::iterator(); ++it) { + tcp::endpoint endpoint = *it; + auto acceptor = tcp::acceptor(io_service_pool_.get_io_service()); + + if (acceptor.open(endpoint.protocol(), ec)) { + continue; + } + + acceptor.set_option(tcp::acceptor::reuse_address(true)); + + if (acceptor.bind(endpoint, ec)) { + continue; + } + + if (acceptor.listen( + backlog == -1 ? boost::asio::socket_base::max_connections : backlog, + ec)) { + continue; + } + + acceptors_.push_back(std::move(acceptor)); + } + + if (acceptors_.empty()) { + return ec; + } + + // ec could have some errors since we may have failed to bind some + // interfaces. + ec.clear(); + + return ec; +} std::shared_ptr cached_date; -namespace { -void update_date() { - cached_date = std::make_shared(util::http_date(time(nullptr))); -} -} // namespace - void server::start_timer() { - update_date(); + cached_date = std::make_shared(util::http_date(time(nullptr))); tick_timer_.async_wait([this](const boost::system::error_code &e) { tick_timer_.expires_at(tick_timer_.expires_at() + @@ -112,47 +147,43 @@ void server::start_timer() { }); } -typedef boost::asio::ssl::stream ssl_socket; +void server::start_accept(boost::asio::ssl::context &tls_context, + tcp::acceptor &acceptor, serve_mux &mux) { + auto new_connection = std::make_shared>( + mux, io_service_pool_.get_io_service(), tls_context); -void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) { - if (ssl_ctx_) { - auto new_connection = std::make_shared>( - mux_, io_service_pool_.get_io_service(), *ssl_ctx_); + acceptor.async_accept(new_connection->socket().lowest_layer(), + [this, &tls_context, &acceptor, &mux, new_connection]( + const boost::system::error_code &e) { + if (!e) { + new_connection->socket().lowest_layer().set_option(tcp::no_delay(true)); + new_connection->socket().async_handshake( + boost::asio::ssl::stream_base::server, + [new_connection](const boost::system::error_code &e) { + if (!e) { + new_connection->start(); + } + }); + } - acceptor.async_accept( - new_connection->socket().lowest_layer(), - [this, &acceptor, new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->socket().lowest_layer().set_option( - boost::asio::ip::tcp::no_delay(true)); - new_connection->socket().async_handshake( - boost::asio::ssl::stream_base::server, - [new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->start(); - } - }); - } + start_accept(tls_context, acceptor, mux); + }); +} - start_accept(acceptor); - }); - } else { - auto new_connection = - std::make_shared>( - mux_, io_service_pool_.get_io_service()); +void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) { + auto new_connection = std::make_shared>( + mux, io_service_pool_.get_io_service()); - acceptor.async_accept( - new_connection->socket(), - [this, &acceptor, new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->socket().set_option( - boost::asio::ip::tcp::no_delay(true)); - new_connection->start(); - } + acceptor.async_accept(new_connection->socket(), + [this, &acceptor, &mux, new_connection]( + const boost::system::error_code &e) { + if (!e) { + new_connection->socket().set_option(tcp::no_delay(true)); + new_connection->start(); + } - start_accept(acceptor); - }); - } + start_accept(acceptor, mux); + }); } } // namespace server diff --git a/src/asio_server.h b/src/asio_server.h index 533ce2da..048d6ceb 100644 --- a/src/asio_server.h +++ b/src/asio_server.h @@ -57,39 +57,49 @@ namespace server { class serve_mux; -/// The top-level class of the HTTP server. +using boost::asio::ip::tcp; + +using ssl_socket = boost::asio::ssl::stream; + class server : private boost::noncopyable { public: - /// Construct the server to listen on the specified TCP address and port, and - /// serve up files from the given directory. - explicit server(const std::string &address, uint16_t port, - std::size_t io_service_pool_size, serve_mux &mux_, - std::unique_ptr ssl_ctx, - int backlog = -1); + explicit server(std::size_t io_service_pool_size); - /// Run the server's io_service loop. - void run(); + boost::system::error_code + listen_and_serve(boost::system::error_code &ec, + boost::asio::ssl::context *tls_context, + const std::string &address, const std::string &port, + int backlog, serve_mux &mux); private: /// Initiate an asynchronous accept operation. - void start_accept(boost::asio::ip::tcp::acceptor &acceptor); + void start_accept(tcp::acceptor &acceptor, serve_mux &mux); + /// Same as above but with tls_context + void start_accept(boost::asio::ssl::context &tls_context, + tcp::acceptor &acceptor, serve_mux &mux); + + /// Resolves address and bind socket to the resolved addresses. + boost::system::error_code bind_and_listen(boost::system::error_code &ec, + const std::string &address, + const std::string &port, + int backlog); void start_timer(); - /// The pool of io_service objects used to perform asynchronous operations. + /// The pool of io_service objects used to perform asynchronous + /// operations. io_service_pool io_service_pool_; - /// The signal_set is used to register for process termination notifications. + /// The signal_set is used to register for process termination + /// notifications. boost::asio::signal_set signals_; boost::asio::deadline_timer tick_timer_; /// Acceptor used to listen for incoming connections. - std::vector acceptors_; + std::vector acceptors_; std::unique_ptr ssl_ctx_; - - serve_mux &mux_; }; } // namespace server diff --git a/src/asio_server_http2.cc b/src/asio_server_http2.cc index e269adb5..f00d77cd 100644 --- a/src/asio_server_http2.cc +++ b/src/asio_server_http2.cc @@ -40,16 +40,21 @@ http2::http2() : impl_(make_unique()) {} http2::~http2() {} -void http2::listen_and_serve(const std::string &address, uint16_t port) { - impl_->listen_and_serve(address, port); +boost::system::error_code http2::listen_and_serve(boost::system::error_code &ec, + const std::string &address, + const std::string &port) { + return impl_->listen_and_serve(ec, nullptr, address, port); +} + +boost::system::error_code +http2::listen_and_serve(boost::system::error_code &ec, + boost::asio::ssl::context &tls_context, + const std::string &address, const std::string &port) { + return impl_->listen_and_serve(ec, &tls_context, address, port); } void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); } -void http2::tls(std::string private_key_file, std::string certificate_file) { - impl_->tls(std::move(private_key_file), std::move(certificate_file)); -} - void http2::backlog(int backlog) { impl_->backlog(backlog); } bool http2::handle(std::string pattern, request_cb cb) { diff --git a/src/asio_server_http2_impl.cc b/src/asio_server_http2_impl.cc index 1fd08ce2..96ca2432 100644 --- a/src/asio_server_http2_impl.cc +++ b/src/asio_server_http2_impl.cc @@ -39,66 +39,15 @@ namespace server { http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {} -namespace { -std::vector &get_alpn_token() { - static auto alpn_token = util::get_default_alpn(); - return alpn_token; -} -} // namespace - -void http2_impl::listen_and_serve(const std::string &address, uint16_t port) { - std::unique_ptr ssl_ctx; - - if (!private_key_file_.empty() && !certificate_file_.empty()) { - ssl_ctx = make_unique( - boost::asio::ssl::context::sslv23); - - ssl_ctx->use_private_key_file(private_key_file_, - boost::asio::ssl::context::pem); - ssl_ctx->use_certificate_chain_file(certificate_file_); - - auto ctx = ssl_ctx->native_handle(); - - SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | - SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET | - SSL_OP_CIPHER_SERVER_PREFERENCE); - SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); - - SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST); - - auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - if (ecdh) { - SSL_CTX_set_tmp_ecdh(ctx, ecdh); - EC_KEY_free(ecdh); - } - - SSL_CTX_set_next_protos_advertised_cb( - ctx, - [](SSL *s, const unsigned char **data, unsigned int *len, void *arg) { - auto &token = get_alpn_token(); - - *data = token.data(); - *len = token.size(); - - return SSL_TLSEXT_ERR_OK; - }, - nullptr); - } - - server(address, port, num_threads_, mux_, std::move(ssl_ctx), backlog_).run(); +boost::system::error_code http2_impl::listen_and_serve( + boost::system::error_code &ec, boost::asio::ssl::context *tls_context, + const std::string &address, const std::string &port) { + return server(num_threads_) + .listen_and_serve(ec, tls_context, address, port, backlog_, mux_); } void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; } -void http2_impl::tls(std::string private_key_file, - std::string certificate_file) { - private_key_file_ = std::move(private_key_file); - certificate_file_ = std::move(certificate_file); -} - void http2_impl::backlog(int backlog) { backlog_ = backlog; } bool http2_impl::handle(std::string pattern, request_cb cb) { diff --git a/src/asio_server_http2_impl.h b/src/asio_server_http2_impl.h index d2f373e3..f097aea0 100644 --- a/src/asio_server_http2_impl.h +++ b/src/asio_server_http2_impl.h @@ -42,15 +42,15 @@ class server; class http2_impl { public: http2_impl(); - void listen_and_serve(const std::string &address, uint16_t port); + boost::system::error_code + listen_and_serve(boost::system::error_code &ec, + boost::asio::ssl::context *tls_context, + const std::string &address, const std::string &port); void num_threads(size_t num_threads); - void tls(std::string private_key_file, std::string certificate_file); void backlog(int backlog); bool handle(std::string pattern, request_cb cb); private: - std::string private_key_file_; - std::string certificate_file_; std::unique_ptr server_; std::size_t num_threads_; int backlog_; diff --git a/src/asio_server_tls_context.cc b/src/asio_server_tls_context.cc new file mode 100644 index 00000000..11336c98 --- /dev/null +++ b/src/asio_server_tls_context.cc @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_tls_context.h" + +#include + +#include + +#include "ssl.h" +#include "util.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +namespace { +std::vector &get_alpn_token() { + static auto alpn_token = util::get_default_alpn(); + return alpn_token; +} +} // namespace + +boost::system::error_code +configure_tls_context_easy(boost::system::error_code &ec, + boost::asio::ssl::context &tls_context) { + ec.clear(); + + auto ctx = tls_context.native_handle(); + + SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET | + SSL_OP_CIPHER_SERVER_PREFERENCE); + SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); + + SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST); + + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh) { + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + EC_KEY_free(ecdh); + } + + SSL_CTX_set_next_protos_advertised_cb( + ctx, + [](SSL *s, const unsigned char **data, unsigned int *len, void *arg) { + auto &token = get_alpn_token(); + + *data = token.data(); + *len = token.size(); + + return SSL_TLSEXT_ERR_OK; + }, + nullptr); + + return ec; +} + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_tls_context.h b/src/asio_server_tls_context.h new file mode 100644 index 00000000..0f9b563e --- /dev/null +++ b/src/asio_server_tls_context.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_TLS_CONTEXT_H +#define ASIO_SERVER_TLS_CONTEXT_H + +#include "nghttp2_config.h" + +#include + +#endif // ASIO_SERVER_TLS_CONTEXT_H diff --git a/src/includes/nghttp2/asio_http2_server.h b/src/includes/nghttp2/asio_http2_server.h index 12ac871c..21c662c5 100644 --- a/src/includes/nghttp2/asio_http2_server.h +++ b/src/includes/nghttp2/asio_http2_server.h @@ -127,8 +127,17 @@ public: ~http2(); // Starts listening connection on given address and port and serves - // incoming requests. - void listen_and_serve(const std::string &address, uint16_t port); + // incoming requests in cleartext TCP connection. + boost::system::error_code listen_and_serve(boost::system::error_code &ec, + const std::string &address, + const std::string &port); + + // Starts listening connection on given address and port and serves + // incoming requests in SSL/TLS encrypted connection. + boost::system::error_code + listen_and_serve(boost::system::error_code &ec, + boost::asio::ssl::context &tls_context, + const std::string &address, const std::string &port); // Registers request handler |cb| with path pattern |pattern|. This // function will fail and returns false if same pattern has been @@ -141,10 +150,6 @@ public: // It defaults to 1. void num_threads(size_t num_threads); - // Sets TLS private key file and certificate file. Both files must - // be in PEM format. - void tls(std::string private_key_file, std::string certificate_file); - // Sets the maximum length to which the queue of pending // connections. void backlog(int backlog); @@ -153,6 +158,13 @@ private: std::unique_ptr impl_; }; +// Configures |tls_context| for server use. This function sets couple +// of OpenSSL options (disables SSLv2 and SSLv3 and compression) and +// enables ECDHE ciphers. NPN callback is also configured. +boost::system::error_code +configure_tls_context_easy(boost::system::error_code &ec, + boost::asio::ssl::context &tls_context); + // Returns request handler to do redirect to |uri| using // |status_code|. The |uri| appears in "location" header field as is. request_cb redirect_handler(int status_code, std::string uri);