diff --git a/client_http.hpp b/client_http.hpp index 795dcd3..a9b6344 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -48,6 +48,18 @@ namespace SimpleWeb { Response(): content(&content_buffer) {} }; + class Config { + friend class ClientBase; + private: + Config() {} + public: + /// Set timeout on requests in seconds. Default value: 0 (no timeout). + size_t timeout=0; + }; + + /// Set before calling request + Config config; + std::shared_ptr request(const std::string& request_type, const std::string& path="/", boost::string_ref content="", const std::map& header=std::map()) { std::string corrected_path=path; @@ -67,12 +79,18 @@ namespace SimpleWeb { connect(); + auto timer=get_timeout_timer(); boost::asio::async_write(*socket, write_buffer, - [this, &content](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + [this, &content, timer](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + if(timer) + timer->cancel(); if(!ec) { if(!content.empty()) { + auto timer=get_timeout_timer(); boost::asio::async_write(*socket, boost::asio::buffer(content.data(), content.size()), - [this](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + [this, timer](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + if(timer) + timer->cancel(); if(ec) { socket=nullptr; throw boost::system::system_error(ec); @@ -114,8 +132,11 @@ namespace SimpleWeb { if(content_length>0) write_stream << content.rdbuf(); + auto timer=get_timeout_timer(); boost::asio::async_write(*socket, write_buffer, - [this](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + [this, timer](const boost::system::error_code &ec, size_t /*bytes_transferred*/) { + if(timer) + timer->cancel(); if(ec) { socket=nullptr; throw boost::system::system_error(ec); @@ -153,6 +174,22 @@ namespace SimpleWeb { virtual void connect()=0; + std::shared_ptr get_timeout_timer() { + if(config.timeout==0) + return nullptr; + + auto timer=std::make_shared(io_service); + timer->expires_from_now(boost::posix_time::seconds(config.timeout)); + timer->async_wait([this](const boost::system::error_code& ec) { + if(!ec) { + boost::system::error_code ec; + socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + socket->lowest_layer().close(); + } + }); + return timer; + } + void parse_response_header(const std::shared_ptr &response) const { std::string line; getline(response->content, line); @@ -184,8 +221,11 @@ namespace SimpleWeb { boost::asio::streambuf chunked_streambuf; + auto timer=get_timeout_timer(); boost::asio::async_read_until(*socket, response->content_buffer, "\r\n\r\n", - [this, &response, &chunked_streambuf](const boost::system::error_code& ec, size_t bytes_transferred) { + [this, &response, &chunked_streambuf, timer](const boost::system::error_code& ec, size_t bytes_transferred) { + if(timer) + timer->cancel(); if(!ec) { size_t num_additional_bytes=response->content_buffer.size()-bytes_transferred; @@ -195,9 +235,12 @@ namespace SimpleWeb { if(header_it!=response->header.end()) { auto content_length=stoull(header_it->second); if(content_length>num_additional_bytes) { + auto timer=get_timeout_timer(); boost::asio::async_read(*socket, response->content_buffer, boost::asio::transfer_exactly(content_length-num_additional_bytes), - [this](const boost::system::error_code& ec, size_t /*bytes_transferred*/) { + [this, timer](const boost::system::error_code& ec, size_t /*bytes_transferred*/) { + if(timer) + timer->cancel(); if(ec) { socket=nullptr; throw boost::system::system_error(ec); @@ -221,8 +264,11 @@ namespace SimpleWeb { } void request_read_chunked(const std::shared_ptr &response, boost::asio::streambuf &streambuf) { + auto timer=get_timeout_timer(); boost::asio::async_read_until(*socket, response->content_buffer, "\r\n", - [this, &response, &streambuf](const boost::system::error_code& ec, size_t bytes_transferred) { + [this, &response, &streambuf, timer](const boost::system::error_code& ec, size_t bytes_transferred) { + if(timer) + timer->cancel(); if(!ec) { std::string line; getline(response->content, line); @@ -251,9 +297,12 @@ namespace SimpleWeb { }; if((2+length)>num_additional_bytes) { + auto timer=get_timeout_timer(); boost::asio::async_read(*socket, response->content_buffer, boost::asio::transfer_exactly(2+length-num_additional_bytes), - [this, post_process](const boost::system::error_code& ec, size_t /*bytes_transferred*/) { + [this, post_process, timer](const boost::system::error_code& ec, size_t /*bytes_transferred*/) { + if(timer) + timer->cancel(); if(!ec) { post_process(); } diff --git a/client_https.hpp b/client_https.hpp index c6cd89b..0999d40 100644 --- a/client_https.hpp +++ b/client_https.hpp @@ -48,8 +48,11 @@ namespace SimpleWeb { boost::asio::ip::tcp::no_delay option(true); socket->lowest_layer().set_option(option); + auto timer=get_timeout_timer(); socket->async_handshake(boost::asio::ssl::stream_base::client, - [this](const boost::system::error_code& ec) { + [this, timer](const boost::system::error_code& ec) { + if(timer) + timer->cancel(); if(ec) { socket=nullptr; throw boost::system::system_error(ec); diff --git a/http_examples.cpp b/http_examples.cpp index 811e62e..ba3c608 100644 --- a/http_examples.cpp +++ b/http_examples.cpp @@ -157,7 +157,7 @@ int main() { auto r3=client.request("POST", "/json", json_string); cout << r3->content.rdbuf() << endl; - + server_thread.join(); return 0; diff --git a/server_http.hpp b/server_http.hpp index c274767..483ce34 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -212,8 +212,11 @@ namespace SimpleWeb { virtual void accept()=0; - std::shared_ptr set_timeout_on_socket(const std::shared_ptr &socket, long seconds) { - std::shared_ptr timer(new boost::asio::deadline_timer(*io_service)); + std::shared_ptr get_timeout_timer(const std::shared_ptr &socket, long seconds) { + if(seconds==0) + return nullptr; + + auto timer=std::make_shared(*io_service); timer->expires_from_now(boost::posix_time::seconds(seconds)); timer->async_wait([socket](const boost::system::error_code& ec){ if(!ec) { @@ -239,13 +242,11 @@ namespace SimpleWeb { } //Set timeout on the following boost::asio::async-read or write function - std::shared_ptr timer; - if(timeout_request>0) - timer=set_timeout_on_socket(socket, timeout_request); + auto timer=get_timeout_timer(socket, timeout_request); boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer](const boost::system::error_code& ec, size_t bytes_transferred) { - if(timeout_request>0) + if(timer) timer->cancel(); if(!ec) { //request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs: @@ -261,9 +262,7 @@ namespace SimpleWeb { auto it=request->header.find("Content-Length"); if(it!=request->header.end()) { //Set timeout on the following boost::asio::async-read or write function - std::shared_ptr timer; - if(timeout_content>0) - timer=set_timeout_on_socket(socket, timeout_content); + auto timer=get_timeout_timer(socket, timeout_content); unsigned long long content_length; try { content_length=stoull(it->second); @@ -278,14 +277,14 @@ namespace SimpleWeb { boost::asio::transfer_exactly(content_length-num_additional_bytes), [this, socket, request, timer] (const boost::system::error_code& ec, size_t /*bytes_transferred*/) { - if(timeout_content>0) + if(timer) timer->cancel(); if(!ec) find_resource(socket, request); }); } else { - if(timeout_content>0) + if(timer) timer->cancel(); find_resource(socket, request); } @@ -362,15 +361,13 @@ namespace SimpleWeb { std::function::Response>, std::shared_ptr::Request>)>& resource_function) { //Set timeout on the following boost::asio::async-read or write function - std::shared_ptr timer; - if(timeout_content>0) - timer=set_timeout_on_socket(socket, timeout_content); + auto timer=get_timeout_timer(socket, timeout_content); auto response=std::shared_ptr(new Response(socket), [this, request, timer](Response *response_ptr) { auto response=std::shared_ptr(response_ptr); send(response, [this, response, request, timer](const boost::system::error_code& ec) { if(!ec) { - if(timeout_content>0) + if(timer) timer->cancel(); float http_version; try { @@ -419,7 +416,7 @@ namespace SimpleWeb { void accept() { //Create new socket for this connection //Shared_ptr is used to pass temporary objects to the asynchronous functions - std::shared_ptr socket(new HTTP(*io_service)); + auto socket=std::make_shared(*io_service); acceptor->async_accept(*socket, [this, socket](const boost::system::error_code& ec){ //Immediately start accepting a new connection (if io_service hasn't been stopped) diff --git a/server_https.hpp b/server_https.hpp index 67e7718..bbd7f79 100644 --- a/server_https.hpp +++ b/server_https.hpp @@ -28,7 +28,7 @@ namespace SimpleWeb { void accept() { //Create new socket for this connection //Shared_ptr is used to pass temporary objects to the asynchronous functions - std::shared_ptr socket(new HTTPS(*io_service, context)); + auto socket=std::make_shared(*io_service, context); acceptor->async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code& ec) { //Immediately start accepting a new connection (if io_service hasn't been stopped) @@ -41,12 +41,10 @@ namespace SimpleWeb { socket->lowest_layer().set_option(option); //Set timeout on the following boost::asio::ssl::stream::async_handshake - std::shared_ptr timer; - if(timeout_request>0) - timer=set_timeout_on_socket(socket, timeout_request); + auto timer=get_timeout_timer(socket, timeout_request); (*socket).async_handshake(boost::asio::ssl::stream_base::server, [this, socket, timer] (const boost::system::error_code& ec) { - if(timeout_request>0) + if(timer) timer->cancel(); if(!ec) read_request_and_content(socket);