From a6154c7c5bb3901727c1511f7f7586c0f063e572 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 20 Feb 2015 11:14:39 +0100 Subject: [PATCH] Added custom response stream and possibility to flush response to clients synchronously and asynchronously. Various speed ups, including reduced use of regex and preprocessing of regex objects. boost::asio::ip::tcp::no_delay is now turned on for both Client and Server. Note: Not backward compatible with earlier versions. --- CMakeLists.txt | 2 +- README.md | 13 +- client_http.hpp | 70 +++++----- client_https.hpp | 3 + http_examples.cpp | 47 ++++--- https_examples.cpp | 47 ++++--- server_http.hpp | 310 ++++++++++++++++++++++++++++++++------------- server_https.hpp | 7 +- 8 files changed, 331 insertions(+), 168 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 115e0df..500d2bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ else() endif() #Only tested with versions 1.55 and 1.56 -find_package(Boost 1.41.0 COMPONENTS system thread REQUIRED) +find_package(Boost 1.41.0 COMPONENTS system thread coroutine context REQUIRED) message("Boost include dir: ${Boost_INCLUDE_DIR}") message("Boost libraries: ${Boost_LIBRARIES}") include_directories(${Boost_INCLUDE_DIR}) diff --git a/README.md b/README.md index cd7a57c..1b0a34c 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,19 @@ See also https://github.com/eidheim/Simple-WebSocket-Server for an easy way to m * HTTP persistent connection (for HTTP/1.1) * Client supports chunked transfer encoding * Timeouts, if any of Server::timeout_request and Server::timeout_content are >0 (default: Server::timeout_request=5 seconds, and Server::timeout_content=300 seconds) -* Simple way to add REST resources using regex for path, and anonymous functions +* Simple way to add REST resources using for instance regex for path, and anonymous functions +* Possibility to flush response to clients both synchronously (Server::flush) and asynchronously (Server::async_flush). ###Usage +Note: newest version is NOT backward compatible with earlier versions. + See http_examples.cpp or https_examples.cpp for example usage. See particularly the JSON-POST (using Boost.PropertyTree) and the GET /match/[number] examples, which are most relevant. +The default_resource includes example use of Server::flush. Note that Server::async_flush might be slightly slower than Server::flush unless you need to process computationally expensive tasks while simultaneously sending large datasets to a client. + ### Dependencies Boost C++ libraries must be installed, go to http://www.boost.org for download and instructions. @@ -33,7 +38,7 @@ Compile with a C++11 compiler supporting regex (for instance g++ 4.9): On Linux using g++: add `-pthread` -Note: added `-lboost_thread` to make the json-example thread safe. On some systems you might have to use `-lboost_thread-mt` instead. +Note: added `-lboost_thread` to make the json-example thread safe. Also added `-lboost_coroutine -lboost_context` to make synchronous and asynchronous flushing of response stream work. On some systems you might have to use postfix `-mt` to link to these libraries. You can now also compile using CMake and make: @@ -44,7 +49,7 @@ make #### HTTP -`g++ -O3 -std=c++11 http_examples.cpp -lboost_system -lboost_thread -o http_examples` +`g++ -O3 -std=c++11 http_examples.cpp -lboost_system -lboost_thread -lboost_coroutine -lboost_context -o http_examples` Then to run the server and client examples: `./http_examples` @@ -52,7 +57,7 @@ Also, direct your favorite browser to for instance http://localhost:8080/ #### HTTPS -`g++ -O3 -std=c++11 https_examples.cpp -lboost_system -lboost_thread -lssl -lcrypto -o https_examples` +`g++ -O3 -std=c++11 https_examples.cpp -lboost_system -lboost_thread -lboost_coroutine -lboost_context -lssl -lcrypto -o https_examples` Before running the server, an RSA private key (server.key) and an SSL certificate (server.crt) must be created. Follow, for instance, the instructions given here (for a self-signed certificate): http://www.akadia.com/services/ssh_test_certificate.html diff --git a/client_http.hpp b/client_http.hpp index a840408..1fe5c1f 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -5,8 +5,6 @@ #include #include -#include -#include #include namespace SimpleWeb { @@ -62,13 +60,13 @@ namespace SimpleWeb { try { connect(); - + boost::asio::write(*socket, write_buffer); - + size_t bytes_transferred = boost::asio::read_until(*socket, response->content_buffer, "\r\n\r\n"); - + size_t num_additional_bytes=response->content_buffer.size()-bytes_transferred; - + parse_response_header(response, response->content); if(response->header.count("Content-Length")>0) { @@ -130,49 +128,41 @@ namespace SimpleWeb { ClientBase(const std::string& host_port, unsigned short default_port) : asio_resolver(asio_io_service), socket_error(false) { - std::regex e("^([^:/]+):?([0-9]*)$"); - - std::smatch sm; - - if(std::regex_match(host_port, sm, e)) { - host=sm[1]; + size_t host_end=host_port.find(':'); + if(host_end==std::string::npos) { + host=host_port; port=default_port; - if(sm[2]!="") - port=(unsigned short)std::stoul(sm[2]); - asio_endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port); } else { - throw std::invalid_argument("Error parsing host_port"); + host=host_port.substr(0, host_end); + port=(unsigned short)stoul(host_port.substr(host_end+1)); } + + asio_endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port); } virtual void connect()=0; - void parse_response_header(std::shared_ptr response, std::istream& stream) const { - std::smatch sm; - - //Parse the first line + void parse_response_header(std::shared_ptr response, std::istream& stream) const { std::string line; getline(stream, line); - line.pop_back(); + size_t version_end=line.find(' '); + if(version_end!=std::string::npos) { + response->http_version=line.substr(5, version_end-5); + response->status_code=line.substr(version_end+1, line.size()-version_end-2); + + getline(stream, line); + size_t param_end=line.find(':'); + while(param_end!=std::string::npos) { + size_t value_start=param_end+1; + if(line[value_start]==' ') + value_start++; + + response->header[line.substr(0, param_end)]=line.substr(value_start, line.size()-value_start-1); - std::regex e("^HTTP/([^ ]*) (.*)$"); - if(std::regex_match(line, sm, e)) { - response->http_version=sm[1]; - response->status_code=sm[2]; - - e="^([^:]*): ?(.*)$"; - //Parse the rest of the header - bool matched; - do { getline(stream, line); - line.pop_back(); - matched=std::regex_match(line, sm, e); - if(matched) { - response->header[sm[1]]=sm[2]; - } - - } while(matched==true); + param_end=line.find(':'); + } } } }; @@ -187,13 +177,17 @@ namespace SimpleWeb { public: Client(const std::string& server_port_path) : ClientBase::ClientBase(server_port_path, 80) { socket=std::make_shared(asio_io_service); - }; + } private: void connect() { if(socket_error || !socket->is_open()) { boost::asio::ip::tcp::resolver::query query(host, std::to_string(port)); boost::asio::connect(*socket, asio_resolver.resolve(query)); + + boost::asio::ip::tcp::no_delay option(true); + socket->set_option(option); + socket_error=false; } } diff --git a/client_https.hpp b/client_https.hpp index adc53a1..f6fb865 100644 --- a/client_https.hpp +++ b/client_https.hpp @@ -38,6 +38,9 @@ namespace SimpleWeb { boost::asio::ip::tcp::resolver::query query(host, std::to_string(port)); boost::asio::connect(socket->lowest_layer(), asio_resolver.resolve(query)); + boost::asio::ip::tcp::no_delay option(true); + socket->lowest_layer().set_option(option); + socket->handshake(boost::asio::ssl::stream_base::client); socket_error=false; diff --git a/http_examples.cpp b/http_examples.cpp index b7cedbb..1767f64 100644 --- a/http_examples.cpp +++ b/http_examples.cpp @@ -10,19 +10,20 @@ #include using namespace std; -using namespace SimpleWeb; //Added for the json-example: using namespace boost::property_tree; +typedef SimpleWeb::Server HttpServer; +typedef SimpleWeb::Client HttpClient; + int main() { //HTTP-server at port 8080 using 4 threads - Server server(8080, 4); + HttpServer server(8080, 4); - //Add resources using regular expression for path, a method-string, and an anonymous function + //Add resources using path- and method-string, and an anonymous function //POST-example for the path /string, responds the posted string - // If C++14: use 'auto' instead of 'shared_ptr::Request>' - server.resource["^/string/?$"]["POST"]=[](ostream& response, shared_ptr::Request> request) { - //Retrieve string from istream (*request.content) + server.resource["/string"]["POST"]=[](HttpServer::Response& response, shared_ptr request) { + //Retrieve string from istream (request->content) stringstream ss; request->content >> ss.rdbuf(); string content=ss.str(); @@ -38,7 +39,7 @@ int main() { // "lastName": "Smith", // "age": 25 //} - server.resource["^/json/?$"]["POST"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["/json"]["POST"]=[](HttpServer::Response& response, shared_ptr request) { try { ptree pt; read_json(request->content, pt); @@ -54,7 +55,7 @@ int main() { //GET-example for the path /info //Responds with request-information - server.resource["^/info/?$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["/info"]["GET"]=[](HttpServer::Response& response, shared_ptr request) { stringstream content_stream; content_stream << "

