classserver{public:server(constserver&)=delete;server&operator=(constserver&)=delete;// Construct the server to listen on the specified TCP address and// port, and serve up files from the given directory.explicitserver(conststd::string&address,conststd::string&port,conststd::string&doc_root);// Run the server's io_context loop.voidrun();private:// Perform an asynchronous accept operation.voidschedule_accept();// Wait for a request to stop the server.voidschedule_await_stop();// The io_context used to perform asynchronous operations.asio::io_contextio_context_;// Executor for the io contextasio::thread_poolpool_;// The signal_set is used to register for process termination// notifications.asio::signal_setsignals_;// Acceptor used to listen for incoming connections.asio::ip::tcp::acceptoracceptor_;// The connection manager which owns all live connections.connection_managerconnection_manager_;// The handler for all incoming requests.request_handlerrequest_handler_;// Helper class to setup signalsvoidsetup_signals();// Helper class to setup acceptorvoidsetup_acceptor(conststd::string&address,conststd::string&port);};
voidserver::setup_acceptor(conststd::string&address,conststd::string&port){// Open the acceptor with the option to// reuse the address (i.e. SO_REUSEADDR).asio::ip::tcp::resolverresolver(io_context_);asio::ip::tcp::endpointendpoint=*resolver.resolve(address,port).begin();acceptor_.open(endpoint.protocol());acceptor_.set_option(asio::socket_base::reuse_address(true));acceptor_.bind(endpoint);acceptor_.listen();schedule_accept();}
1 2 3 4 5 6 7 8 9101112
voidserver::setup_signals(){// 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.signals_.add(SIGINT);signals_.add(SIGTERM);#if defined(SIGQUIT)signals_.add(SIGQUIT);#endif // defined(SIGQUIT)schedule_await_stop();}
1 2 3 4 5 6 7 8 910
voidserver::run(){// The io_context::run() call will block until all asynchronous// operations have finished. While the server is running, there is// always at least one asynchronous operation outstanding: the// asynchronous accept call waiting for new incoming connections.for(size_ti=0;i<std::thread::hardware_concurrency();++i){asio::post(pool_,[this]{io_context_.run();});}pool_.join();}
1 2 3 4 5 6 7 8 91011121314151617
voidserver::schedule_accept(){acceptor_.async_accept([this](std::error_codeec,asio::ip::tcp::socketsocket){// Check whether the server was stopped by a signal before// this completion handler had a chance to run.if(!acceptor_.is_open()){return;}if(!ec){connection_manager_.start(std::make_shared<connection>(std::move(socket),connection_manager_,request_handler_));}schedule_accept();});}
123456789
voidserver::schedule_await_stop(){signals_.async_wait([this](std::error_code/*ec*/,int/*signo*/){// The server is stopped by cancelling all outstanding// asynchronous operations. Once all operations have finished// the io_context::run() call will exit.acceptor_.close();connection_manager_.stop_all();});}
// Manages open connections so that they may be cleanly stopped when// the server needs to shut down.classconnection_manager{public:connection_manager(constconnection_manager&)=delete;connection_manager&operator=(constconnection_manager&)=delete;// Construct a connection manager.connection_manager();// Add the specified connection to the manager and start it.voidstart(connection_ptrc);// Stop the specified connection.voidstop(connection_ptrc);// Stop all connections.voidstop_all();private:// The managed connections.std::set<connection_ptr>connections_;};
classconnection:publicstd::enable_shared_from_this<connection>{public:connection(constconnection&)=delete;connection&operator=(constconnection&)=delete;// Construct a connection with the given socket.explicitconnection(asio::ip::tcp::socketsocket,connection_manager&manager,request_handler&handler);// Start the first asynchronous operation for the connection.voidstart();// Stop all asynchronous operations associated with the connection.voidstop();private:// Perform an asynchronous read operation.voidschedule_read();// Perform an asynchronous write operation.voidschedule_write();// Socket for the connection.asio::ip::tcp::socketsocket_;// The manager for this connection.connection_manager&connection_manager_;// The handler used to process the incoming request.request_handler&request_handler_;// Buffer for incoming data.std::array<char,8192>buffer_;// The incoming request.requestrequest_;// The parser for the incoming request.request_parserrequest_parser_;// The reply to be sent back to the client.replyreply_;};
voidconnection::schedule_read(){autoself(shared_from_this());socket_.async_read_some(asio::buffer(buffer_),[this,self](std::error_codeec,std::size_tbytes_transferred){if(!ec){// Parse as an HTTP requestrequest_parser::result_typeresult;std::tie(result,std::ignore)=request_parser_.parse(request_,buffer_.data(),buffer_.data()+bytes_transferred);// Generate reply with the request handler and write itif(result==request_parser::good){request_handler_.handle_request(request_,reply_);schedule_write();}elseif(result==request_parser::bad){reply_=reply::stock_reply(reply::bad_request);schedule_write();}else{schedule_read();}}elseif(ec!=asio::error::operation_aborted){connection_manager_.stop(shared_from_this());}});}
structreply{// The status of the reply.enumstatus_type{ok=200,created=201,accepted=202,no_content=204,multiple_choices=300,moved_permanently=301,moved_temporarily=302,not_modified=304,bad_request=400,unauthorized=401,forbidden=403,not_found=404,internal_server_error=500,not_implemented=501,bad_gateway=502,service_unavailable=503}status;// The headers to be included in the reply.std::vector<header>headers;// The content to be sent in the reply.std::stringcontent;// Convert the reply into a vector of buffers. The buffers do not// own the underlying memory blocks, therefore the reply object// must remain valid and not be changed until the write operation// has completed.std::vector<asio::const_buffer>to_buffers();// Get a stock reply.staticreplystock_reply(status_typestatus);};
namespacestatus_strings{conststd::stringok="HTTP/1.0 200 OK\r\n";conststd::stringcreated="HTTP/1.0 201 Created\r\n";conststd::stringaccepted="HTTP/1.0 202 Accepted\r\n";conststd::stringno_content="HTTP/1.0 204 No Content\r\n";conststd::stringmultiple_choices="HTTP/1.0 300 Multiple Choices\r\n";conststd::stringmoved_permanently="HTTP/1.0 301 Moved Permanently\r\n";conststd::stringmoved_temporarily="HTTP/1.0 302 Moved Temporarily\r\n";conststd::stringnot_modified="HTTP/1.0 304 Not Modified\r\n";conststd::stringbad_request="HTTP/1.0 400 Bad Request\r\n";conststd::stringunauthorized="HTTP/1.0 401 Unauthorized\r\n";conststd::stringforbidden="HTTP/1.0 403 Forbidden\r\n";conststd::stringnot_found="HTTP/1.0 404 Not Found\r\n";conststd::stringinternal_server_error="HTTP/1.0 500 Internal Server Error\r\n";conststd::stringnot_implemented="HTTP/1.0 501 Not Implemented\r\n";conststd::stringbad_gateway="HTTP/1.0 502 Bad Gateway\r\n";conststd::stringservice_unavailable="HTTP/1.0 503 Service Unavailable\r\n";
namespacestock_replies{namespaceas_string{constexprcharok[]="";constexprcharcreated[]="<html>""<head><title>Created</title></head>""<body><h1>201 Created</h1></body>""</html>";constexprcharaccepted[]="<html>""<head><title>Accepted</title></head>""<body><h1>202 Accepted</h1></body>""</html>";constexprcharno_content[]="<html>""<head><title>No Content</title></head>""<body><h1>204 Content</h1></body>""</html>";constexprcharmultiple_choices[]="<html>""<head><title>Multiple Choices</title></head>""<body><h1>300 Multiple Choices</h1></body>""</html>";constexprcharmoved_permanently[]="<html>""<head><title>Moved Permanently</title></head>""<body><h1>301 Moved Permanently</h1></body>""</html>";constexprcharmoved_temporarily[]="<html>""<head><title>Moved Temporarily</title></head>""<body><h1>302 Moved Temporarily</h1></body>""</html>";constexprcharnot_modified[]="<html>""<head><title>Not Modified</title></head>""<body><h1>304 Not Modified</h1></body>""</html>";constexprcharbad_request[]="<html>""<head><title>Bad Request</title></head>""<body><h1>400 Bad Request</h1></body>""</html>";constexprcharunauthorized[]="<html>""<head><title>Unauthorized</title></head>""<body><h1>401 Unauthorized</h1></body>""</html>";constexprcharforbidden[]="<html>""<head><title>Forbidden</title></head>""<body><h1>403 Forbidden</h1></body>""</html>";constexprcharnot_found[]="<html>""<head><title>Not Found</title></head>""<body><h1>404 Not Found</h1></body>""</html>";constexprcharinternal_server_error[]="<html>""<head><title>Internal Server Error</title></head>""<body><h1>500 Internal Server Error</h1></body>""</html>";constexprcharnot_implemented[]="<html>""<head><title>Not Implemented</title></head>""<body><h1>501 Not Implemented</h1></body>""</html>";constexprcharbad_gateway[]="<html>""<head><title>Bad Gateway</title></head>""<body><h1>502 Bad Gateway</h1></body>""</html>";constexprcharservice_unavailable[]="<html>""<head><title>Service Unavailable</title></head>""<body><h1>503 Service Unavailable</h1></body>""</html>";}// namespace as_stringstd::stringto_string(reply::status_typestatus){switch(status){casereply::ok:// NOLINT(bugprone-branch-clone)returnas_string::ok;casereply::created:returnas_string::created;casereply::accepted:returnas_string::accepted;casereply::no_content:returnas_string::no_content;casereply::multiple_choices:returnas_string::multiple_choices;casereply::moved_permanently:returnas_string::moved_permanently;casereply::moved_temporarily:returnas_string::moved_temporarily;casereply::not_modified:returnas_string::not_modified;casereply::bad_request:returnas_string::bad_request;casereply::unauthorized:returnas_string::unauthorized;casereply::forbidden:returnas_string::forbidden;casereply::not_found:returnas_string::not_found;casereply::internal_server_error:returnas_string::internal_server_error;casereply::not_implemented:returnas_string::not_implemented;casereply::bad_gateway:returnas_string::bad_gateway;casereply::service_unavailable:returnas_string::service_unavailable;default:returnas_string::internal_server_error;}}}// namespace stock_replies
classrequest_parser{public:// Construct ready to parse the request method.request_parser();// Reset to initial parser state.voidreset();// Result of parse.enumresult_type{good,bad,indeterminate};// Parse some data. The enum return value is good when a complete// request has been parsed, bad if the data is invalid,// indeterminate when more data is required. The InputIterator// return value indicates how much of the input has been consumed.template<typenameInputIterator>std::tuple<result_type,InputIterator>parse(request&req,InputIteratorbegin,InputIteratorend){while(begin!=end){result_typeresult=consume(req,*begin++);if(result==good||result==bad)returnstd::make_tuple(result,begin);}returnstd::make_tuple(indeterminate,begin);}private:// Handle the next character of input.result_typeconsume(request&req,charinput);// Check if a byte is an HTTP character.staticboolis_char(intc);// Check if a byte is an HTTP control character.staticboolis_ctl(intc);// Check if a byte is defined as an HTTP tspecial character.staticboolis_tspecial(intc);// Check if a byte is a digit.staticboolis_digit(intc);// The current state of the parser.enumstate{method_start,method,uri,http_version_h,http_version_t_1,http_version_t_2,http_version_p,http_version_slash,http_version_major_start,http_version_major,http_version_minor_start,http_version_minor,expecting_newline_1,header_line_start,header_lws,header_name,space_before_header_value,header_value,expecting_newline_2,expecting_newline_3}state_;};
classrequest_handler{public:request_handler(constrequest_handler&)=delete;request_handler&operator=(constrequest_handler&)=delete;// Construct with a directory containing files to be served.explicitrequest_handler(conststd::string&doc_root);// Handle a request and produce a reply.voidhandle_request(constrequest&req,reply&rep);private:// The directory containing the files to be served.std::stringdoc_root_;// Perform URL-decoding on a string. Returns false if the encoding// was invalid.staticboolurl_decode(conststd::string&in,std::string&out);};
voidrequest_handler::handle_request(constrequest&req,reply&rep){// Decode url to path.std::stringrequest_path;if(!url_decode(req.uri,request_path)){rep=reply::stock_reply(reply::bad_request);return;}// Request path must be absolute and not contain "..".if(request_path.empty()||request_path[0]!='/'||request_path.find("..")!=std::string::npos){rep=reply::stock_reply(reply::bad_request);return;}// If path ends in slash (i.e. is a directory) then add// "index.html".if(request_path[request_path.size()-1]=='/'){request_path+="index.html";}// Determine the file extension.std::size_tlast_slash_pos=request_path.find_last_of("/");std::size_tlast_dot_pos=request_path.find_last_of(".");std::stringextension;if(last_dot_pos!=std::string::npos&&last_dot_pos>last_slash_pos){extension=request_path.substr(last_dot_pos+1);}// Open the file to send back.std::stringfull_path=doc_root_+request_path;std::ifstreamis(full_path.c_str(),std::ios::in|std::ios::binary);if(!is){rep=reply::stock_reply(reply::not_found);return;}// Fill out the reply to be sent to the client.rep.status=reply::ok;charbuf[512];while(is.read(buf,sizeof(buf)).gcount()>0)rep.content.append(buf,is.gcount());rep.headers.resize(2);rep.headers[0].name="Content-Length";rep.headers[0].value=std::to_string(rep.content.size());rep.headers[1].name="Content-Type";rep.headers[1].value=mime_types::extension_to_type(extension);}