From 92c7b6fbd1ff9cd926f98f09263fbaf8eaa26056 Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Mon, 19 Aug 2019 14:22:54 +0300 Subject: [PATCH 1/7] Added 'Connection' header extraction. --- include/server/http/request.hpp | 1 + src/server/request_parser.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/server/http/request.hpp b/include/server/http/request.hpp index 1b86253f7..e5d867c23 100644 --- a/include/server/http/request.hpp +++ b/include/server/http/request.hpp @@ -17,6 +17,7 @@ struct request std::string uri; std::string referrer; std::string agent; + std::string connection; boost::asio::ip::address endpoint; }; } diff --git a/src/server/request_parser.cpp b/src/server/request_parser.cpp index e408a792f..283425693 100644 --- a/src/server/request_parser.cpp +++ b/src/server/request_parser.cpp @@ -180,6 +180,11 @@ RequestParser::RequestStatus RequestParser::consume(http::request ¤t_reque current_request.agent = current_header.value; } + if (boost::iequals(current_header.name, "Connection")) + { + current_request.connection = current_header.value; + } + if (input == '\r') { state = internal_state::expecting_newline_3; From a0582a3e68e87084858247e73b56dce421f9c175 Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Mon, 19 Aug 2019 15:45:20 +0300 Subject: [PATCH 2/7] Added keep-alive support to the http server. --- include/server/connection.hpp | 3 ++ src/server/connection.cpp | 97 ++++++++++++++++++++--------------- src/server/http/reply.cpp | 6 +-- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/include/server/connection.hpp b/include/server/connection.hpp index 08821597e..ea4e7f81e 100644 --- a/include/server/connection.hpp +++ b/include/server/connection.hpp @@ -65,6 +65,9 @@ class Connection : public std::enable_shared_from_this std::vector compressed_output; // Header compression_header; std::vector output_buffer; + //Keep alive support + bool keep_alive = false; + short processed_requests = 512; }; } } diff --git a/src/server/connection.cpp b/src/server/connection.cpp index 9cb478afc..4b1cdd518 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -4,12 +4,14 @@ #include #include +#include #include #include #include #include #include +#include namespace osrm { @@ -26,12 +28,12 @@ boost::asio::ip::tcp::socket &Connection::socket() { return TCP_socket; } /// Start the first asynchronous operation for the connection. void Connection::start() { - TCP_socket.async_read_some( - boost::asio::buffer(incoming_data_buffer), - strand.wrap(boost::bind(&Connection::handle_read, - this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + TCP_socket.async_read_some( + boost::asio::buffer(incoming_data_buffer), + strand.wrap(boost::bind(&Connection::handle_read, + this->shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); } void Connection::handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) @@ -45,9 +47,9 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t http::compression_type compression_type(http::no_compression); RequestParser::RequestStatus result; std::tie(result, compression_type) = - request_parser.parse(current_request, - incoming_data_buffer.data(), - incoming_data_buffer.data() + bytes_transferred); + request_parser.parse(current_request, + incoming_data_buffer.data(), + incoming_data_buffer.data() + bytes_transferred); // the request has been parsed if (result == RequestParser::RequestStatus::valid) @@ -55,32 +57,40 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t current_request.endpoint = TCP_socket.remote_endpoint().address(); request_handler.HandleRequest(current_request, current_reply); + if (boost::iequals(current_request.connection, "close")) { + current_reply.headers.emplace_back("Connection", "close"); + } else { + keep_alive = true; + current_reply.headers.emplace_back("Connection", "keep-alive"); + current_reply.headers.emplace_back("Keep-Alive", "timeout=5, max=512"); + } + // compress the result w/ gzip/deflate if requested switch (compression_type) { - case http::deflate_rfc1951: - // use deflate for compression - current_reply.headers.insert(current_reply.headers.begin(), - {"Content-Encoding", "deflate"}); - compressed_output = compress_buffers(current_reply.content, compression_type); - current_reply.set_size(static_cast(compressed_output.size())); - output_buffer = current_reply.headers_to_buffers(); - output_buffer.push_back(boost::asio::buffer(compressed_output)); - break; - case http::gzip_rfc1952: - // use gzip for compression - current_reply.headers.insert(current_reply.headers.begin(), - {"Content-Encoding", "gzip"}); - compressed_output = compress_buffers(current_reply.content, compression_type); - current_reply.set_size(static_cast(compressed_output.size())); - output_buffer = current_reply.headers_to_buffers(); - output_buffer.push_back(boost::asio::buffer(compressed_output)); - break; - case http::no_compression: - // don't use any compression - current_reply.set_uncompressed_size(); - output_buffer = current_reply.to_buffers(); - break; + case http::deflate_rfc1951: + // use deflate for compression + current_reply.headers.insert(current_reply.headers.begin(), + {"Content-Encoding", "deflate"}); + compressed_output = compress_buffers(current_reply.content, compression_type); + current_reply.set_size(static_cast(compressed_output.size())); + output_buffer = current_reply.headers_to_buffers(); + output_buffer.push_back(boost::asio::buffer(compressed_output)); + break; + case http::gzip_rfc1952: + // use gzip for compression + current_reply.headers.insert(current_reply.headers.begin(), + {"Content-Encoding", "gzip"}); + compressed_output = compress_buffers(current_reply.content, compression_type); + current_reply.set_size(static_cast(compressed_output.size())); + output_buffer = current_reply.headers_to_buffers(); + output_buffer.push_back(boost::asio::buffer(compressed_output)); + break; + case http::no_compression: + // don't use any compression + current_reply.set_uncompressed_size(); + output_buffer = current_reply.to_buffers(); + break; } // write result to stream boost::asio::async_write(TCP_socket, @@ -103,11 +113,11 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t { // we don't have a result yet, so continue reading TCP_socket.async_read_some( - boost::asio::buffer(incoming_data_buffer), - strand.wrap(boost::bind(&Connection::handle_read, - this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + boost::asio::buffer(incoming_data_buffer), + strand.wrap(boost::bind(&Connection::handle_read, + this->shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); } } @@ -116,9 +126,16 @@ void Connection::handle_write(const boost::system::error_code &error) { if (!error) { - // Initiate graceful connection closure. - boost::system::error_code ignore_error; - TCP_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_error); + if (keep_alive && processed_requests > 0) { + --processed_requests; + current_request = http::request(); + request_parser = RequestParser(); + this->start(); + } else { + // Initiate graceful connection closure. + boost::system::error_code ignore_error; + TCP_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_error); + } } } diff --git a/src/server/http/reply.cpp b/src/server/http/reply.cpp index d279417e0..b73e381a7 100644 --- a/src/server/http/reply.cpp +++ b/src/server/http/reply.cpp @@ -103,11 +103,7 @@ boost::asio::const_buffer reply::status_to_buffer(const reply::status_type statu return boost::asio::buffer(http_bad_request_string); } -reply::reply() : status(ok) -{ - // We do not currently support keep alive. Always set 'Connection: close'. - headers.emplace_back("Connection", "close"); -} +reply::reply() : status(ok) {} } } } From 22550d078f2a75341b2a74d9800361325bdeb70b Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Mon, 19 Aug 2019 16:15:56 +0300 Subject: [PATCH 3/7] Added timeout handling for keep-alive operations. --- include/server/connection.hpp | 5 +++++ src/server/connection.cpp | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/server/connection.hpp b/include/server/connection.hpp index ea4e7f81e..dda92f863 100644 --- a/include/server/connection.hpp +++ b/include/server/connection.hpp @@ -52,11 +52,15 @@ class Connection : public std::enable_shared_from_this /// Handle completion of a write operation. void handle_write(const boost::system::error_code &e); + /// Handle read timeout + void handle_timeout(); + std::vector compress_buffers(const std::vector &uncompressed_data, const http::compression_type compression_type); boost::asio::io_service::strand strand; boost::asio::ip::tcp::socket TCP_socket; + boost::asio::deadline_timer timer; RequestHandler &request_handler; RequestParser request_parser; boost::array incoming_data_buffer; @@ -68,6 +72,7 @@ class Connection : public std::enable_shared_from_this //Keep alive support bool keep_alive = false; short processed_requests = 512; + short keepalive_timeout = 5; // In seconds }; } } diff --git a/src/server/connection.cpp b/src/server/connection.cpp index 4b1cdd518..15ab82760 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -19,7 +19,7 @@ namespace server { Connection::Connection(boost::asio::io_service &io_service, RequestHandler &handler) - : strand(io_service), TCP_socket(io_service), request_handler(handler) + : strand(io_service), TCP_socket(io_service), timer(io_service), request_handler(handler) { } @@ -28,12 +28,19 @@ boost::asio::ip::tcp::socket &Connection::socket() { return TCP_socket; } /// Start the first asynchronous operation for the connection. void Connection::start() { - TCP_socket.async_read_some( + TCP_socket.async_read_some( boost::asio::buffer(incoming_data_buffer), strand.wrap(boost::bind(&Connection::handle_read, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred))); + + //init async timer + timer.cancel(); + timer.expires_from_now(boost::posix_time::seconds(keepalive_timeout)); + timer.async_wait(boost::bind(&Connection::handle_timeout, + this->shared_from_this())); + } void Connection::handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) @@ -133,12 +140,18 @@ void Connection::handle_write(const boost::system::error_code &error) this->start(); } else { // Initiate graceful connection closure. - boost::system::error_code ignore_error; - TCP_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_error); + handle_timeout(); } } } +/// Handle completion of a write operation. +void Connection::handle_timeout() +{ + boost::system::error_code ignore_error; + TCP_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_error); +} + std::vector Connection::compress_buffers(const std::vector &uncompressed_data, const http::compression_type compression_type) { From a7b7d77e1fff8a7605319ce6a248178104f39a19 Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Mon, 19 Aug 2019 16:27:45 +0300 Subject: [PATCH 4/7] Formatted the code. --- include/server/connection.hpp | 2 +- src/server/connection.cpp | 96 ++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/include/server/connection.hpp b/include/server/connection.hpp index dda92f863..b47a2a04f 100644 --- a/include/server/connection.hpp +++ b/include/server/connection.hpp @@ -69,7 +69,7 @@ class Connection : public std::enable_shared_from_this std::vector compressed_output; // Header compression_header; std::vector output_buffer; - //Keep alive support + // Keep alive support bool keep_alive = false; short processed_requests = 512; short keepalive_timeout = 5; // In seconds diff --git a/src/server/connection.cpp b/src/server/connection.cpp index 15ab82760..521797194 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -2,16 +2,16 @@ #include "server/request_handler.hpp" #include "server/request_parser.hpp" +#include #include #include -#include #include #include #include #include -#include #include +#include namespace osrm { @@ -29,18 +29,16 @@ boost::asio::ip::tcp::socket &Connection::socket() { return TCP_socket; } void Connection::start() { TCP_socket.async_read_some( - boost::asio::buffer(incoming_data_buffer), - strand.wrap(boost::bind(&Connection::handle_read, - this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + boost::asio::buffer(incoming_data_buffer), + strand.wrap(boost::bind(&Connection::handle_read, + this->shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); - //init async timer + // init async timer timer.cancel(); timer.expires_from_now(boost::posix_time::seconds(keepalive_timeout)); - timer.async_wait(boost::bind(&Connection::handle_timeout, - this->shared_from_this())); - + timer.async_wait(boost::bind(&Connection::handle_timeout, this->shared_from_this())); } void Connection::handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) @@ -54,9 +52,9 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t http::compression_type compression_type(http::no_compression); RequestParser::RequestStatus result; std::tie(result, compression_type) = - request_parser.parse(current_request, - incoming_data_buffer.data(), - incoming_data_buffer.data() + bytes_transferred); + request_parser.parse(current_request, + incoming_data_buffer.data(), + incoming_data_buffer.data() + bytes_transferred); // the request has been parsed if (result == RequestParser::RequestStatus::valid) @@ -64,9 +62,12 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t current_request.endpoint = TCP_socket.remote_endpoint().address(); request_handler.HandleRequest(current_request, current_reply); - if (boost::iequals(current_request.connection, "close")) { + if (boost::iequals(current_request.connection, "close")) + { current_reply.headers.emplace_back("Connection", "close"); - } else { + } + else + { keep_alive = true; current_reply.headers.emplace_back("Connection", "keep-alive"); current_reply.headers.emplace_back("Keep-Alive", "timeout=5, max=512"); @@ -75,29 +76,29 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t // compress the result w/ gzip/deflate if requested switch (compression_type) { - case http::deflate_rfc1951: - // use deflate for compression - current_reply.headers.insert(current_reply.headers.begin(), - {"Content-Encoding", "deflate"}); - compressed_output = compress_buffers(current_reply.content, compression_type); - current_reply.set_size(static_cast(compressed_output.size())); - output_buffer = current_reply.headers_to_buffers(); - output_buffer.push_back(boost::asio::buffer(compressed_output)); - break; - case http::gzip_rfc1952: - // use gzip for compression - current_reply.headers.insert(current_reply.headers.begin(), - {"Content-Encoding", "gzip"}); - compressed_output = compress_buffers(current_reply.content, compression_type); - current_reply.set_size(static_cast(compressed_output.size())); - output_buffer = current_reply.headers_to_buffers(); - output_buffer.push_back(boost::asio::buffer(compressed_output)); - break; - case http::no_compression: - // don't use any compression - current_reply.set_uncompressed_size(); - output_buffer = current_reply.to_buffers(); - break; + case http::deflate_rfc1951: + // use deflate for compression + current_reply.headers.insert(current_reply.headers.begin(), + {"Content-Encoding", "deflate"}); + compressed_output = compress_buffers(current_reply.content, compression_type); + current_reply.set_size(static_cast(compressed_output.size())); + output_buffer = current_reply.headers_to_buffers(); + output_buffer.push_back(boost::asio::buffer(compressed_output)); + break; + case http::gzip_rfc1952: + // use gzip for compression + current_reply.headers.insert(current_reply.headers.begin(), + {"Content-Encoding", "gzip"}); + compressed_output = compress_buffers(current_reply.content, compression_type); + current_reply.set_size(static_cast(compressed_output.size())); + output_buffer = current_reply.headers_to_buffers(); + output_buffer.push_back(boost::asio::buffer(compressed_output)); + break; + case http::no_compression: + // don't use any compression + current_reply.set_uncompressed_size(); + output_buffer = current_reply.to_buffers(); + break; } // write result to stream boost::asio::async_write(TCP_socket, @@ -120,11 +121,11 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t { // we don't have a result yet, so continue reading TCP_socket.async_read_some( - boost::asio::buffer(incoming_data_buffer), - strand.wrap(boost::bind(&Connection::handle_read, - this->shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + boost::asio::buffer(incoming_data_buffer), + strand.wrap(boost::bind(&Connection::handle_read, + this->shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); } } @@ -133,12 +134,15 @@ void Connection::handle_write(const boost::system::error_code &error) { if (!error) { - if (keep_alive && processed_requests > 0) { + if (keep_alive && processed_requests > 0) + { --processed_requests; current_request = http::request(); request_parser = RequestParser(); this->start(); - } else { + } + else + { // Initiate graceful connection closure. handle_timeout(); } From 4fbf58adb398ac138e8fc7515767174b34f3206c Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Mon, 19 Aug 2019 17:57:09 +0300 Subject: [PATCH 5/7] Documented HTTP server 'keep-alive' support --- docs/http.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/http.md b/docs/http.md index ba6858ec7..ff4b24602 100644 --- a/docs/http.md +++ b/docs/http.md @@ -1,3 +1,8 @@ +# OSRM HTTP server + +Built-in HTTP server is a basic HTTP/1.0 server that supports 'keep-alive' extension. Persistent connections are limited to 512 requests per +connection and allow no more then 5 seconds between requests. + ## General options All OSRM HTTP requests use a common structure. From 9efcab21083e113d8eff40a1d717fa3231f8d8f8 Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Tue, 20 Aug 2019 12:04:58 +0300 Subject: [PATCH 6/7] Receive timeout should only be active for a second and following requests on a keep-alive connections. --- include/server/connection.hpp | 4 +++- src/server/connection.cpp | 39 +++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/include/server/connection.hpp b/include/server/connection.hpp index b47a2a04f..0680735f0 100644 --- a/include/server/connection.hpp +++ b/include/server/connection.hpp @@ -53,7 +53,9 @@ class Connection : public std::enable_shared_from_this void handle_write(const boost::system::error_code &e); /// Handle read timeout - void handle_timeout(); + void handle_timeout(boost::system::error_code); + + void handle_shutdown(); std::vector compress_buffers(const std::vector &uncompressed_data, const http::compression_type compression_type); diff --git a/src/server/connection.cpp b/src/server/connection.cpp index 521797194..8f31cb574 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -10,7 +10,6 @@ #include #include -#include #include namespace osrm @@ -35,10 +34,14 @@ void Connection::start() boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred))); - // init async timer - timer.cancel(); - timer.expires_from_now(boost::posix_time::seconds(keepalive_timeout)); - timer.async_wait(boost::bind(&Connection::handle_timeout, this->shared_from_this())); + if (keep_alive) + { + // Ok, we know it is not a first request, as we switched to keepalive + timer.cancel(); + timer.expires_from_now(boost::posix_time::seconds(keepalive_timeout)); + timer.async_wait(std::bind( + &Connection::handle_timeout, this->shared_from_this(), std::placeholders::_1)); + } } void Connection::handle_read(const boost::system::error_code &error, std::size_t bytes_transferred) @@ -48,6 +51,12 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t return; } + if (keep_alive) + { + timer.cancel(); + timer.expires_from_now(boost::posix_time::seconds(0)); + } + // no error detected, let's parse the request http::compression_type compression_type(http::no_compression); RequestParser::RequestStatus result; @@ -143,15 +152,27 @@ void Connection::handle_write(const boost::system::error_code &error) } else { - // Initiate graceful connection closure. - handle_timeout(); + handle_shutdown(); } } } -/// Handle completion of a write operation. -void Connection::handle_timeout() +/// Handle completion of a timeout timer.. +void Connection::handle_timeout(boost::system::error_code ec) { + // We can get there for 3 reasons: spurious wakeup by timer.cancel(), which should be ignored + // Slow client with a delayed _first_ request, which should be ignored too + // Absent next request during waiting time in the keepalive mode - should stop right there. + if (ec != boost::asio::error::operation_aborted) + { + TCP_socket.cancel(); + handle_shutdown(); + } +} + +void Connection::handle_shutdown() +{ + // Initiate graceful connection closure. boost::system::error_code ignore_error; TCP_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_error); } From 2462826c204ac1011e3ba8c861c6437a224477c5 Mon Sep 17 00:00:00 2001 From: Denis Chaplygin Date: Fri, 23 Aug 2019 13:07:17 +0300 Subject: [PATCH 7/7] Added changelog entry. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 275101745..baee7fcf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - ADDED: new waypoints parameter to the `route` plugin, enabling silent waypoints [#5345](https://github.com/Project-OSRM/osrm-backend/pull/5345) - ADDED: data timestamp information in the response (saved in new file `.osrm.timestamp`). [#5115](https://github.com/Project-OSRM/osrm-backend/issues/5115) - ADDED: new API parameter - `snapping=any|default` to allow snapping to previously unsnappable edges [#5361](https://github.com/Project-OSRM/osrm-backend/pull/5361) + - ADDED: keepalive support to the osrm-routed HTTP server [#5518](https://github.com/Project-OSRM/osrm-backend/pull/5518) - Routing: - CHANGED: allow routing past `barrier=arch` [#5352](https://github.com/Project-OSRM/osrm-backend/pull/5352) - CHANGED: default car weight was reduced to 2000 kg. [#5371](https://github.com/Project-OSRM/osrm-backend/pull/5371)