Request:

"; content_stream << request->method << " " << request->path << " HTTP/" << request->http_version << "
"; @@ -68,9 +69,10 @@ int main() { response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf(); }; - //GET-example for the path /match/[number], responds with the matched string in path (number) + //GET-example for the path /match/[number] using regex, responds with the matched string in path (number) + //The 'r' at the beginning of the path-string indicates that the following string is a regular expression //For instance a request GET /match/123 will receive: 123 - server.resource["^/match/([0-9]+)/?$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["r^/match/([0-9]+)/?$"]["GET"]=[](HttpServer::Response& response, shared_ptr request) { string number=request->path_match[1]; response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; }; @@ -79,10 +81,10 @@ int main() { //Will respond with content in the web/-directory, and its subdirectories. //Default file: index.html //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server - server.default_resource["^/?(.*)$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { - string filename="web/"; + server.default_resource["GET"]=[](HttpServer::Response& response, shared_ptr request) { + string filename="web"; - string path=request->path_match[1]; + string path=request->path; //Replace all ".." with "." (so we can't leave the web-directory) size_t pos; @@ -106,8 +108,17 @@ int main() { ifs.seekg(0, ios::beg); - //The file-content is copied to the response-stream. Should not be used for very large files. - response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n" << ifs.rdbuf(); + response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n"; + + //read and send 128 KB at a time + size_t buffer_size=131072; + vector buffer(buffer_size); + stringstream ss; + size_t read_length; + while((read_length=ifs.read(&buffer[0], buffer_size).gcount())>0) { + ss.write(&buffer[0], read_length); + response << ss.rdbuf() << HttpServer::flush; + } ifs.close(); } @@ -126,7 +137,7 @@ int main() { this_thread::sleep_for(chrono::seconds(1)); //Client examples - Client client("localhost:8080"); + HttpClient client("localhost:8080"); auto r1=client.request("GET", "/match/123"); cout << r1->content.rdbuf() << endl; @@ -138,8 +149,8 @@ int main() { ss.str(json); auto r3=client.request("POST", "/json", ss); cout << r3->content.rdbuf() << endl; - + server_thread.join(); return 0; -} +} \ No newline at end of file diff --git a/https_examples.cpp b/https_examples.cpp index 1011129..a3a7c7a 100644 --- a/https_examples.cpp +++ b/https_examples.cpp @@ -10,19 +10,20 @@ #include using namespace std; -using namespace SimpleWeb; //Added for the json-example: using namespace boost::property_tree; +typedef SimpleWeb::Server HttpsServer; +typedef SimpleWeb::Client HttpsClient; + int main() { //HTTPS-server at port 8080 using 4 threads - Server server(8080, 4, "server.crt", "server.key"); + HttpsServer server(8080, 1, "server.crt", "server.key"); - //Add resources using regular expression for path, a method-string, and an anonymous function + //Add resources using path- and method-string, and an anonymous function //POST-example for the path /string, responds the posted string - // If C++14: use 'auto' instead of 'shared_ptr::Request>' - server.resource["^/string/?$"]["POST"]=[](ostream& response, shared_ptr::Request> request) { - //Retrieve string from istream (*request.content) + server.resource["/string"]["POST"]=[](HttpsServer::Response& response, shared_ptr request) { + //Retrieve string from istream (request->content) stringstream ss; request->content >> ss.rdbuf(); string content=ss.str(); @@ -38,13 +39,13 @@ int main() { // "lastName": "Smith", // "age": 25 //} - server.resource["^/json/?$"]["POST"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["/json"]["POST"]=[](HttpsServer::Response& response, shared_ptr request) { try { ptree pt; read_json(request->content, pt); string name=pt.get("firstName")+" "+pt.get("lastName"); - + response << "HTTP/1.1 200 OK\r\nContent-Length: " << name.length() << "\r\n\r\n" << name; } catch(exception& e) { @@ -54,7 +55,7 @@ int main() { //GET-example for the path /info //Responds with request-information - server.resource["^/info/?$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["/info"]["GET"]=[](HttpsServer::Response& response, shared_ptr request) { stringstream content_stream; content_stream << "

Request:

"; content_stream << request->method << " " << request->path << " HTTP/" << request->http_version << "
"; @@ -68,9 +69,10 @@ int main() { response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf(); }; - //GET-example for the path /match/[number], responds with the matched string in path (number) + //GET-example for the path /match/[number] using regex, responds with the matched string in path (number) + //The 'r' at the beginning of the path-string indicates that the string is a regular expression //For instance a request GET /match/123 will receive: 123 - server.resource["^/match/([0-9]+)/?$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { + server.resource["r^/match/([0-9]+)/?$"]["GET"]=[](HttpsServer::Response& response, shared_ptr request) { string number=request->path_match[1]; response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number; }; @@ -79,10 +81,10 @@ int main() { //Will respond with content in the web/-directory, and its subdirectories. //Default file: index.html //Can for instance be used to retrieve an HTML 5 client that uses REST-resources on this server - server.default_resource["^/?(.*)$"]["GET"]=[](ostream& response, shared_ptr::Request> request) { - string filename="web/"; + server.default_resource["GET"]=[](HttpsServer::Response& response, shared_ptr request) { + string filename="web"; - string path=request->path_match[1]; + string path=request->path; //Replace all ".." with "." (so we can't leave the web-directory) size_t pos; @@ -106,8 +108,17 @@ int main() { ifs.seekg(0, ios::beg); - //The file-content is copied to the response-stream. Should not be used for very large files. - response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n" << ifs.rdbuf(); + response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n"; + + //read and send 128 KB at a time + size_t buffer_size=131072; + vector buffer(buffer_size); + stringstream ss; + size_t read_length; + while((read_length=ifs.read(&buffer[0], buffer_size).gcount())>0) { + ss.write(&buffer[0], read_length); + response << ss.rdbuf() << HttpsServer::flush; + } ifs.close(); } @@ -127,7 +138,7 @@ int main() { //Client examples //Second Client() parameter set to false: no certificate verification - Client client("localhost:8080", false); + HttpsClient client("localhost:8080", false); auto r1=client.request("GET", "/match/123"); cout << r1->content.rdbuf() << endl; @@ -143,4 +154,4 @@ int main() { server_thread.join(); return 0; -} +} \ No newline at end of file diff --git a/server_http.hpp b/server_http.hpp index 888b4d2..37fb73d 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -2,15 +2,117 @@ #define SERVER_HTTP_HPP #include +#include #include #include #include +#include namespace SimpleWeb { template class ServerBase { public: + class Response { + friend class ServerBase; + private: + std::shared_ptr strand; + + boost::asio::yield_context& yield; + + boost::asio::streambuf streambuf; + + std::shared_ptr socket; + + std::shared_ptr async_timer; + + std::shared_ptr async_writing; + std::shared_ptr async_waiting; + + Response(boost::asio::io_service& io_service, std::shared_ptr socket, std::shared_ptr strand, + boost::asio::yield_context& yield): + strand(strand), yield(yield), socket(socket), async_timer(new boost::asio::deadline_timer(io_service)), + async_writing(new bool(false)), async_waiting(new bool(false)), stream(&streambuf) {} + + void async_flush(std::function callback=nullptr) { + if(!callback && !socket->lowest_layer().is_open()) { + if(*async_waiting) + async_timer->cancel(); + throw std::runtime_error("Broken pipe."); + } + + std::shared_ptr write_buffer(new boost::asio::streambuf); + std::ostream response(write_buffer.get()); + response << stream.rdbuf(); + + //Wait until previous async_flush is finished + strand->dispatch([this](){ + if(*async_writing) { + *async_waiting=true; + try { + async_timer->async_wait(yield); + } + catch(std::exception& e) { + } + *async_waiting=false; + } + }); + + *async_writing=true; + + auto socket_=this->socket; + auto async_writing_=this->async_writing; + auto async_timer_=this->async_timer; + auto async_waiting_=this->async_waiting; + + boost::asio::async_write(*socket, *write_buffer, + strand->wrap([socket_, write_buffer, callback, async_writing_, async_timer_, async_waiting_] + (const boost::system::error_code& ec, size_t bytes_transferred) { + *async_writing_=false; + if(*async_waiting_) + async_timer_->cancel(); + if(callback) + callback(ec); + })); + } + + void flush() { + boost::asio::streambuf write_buffer; + std::ostream response(&write_buffer); + response << stream.rdbuf(); + + boost::asio::async_write(*socket, write_buffer, yield); + } + + public: + std::ostream stream; + + template + Response& operator<<(const T& t) { + stream << t; + return *this; + } + + Response& operator<<(std::ostream& (*manip)(std::ostream&)) { + stream << manip; + return *this; + } + + Response& operator<<(Response& (*manip)(Response&)) { + return manip(*this); + } + }; + + static Response& async_flush(Response& r) { + r.async_flush(); + return r; + } + + static Response& flush(Response& r) { + r.flush(); + return r; + } + class Request { friend class ServerBase; public: @@ -23,41 +125,45 @@ namespace SimpleWeb { std::smatch path_match; private: - Request(): content(&content_buffer) {} + Request(): content(&streambuf) {} - boost::asio::streambuf content_buffer; + boost::asio::streambuf streambuf; }; - typedef std::map::Request>)> > > resource_type; + std::unordered_map::Response&, std::shared_ptr::Request>)> > > resource; - resource_type resource; + std::unordered_map::Response&, std::shared_ptr::Request>)> > default_resource; - resource_type default_resource; + private: + std::vector::Response&, std::shared_ptr::Request>)> > > > regex_resource; + public: void start() { - //All resources with default_resource at the end of vector - //Used in the respond-method - all_resources.clear(); - for(auto it=resource.begin(); it!=resource.end();it++) { - all_resources.push_back(it); - } - for(auto it=default_resource.begin(); it!=default_resource.end();it++) { - all_resources.push_back(it); + //Move the resources with regular expressions to regex_resource for more efficient request processing + for(auto it=resource.begin();it!=resource.end();) { + if(it->first.size()>0 && it->first[0]=='r') { + regex_resource.emplace_back(std::regex(it->first.substr(1)), std::move(it->second)); + it=resource.erase(it); + } + else + it++; } - accept(); + accept(); //If num_threads>1, start m_io_service.run() in (num_threads-1) threads for thread-pooling threads.clear(); for(size_t c=1;c all_resources; ServerBase(unsigned short port, size_t num_threads, size_t timeout_request, size_t timeout_send_or_receive) : - endpoint(boost::asio::ip::tcp::v4(), port), acceptor(m_io_service, endpoint), num_threads(num_threads), + endpoint(boost::asio::ip::tcp::v4(), port), acceptor(io_service, endpoint), num_threads(num_threads), timeout_request(timeout_request), timeout_content(timeout_send_or_receive) {} virtual void accept()=0; std::shared_ptr set_timeout_on_socket(std::shared_ptr socket, size_t seconds) { - std::shared_ptr timer(new boost::asio::deadline_timer(m_io_service)); + std::shared_ptr timer(new boost::asio::deadline_timer(io_service)); timer->expires_from_now(boost::posix_time::seconds(seconds)); timer->async_wait([socket](const boost::system::error_code& ec){ if(!ec) { - socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both); + boost::system::error_code ec; + socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); socket->lowest_layer().close(); } }); return timer; - } + } + + std::shared_ptr set_timeout_on_socket(std::shared_ptr socket, std::shared_ptr strand, size_t seconds) { + std::shared_ptr timer(new boost::asio::deadline_timer(io_service)); + timer->expires_from_now(boost::posix_time::seconds(seconds)); + timer->async_wait(strand->wrap([socket](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 read_request_and_content(std::shared_ptr socket) { //Create new streambuf (Request::streambuf) for async_read_until() @@ -110,8 +226,8 @@ namespace SimpleWeb { std::shared_ptr timer; if(timeout_request>0) timer=set_timeout_on_socket(socket, timeout_request); - - boost::asio::async_read_until(*socket, request->content_buffer, "\r\n\r\n", + + 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) timer->cancel(); @@ -120,7 +236,7 @@ namespace SimpleWeb { //"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" //The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the //streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). - size_t num_additional_bytes=request->content_buffer.size()-bytes_transferred; + size_t num_additional_bytes=request->streambuf.size()-bytes_transferred; parse_request(request, request->content); @@ -131,84 +247,101 @@ namespace SimpleWeb { if(timeout_content>0) timer=set_timeout_on_socket(socket, timeout_content); - boost::asio::async_read(*socket, request->content_buffer, + boost::asio::async_read(*socket, request->streambuf, boost::asio::transfer_exactly(stoull(request->header["Content-Length"])-num_additional_bytes), [this, socket, request, timer] (const boost::system::error_code& ec, size_t bytes_transferred) { if(timeout_content>0) timer->cancel(); if(!ec) - write_response(socket, request); + find_resource(socket, request); }); } - else { - write_response(socket, request); + else { + find_resource(socket, request); } } }); } void parse_request(std::shared_ptr request, std::istream& stream) const { - std::regex e("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$"); - - std::smatch sm; - - //First parse request method, path, and HTTP-version from the first line std::string line; getline(stream, line); - line.pop_back(); - if(std::regex_match(line, sm, e)) { - request->method=sm[1]; - request->path=sm[2]; - request->http_version=sm[3]; + size_t method_end=line.find(' '); + size_t path_end=line.find(' ', method_end+1); + if(method_end!=std::string::npos && path_end!=std::string::npos) { + request->method=line.substr(0, method_end); + request->path=line.substr(method_end+1, path_end-method_end-1); + request->http_version=line.substr(path_end+6, line.size()-path_end-7); + + getline(stream, line); + size_t param_end=line.find(':'); + while(param_end!=std::string::npos) { + size_t value_start=param_end+1; + if(line[value_start]==' ') + value_start++; + + request->header[line.substr(0, param_end)]=line.substr(value_start, line.size()-value_start-1); - bool matched; - e="^([^:]*): ?(.*)$"; - //Parse the rest of the header - do { getline(stream, line); - line.pop_back(); - matched=std::regex_match(line, sm, e); - if(matched) { - request->header[sm[1]]=sm[2]; - } - - } while(matched==true); + param_end=line.find(':'); + } } } - void write_response(std::shared_ptr socket, std::shared_ptr request) { - //Find path- and method-match, and generate response - for(auto res_it: all_resources) { - std::regex e(res_it->first); - std::smatch sm_res; - if(std::regex_match(request->path, sm_res, e)) { - if(res_it->second.count(request->method)>0) { - request->path_match=move(sm_res); - - std::shared_ptr write_buffer(new boost::asio::streambuf); - std::ostream response(write_buffer.get()); - res_it->second[request->method](response, request); - - //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); - - //Capture write_buffer in lambda so it is not destroyed before async_write is finished - boost::asio::async_write(*socket, *write_buffer, - [this, socket, request, write_buffer, timer] - (const boost::system::error_code& ec, size_t bytes_transferred) { - if(timeout_content>0) - timer->cancel(); - //HTTP persistent connection (HTTP 1.1): - if(!ec && stof(request->http_version)>1.05) - read_request_and_content(socket); - }); + void find_resource(std::shared_ptr socket, std::shared_ptr request) { + //Find path- and method-match, and call write_response + auto it_path=resource.find(request->path); + if(it_path!=resource.end()) { + auto it_method=it_path->second.find(request->method); + if(it_method!=it_path->second.end()) { + write_response(socket, request, it_method->second); + return; + } + } + for(auto& res: regex_resource) { + auto it_method=res.second.find(request->method); + if(it_method!=res.second.end()) { + std::smatch sm_res; + if(std::regex_match(request->path, sm_res, res.first)) { + request->path_match=std::move(sm_res); + write_response(socket, request, it_method->second); return; } } } + auto it_method=default_resource.find(request->method); + if(it_method!=default_resource.end()) { + write_response(socket, request, it_method->second); + } + } + + void write_response(std::shared_ptr socket, std::shared_ptr request, + std::function::Response&, std::shared_ptr::Request>)>& resource_function) { + std::shared_ptr strand(new boost::asio::strand(io_service)); + + //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, strand, timeout_content); + + boost::asio::spawn(*strand, [this, strand, &resource_function, socket, request, timer](boost::asio::yield_context yield) { + Response response(io_service, socket, strand, yield); + + try { + resource_function(response, request); + } + catch(std::exception& e) { + return; + } + + response.async_flush([this, socket, request, timer](const boost::system::error_code& ec) { + if(timeout_content>0) + timer->cancel(); + if(!ec && stof(request->http_version)>1.05) + read_request_and_content(socket); + }); + }); } }; @@ -227,13 +360,16 @@ 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(m_io_service)); - - acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) { + std::shared_ptr socket(new HTTP(io_service)); + + acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec){ //Immediately start accepting a new connection accept(); - + if(!ec) { + boost::asio::ip::tcp::no_delay option(true); + socket->set_option(option); + read_request_and_content(socket); } }); diff --git a/server_https.hpp b/server_https.hpp index a3fd67e..097ac25 100644 --- a/server_https.hpp +++ b/server_https.hpp @@ -28,13 +28,16 @@ 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(m_io_service, context)); + std::shared_ptr socket(new HTTPS(io_service, context)); acceptor.async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code& ec) { //Immediately start accepting a new connection accept(); - + if(!ec) { + boost::asio::ip::tcp::no_delay option(true); + 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)