diff --git a/CMakeLists.txt b/CMakeLists.txt index f9d2475..ef69dac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 3.9 ) +cmake_minimum_required( VERSION 3.6 ) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) diff --git a/README.md b/README.md index 1cb4135..ffe683b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![stable version](https://img.shields.io/github/release/JadeMatrix/SHOW.svg?label=stable)](https://github.com/JadeMatrix/SHOW/releases/latest) [![latest version](https://img.shields.io/github/release/JadeMatrix/SHOW/all.svg?label=latest)](https://github.com/JadeMatrix/SHOW/releases) -[![Documentation Status](https://readthedocs.org/projects/show-cpp/badge/?version=v0.8.0)](http://show-cpp.readthedocs.io/en/v0.8.0/?badge=v0.8.0) +[![Documentation Status](https://readthedocs.org/projects/show-cpp/badge/?version=v0.8.1)](http://show-cpp.readthedocs.io/en/v0.8.1/?badge=v0.8.1) SHOW is an idiomatic library for standalone webserver applications written for modern C++. SHOW is simple in the same way the standard library is simple — it doesn't make any design decisions for the programmer, instead offering a set of primitives for building an HTTP web application. Everything — when to serve, what requests to accept, even whether to send a response at all — is completely up to the programmer. diff --git a/doc/Connection.rst b/doc/Connection.rst index 8a52e99..be5eaa4 100644 --- a/doc/Connection.rst +++ b/doc/Connection.rst @@ -28,6 +28,14 @@ Connection The port of the connected client + .. cpp:function:: const std::string& server_address() const + + The address of the server handling the connection + + .. cpp:function:: unsigned int server_port() const + + The port of the server handling the connection + .. cpp:function:: int timeout() const Get the current timeout of this connection, initially inherited from the server the connection is created from diff --git a/doc/Tutorial.rst b/doc/Tutorial.rst index 7d66721..6dfaa09 100644 --- a/doc/Tutorial.rst +++ b/doc/Tutorial.rst @@ -122,9 +122,7 @@ Another thing to keep in mind is that HTTP/1.1 — and HTTP/1.0 with an extensio Sending Responses ================= -Sending responses is slightly more complex than reading basic requests, aside from the error handling which should wrap both. - -Say you want to send a "Hello World" message for any incoming request. First, start with a string containing the response message:: +Sending responses is slightly more involved than reading basic requests. Say you want to send a "Hello World" message for any incoming request. First, start with a string containing the response message:: std::string response_content = "Hello World"; diff --git a/doc/conf.py b/doc/conf.py index 19c1e6c..1095eb4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -56,7 +56,7 @@ # The short X.Y version. version = u'0.8' # The full version, including alpha/beta/rc tags. -release = u'0.8.0' +release = u'0.8.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/examples/echo.cpp b/examples/echo.cpp index 172489c..238938a 100644 --- a/examples/echo.cpp +++ b/examples/echo.cpp @@ -22,7 +22,8 @@ void handle_POST_request( show::request& request ) if( request.unknown_content_length() ) { - // Always require a Content-Length header for this application + // For this example, be safe and reject any request with no Content- + // Length header show::response response( request.connection(), show::http_protocol::HTTP_1_0, @@ -35,13 +36,17 @@ void handle_POST_request( show::request& request ) } else { + // Since we're echoing the request content, just replicate the Content- + // Length header headers[ "Content-Length" ].push_back( // Header values must be strings std::to_string( request.content_length() ) ); // Replicate the Content-Type header of the request if it exists, - // otherwise assume plain text + // otherwise use the default MIME type recommended in the HTTP + // specification, RFC 2616 §7.2.1: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1 auto content_type_header = request.headers().find( "Content-Type" ); if( content_type_header != request.headers().end() @@ -51,18 +56,20 @@ void handle_POST_request( show::request& request ) content_type_header -> second[ 0 ] ); else - headers[ "Content-Type" ].push_back( "text/plain" ); + headers[ "Content-Type" ].push_back( "application/octet-stream" ); - // This is not the most computationally-efficient way to accomplish - // this, see + // This is just the simplest way to read a whole streambuf into a + // string, not the most the fastest; see // https://stackoverflow.com/questions/3203452/how-to-read-entire-stream-into-a-stdstring std::string message = std::string( - std::istreambuf_iterator< char >( ( std::streambuf* )&request ), + std::istreambuf_iterator< char >( &request ), {} ); show::response response( request.connection(), + // Just handling one request per connection in this example, so + // respond with HTTP/1.0 show::http_protocol::HTTP_1_0, { 200, @@ -75,80 +82,62 @@ void handle_POST_request( show::request& request ) } } + int main( int argc, char* argv[] ) { std::string host = "::"; // IPv6 loopback (0.0.0.0 in IPv4) unsigned int port = 9090; // Some random higher port int timeout = 10; // Connection timeout in seconds - try - { - show::server test_server( - host, - port, - timeout - ); - - while( true ) + show::server test_server( + host, + port, + timeout + ); + + while( true ) + try + { + show::connection connection( test_server.serve() ); + try { - show::connection connection( test_server.serve() ); + show::request request( connection ); - try + if( request.method() == "POST" ) { - show::request request( connection ); - - if( request.method() == "POST" ) - { - handle_POST_request( request ); - } - else - { - show::response response( - request.connection(), - show::http_protocol::HTTP_1_0, - { - 405, - "Method Not Allowed" - }, - { server_header } - ); - } + handle_POST_request( request ); } - catch( const show::connection_timeout& ct ) + else { - std::cout - << "timed out waiting on client, closing connection" - << std::endl - ; - break; + show::response response( + request.connection(), + show::http_protocol::HTTP_1_0, + { + 405, + "Method Not Allowed" + }, + { server_header } + ); } } - catch( const show::connection_timeout& ct ) + catch( const show::connection_interrupted& ct ) { std::cout - << "timed out waiting for connection, looping..." + << "client " + << connection.client_address() + << " disconnected or timed out, closing connection" << std::endl ; } - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } return 0; } diff --git a/examples/fileserve.cpp b/examples/fileserve.cpp index da7a0bf..023c043 100644 --- a/examples/fileserve.cpp +++ b/examples/fileserve.cpp @@ -168,7 +168,9 @@ std::string guess_mime_type( const std::string& path ) else if( extension == ".mp3" ) return "audio/mpeg3"; else - // Default MIME type for raw binary + // Default MIME type recommended in the HTTP specification, RFC 2616 + // §7.2.1: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1 return "application/octet-stream"; } @@ -339,81 +341,61 @@ int main( int argc, char* argv[] ) int timeout = 10; // Connection timeout in seconds std::string message = "Hello World!"; - try - { - show::server test_server( - host, - port, - timeout - ); - - while( true ) + show::server test_server( + host, + port, + timeout + ); + + while( true ) + try + { + show::connection connection( test_server.serve() ); + try { - show::connection connection( test_server.serve() ); + show::request request( connection ); - try - { - show::request request( connection ); - - // Only accept GET requests - if( request.method() != "GET" ) - { - show::response response( - request.connection(), - show::http_protocol::HTTP_1_0, - { 501, "Not Implemented" }, - { server_header } - ); - continue; - } - - handle_GET_request( request, argv[ 1 ] ); - } - catch( const show::client_disconnected& cd ) + // Only accept GET requests + if( request.method() != "GET" ) { - std::cout - << "client " - << connection.client_address() - << " disconnected, closing connection" - << std::endl - ; - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting on client " - << connection.client_address() - << ", closing connection" - << std::endl - ; + show::response response( + request.connection(), + show::http_protocol::HTTP_1_0, + { 501, "Not Implemented" }, + { server_header } + ); + continue; } + + handle_GET_request( request, argv[ 1 ] ); + } + catch( const show::client_disconnected& cd ) + { + std::cout + << "client " + << connection.client_address() + << " disconnected, closing connection" + << std::endl + ; } catch( const show::connection_timeout& ct ) { std::cout - << "timed out waiting for connection, looping..." + << "timed out waiting on client " + << connection.client_address() + << ", closing connection" << std::endl ; } - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } return 0; } diff --git a/examples/hello_world.cpp b/examples/hello_world.cpp index 7e3d533..03cd229 100644 --- a/examples/hello_world.cpp +++ b/examples/hello_world.cpp @@ -11,90 +11,76 @@ int main( int argc, char* argv[] ) int timeout = 10; // Connection timeout in seconds std::string message = "Hello World!"; - try - { - show::server test_server( - host, - port, - timeout - ); - - while( true ) + show::server test_server( + host, + port, + timeout + ); + + while( true ) + try + { + show::connection connection( test_server.serve() ); + try { - show::connection connection( test_server.serve() ); + // This simple program doesn't even bother reading requests, but + // a request object is still needed ensure we're actually + // handing an HTTP connection + show::request request( connection ); + + // See the HTTP/1.1 example for an explanation + if( !request.unknown_content_length() ) + request.flush(); + + show::response_code code = { + 200, + "OK" + }; + show::headers_type headers = { + // Set the Server header to display the SHOW version + { "Server", { + show::version::name + + " v" + + show::version::string + } }, + // The message is simple plain text + { "Content-Type", { "text/plain" } }, + { "Content-Length", { + // Header values must be strings + std::to_string( message.size() ) + } } + }; + + show::response response( + connection, + show::http_protocol::HTTP_1_0, + code, + headers + ); - try - { - // This simple program doesn't even bother reading requests, - // but a request object is still needed to create a response - show::request request( connection ); - - show::response_code code = { - 200, - "OK" - }; - show::headers_type headers = { - // Set the Server header to display the SHOW version - { "Server", { - show::version::name - + " v" - + show::version::string - } }, - // The message is simple plain text - { "Content-Type", { "text/plain" } }, - { "Content-Length", { - // Header values must be strings - std::to_string( message.size() ) - } } - }; - - show::response response( - request.connection(), - show::http_protocol::HTTP_1_0, - code, - headers - ); - - response.sputn( message.c_str(), message.size() ); - // Alternatively a std::ostream could be created using the - // response as a buffer, and then the message sent using << - // or write() - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting on client, closing connection" - << std::endl - ; - break; - } + response.sputn( message.c_str(), message.size() ); + // Alternatively a std::ostream could be created using the + // response as a buffer, and then the message sent using << or + // write() } - catch( const show::connection_timeout& ct ) + catch( const show::connection_interrupted& cd ) { std::cout - << "timed out waiting for connection, looping..." + << "client " + << connection.client_address() + << " disconnected or timed out, closing connection" << std::endl ; } - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } return 0; } diff --git a/examples/http_1_1.cpp b/examples/http_1_1.cpp index af68fcc..320ca96 100644 --- a/examples/http_1_1.cpp +++ b/examples/http_1_1.cpp @@ -4,6 +4,117 @@ #include // std::string, std::to_string() +void handle_connection( show::connection& connection ) +{ + while( true ) // Loop over requests on this connection + try + { + show::request request( connection ); + + std::cout + << "serving a " + << request.method() + << " request for client " + << request.client_address() + << std::endl + ; + + // Flush out the request contents, as otherwise they'll still be + // sitting in the connection when we look for another request. If + // content was sent but there was no Content-Length header set, the + // request object constructor will choke & throw an exception (which + // in a real server you'd want to catch & return as a 400). + if( !request.unknown_content_length() ) + request.flush(); + + bool is_1p1 = request.protocol() == show::HTTP_1_1; + + std::string message( + "HTTP/" + + std::string( is_1p1 ? "1.1" : "1.0" ) + + " " + + request.method() + + " request from " + + request.client_address() + + " on path /" + ); + for( + auto iter = request.path().begin(); + iter != request.path().end(); + ++iter + ) + { + message += *iter; + message += "/"; + } + + show::headers_type headers = { + // Set the Server header to display the SHOW version + { "Server", { + show::version::name + + " v" + + show::version::string + } }, + // The message is simple plain text + { "Content-Type", { "text/plain" } }, + { "Content-Length", { + // Header values must be strings + std::to_string( message.size() ) + } } + }; + + show::response response( + request.connection(), + is_1p1 ? show::HTTP_1_1 : show::HTTP_1_0, + { 200, "OK" }, + headers + ); + + response.sputn( message.c_str(), message.size() ); + + // HTTP/1.0 "supports" persistent connections with the "Connection" + // header; this example defers to this header's value (if it exists) + // before checking the protocol version. + auto connection_header = request.headers().find( + "Connection" + ); + if( + connection_header != request.headers().end() + && connection_header -> second.size() == 1 + ) + { + const std::string& ch_val( + connection_header -> second[ 0 ] + ); + if( ch_val == "keep-alive" ) + // Keep connection open, loop to next request + continue; + else if( ch_val == "close" ) + // Close connection + break; + // else fall back to this protocol's default + // connection behavior: + // < HTTP/1.0 : close connection + // >= HTTP/1.1 : keep connection open + } + + if( request.protocol() <= show::HTTP_1_0 ) + break; + // else continue (HTTP/1.1+ defaults to keep-alive) + } + catch( const show::connection_interrupted& ct ) + { + std::cout + << "client " + << connection.client_address() + << " disconnected or timed out, closing connection" + << std::endl + ; + break; + } +} + + int main( int argc, char* argv[] ) { std::string host = "::"; // IPv6 loopback (0.0.0.0 in IPv4) @@ -11,175 +122,33 @@ int main( int argc, char* argv[] ) int timeout = 10; // Connection timeout in seconds std::string message = "Hello World!"; - try - { - show::server test_server( - host, - port, - timeout - ); - - while( true ) // Loop over connections - try - { - show::connection connection( test_server.serve() ); - - std::cout - << "client " - << connection.client_address() - << " connected" - << std::endl - ; - - while( true ) // Loop over requests on this connection - try - { - show::request request( connection ); - - std::cout - << "serving a " - << request.method() - << " request for client " - << request.client_address() - << std::endl - ; - - // Flush out the request contents, as otherwise they'll - // still be sitting in the connection when we look for - // another request. If content was sent but there was - // no Content-Length header sent, the request object - // constructor will chocke & throw an exception (which - // in a real server you'd want to catch & return as a - // 400). - if( !request.unknown_content_length() ) - request.flush(); - - bool is_1p1 = request.protocol() == show::HTTP_1_1; - - std::string message( - "HTTP/" - + std::string( is_1p1 ? "1.1" : "1.0" ) - + " " - + request.method() - + " request from " - + request.client_address() - + " on path /" - ); - for( - auto iter = request.path().begin(); - iter != request.path().end(); - ++iter - ) - { - message += *iter; - message += "/"; - } - - show::response_code code = { - 200, - "OK" - }; - show::headers_type headers = { - // Set the Server header to display the SHOW version - { "Server", { - show::version::name - + " v" - + show::version::string - } }, - // The message is simple plain text - { "Content-Type", { "text/plain" } }, - { "Content-Length", { - // Header values must be strings - std::to_string( message.size() ) - } } - }; - - show::response response( - request.connection(), - is_1p1 ? show::HTTP_1_1 : show::HTTP_1_0, - code, - headers - ); - - response.sputn( message.c_str(), message.size() ); - - // HTTP/1.0 "supported" persistent connections with the - // "Connection" header; this application defers to this - // header's value (if it exists) before checking the - // protocol version. - auto connection_header = request.headers().find( - "Connection" - ); - if( - connection_header != request.headers().end() - && connection_header -> second.size() == 1 - ) - { - const std::string& ch_val( - connection_header -> second[ 0 ] - ); - if( ch_val == "keep-alive" ) - // Keep connection open, loop to next request - continue; - else if( ch_val == "close" ) - // Close connection - break; - // else fall back to this protocol's default - // connection behavior: - // < HTTP/1.0 : close connection - // >= HTTP/1.1 : keep connection open - } - - if( request.protocol() <= show::HTTP_1_0 ) - break; - // else continue (HTTP/1.1+ default to keep-alive) - } - catch( const show::client_disconnected& cd ) - { - std::cout - << "client " - << connection.client_address() - << " disconnected, closing connection" - << std::endl - ; - break; - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting on client " - << connection.client_address() - << ", closing connection" - << std::endl - ; - break; - } - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting for connection, looping..." - << std::endl - ; - } - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + show::server test_server( + host, + port, + timeout + ); + + while( true ) // Loop over connections + try + { + show::connection connection( test_server.serve() ); + + std::cout + << "client " + << connection.client_address() + << " connected" + << std::endl + ; + + handle_connection( connection ); + } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } return 0; } diff --git a/examples/multiple_clients.cpp b/examples/multiple_clients.cpp index e847fd6..fb128de 100644 --- a/examples/multiple_clients.cpp +++ b/examples/multiple_clients.cpp @@ -3,6 +3,7 @@ #include // std::cout, std::cerr #include // std::thread #include // std::list +#include // std::unique_ptr // Set a Server header to display the SHOW version @@ -16,7 +17,7 @@ const show::headers_type::value_type server_header = { }; -void handle_connection( show::connection* connection ) +void handle_connection( std::shared_ptr< show::connection > connection ) { try { @@ -28,6 +29,12 @@ void handle_connection( show::connection* connection ) if( !request.unknown_content_length() ) request.flush(); + // This is just an example to show how multi-threaded connection + // handling could be implemented, and doesn't actually implement any + // server functionality. You may also want to introduce a time delay + // here using `std::this_thread::sleep_for()` to make it clearer that + // multiple connections can be handled at the same time; see + // http://en.cppreference.com/w/cpp/thread/sleep_for show::response response( request.connection(), show::http_protocol::HTTP_1_0, @@ -37,26 +44,18 @@ void handle_connection( show::connection* connection ) // Make sure the response is entirely sent before closing the connection response.flush(); - delete connection; } - catch( const show::client_disconnected& cd ) + catch( const show::connection_interrupted& cd ) { std::cout << "client " << connection -> client_address() - << " disconnected, closing connection" - << std::endl - ; - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting on client " - << connection -> client_address() - << ", closing connection" + << " disconnected or timed out, closing connection" << std::endl ; } + // Handle `std::exception`s and other throws here as we don't want to crash + // the entire program if something goes wrong handling one connection. catch( const std::exception& e ) { std::cerr @@ -77,55 +76,35 @@ void handle_connection( show::connection* connection ) int main( int argc, char* argv[] ) { - try - { - std::string host = "::"; // IPv6 loopback (0.0.0.0 in IPv4) - unsigned int port = 9090; // Some random higher port - int timeout = 10; // Connection timeout in seconds - std::string message = "Hello World!"; - - show::server test_server( - host, - port, - timeout - ); - - while( true ) + std::string host = "::"; // IPv6 loopback (0.0.0.0 in IPv4) + unsigned int port = 9090; // Some random higher port + int timeout = 10; // Connection timeout in seconds + std::string message = "Hello World!"; + + show::server test_server( + host, + port, + timeout + ); + + while( true ) + try { - try - { - std::thread worker( - handle_connection, + std::thread worker( + handle_connection, + std::shared_ptr< show::connection >( new show::connection( test_server.serve() ) - ); - worker.detach(); - } - catch( const show::connection_timeout& ct ) - { - std::cout - << "timed out waiting for connection, looping..." - << std::endl - ; - } + ) + ); + worker.detach(); } - - return 0; - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } + + return 0; } diff --git a/examples/streaming_echo.cpp b/examples/streaming_echo.cpp index 8264ef6..403280a 100644 --- a/examples/streaming_echo.cpp +++ b/examples/streaming_echo.cpp @@ -21,7 +21,7 @@ const std::streamsize buffer_size = 256; void handle_POST_request( show::request& request ) { - // Always require a Content-Length header for this application + // Always require a Content-Length header for this example if( request.unknown_content_length() ) show::response response( request.connection(), @@ -40,7 +40,9 @@ void handle_POST_request( show::request& request ) }; // Replicate the Content-Type header of the request if it exists, - // otherwise assume plain text + // otherwise use the default MIME type recommended in the HTTP + // specification, RFC 2616 §7.2.1: + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1 auto content_type_header = request.headers().find( "Content-Type" ); if( content_type_header != request.headers().end() @@ -50,7 +52,7 @@ void handle_POST_request( show::request& request ) content_type_header -> second[ 0 ] ); else - headers[ "Content-Type" ].push_back( "text/plain" ); + headers[ "Content-Type" ].push_back( "application/octet-stream" ); // Start a response before we read any data show::response response( @@ -117,83 +119,52 @@ int main( int argc, char* argv[] ) int timeout = 10; // Connection timeout in seconds std::string message = "Hello World!"; - try - { - show::server test_server( - host, - port, - timeout - ); - - while( true ) + show::server test_server( + host, + port, + timeout + ); + + while( true ) + try + { + show::connection connection( test_server.serve() ); + try { - show::connection connection( test_server.serve() ); + show::request request( connection ); - try - { - show::request request( connection ); - - // Only accept POST requests - if( request.method() != "POST" ) - { - show::response response( - request.connection(), - show::http_protocol::HTTP_1_0, - { 501, "Not Implemented" }, - { server_header } - ); - continue; - } - - handle_POST_request( request ); - } - catch( const show::client_disconnected& cd ) - { - std::cout - << "client " - << connection.client_address() - << " disconnected, closing connection" - << std::endl - ; - continue; - } - catch( const show::connection_timeout& ct ) + // Only accept POST requests + if( request.method() != "POST" ) { - std::cout - << "timed out waiting on client " - << connection.client_address() - << ", closing connection" - << std::endl - ; + show::response response( + request.connection(), + show::http_protocol::HTTP_1_0, + { 501, "Not Implemented" }, + { server_header } + ); continue; } + + handle_POST_request( request ); } - catch( const show::connection_timeout& ct ) + catch( const show::connection_interrupted& ct ) { std::cout - << "timed out waiting for connection, looping..." + << "client " + << connection.client_address() + << " disconnected or timed out, closing connection" << std::endl ; } - } - catch( const std::exception& e ) - { - std::cerr - << "uncaught std::exception in main(): " - << e.what() - << std::endl - ; - return -1; - } - catch( ... ) - { - std::cerr - << "uncaught non-std::exception in main()" - << std::endl - ; - return -1; - } + } + catch( const show::connection_timeout& ct ) + { + std::cout + << "timed out waiting for connection, looping..." + << std::endl + ; + } return 0; } diff --git a/src/show.hpp b/src/show.hpp index df6a9ea..73e1054 100644 --- a/src/show.hpp +++ b/src/show.hpp @@ -32,8 +32,8 @@ namespace show static const std::string name = "SHOW"; static const int major = 0; static const int minor = 8; - static const int revision = 0; - static const std::string string = "0.8.0"; + static const int revision = 1; + static const std::string string = "0.8.1"; } @@ -187,11 +187,15 @@ namespace show char* get_buffer = nullptr; char* put_buffer = nullptr; int _timeout; + std::string _server_address; + unsigned int _server_port; connection( socket_fd fd, - const std::string& address, - unsigned int port, + const std::string& client_address, + unsigned int client_port, + const std::string& server_address, + unsigned int server_port, int timeout ); @@ -220,6 +224,8 @@ namespace show public: const std::string& client_address() const { return _serve_socket.address; }; const unsigned int client_port () const { return _serve_socket.port; }; + const std::string& server_address() const { return _server_address; }; + const unsigned int server_port () const { return _server_port; }; connection( connection&& ); ~connection(); @@ -244,7 +250,7 @@ namespace show request( class connection& ); request( request&& ); // See note in implementation - connection & connection () const { return _connection; } + show::connection & connection () const { return _connection; } const std::string & client_address () const { return _connection.client_address(); } const unsigned int client_port () const { return _connection.client_port (); } http_protocol protocol () const { return _protocol; } @@ -475,11 +481,15 @@ namespace show inline connection::connection( socket_fd fd, - const std::string& address, - unsigned int port, + const std::string& client_address, + unsigned int client_port, + const std::string& server_address, + unsigned int server_port, int timeout ) : - _serve_socket( fd, address, port ) + _serve_socket( fd, client_address, client_port ), + _server_address( server_address ), + _server_port( server_port ) { // TODO: Only allocate once needed get_buffer = new char[ BUFFER_SIZE ]; @@ -611,10 +621,10 @@ namespace show { int_type gotc = sbumpc(); - if( gotc == traits_type::eof() ) - break; - else + if( gotc == traits_type::not_eof( gotc ) ) s[ i ] = traits_type::to_char_type( gotc ); + else + break; ++i; } @@ -730,10 +740,12 @@ namespace show } inline connection::connection( connection&& o ) : - _serve_socket( std::move( o._serve_socket ) ), - get_buffer( std::move( o.get_buffer ) ), - put_buffer( std::move( o.put_buffer ) ), - _timeout( std::move( o._timeout ) ) + _serve_socket( std::move( o._serve_socket ) ), + get_buffer( std::move( o.get_buffer ) ), + put_buffer( std::move( o.put_buffer ) ), + _timeout( std::move( o._timeout ) ), + _server_address( std::move( o._server_address ) ), + _server_port( std::move( o._server_port ) ) { // See comment in `request::request(&&)` implementation } @@ -1086,8 +1098,8 @@ namespace show return traits_type::eof(); else { - int_type c = _connection.uflow(); - if( c == traits_type::eof() ) + int_type c = _connection.underflow(); + if( c != traits_type::not_eof( c ) ) throw client_disconnected(); return c; } @@ -1098,7 +1110,7 @@ namespace show if( eof() ) return traits_type::eof(); int_type c = _connection.uflow(); - if( c == traits_type::eof() ) + if( c != traits_type::not_eof( c ) ) throw client_disconnected(); ++read_content; return c; @@ -1310,15 +1322,15 @@ namespace show "listen" ); - sockaddr_in6 client_address; - socklen_t client_address_len = sizeof( client_address ); + sockaddr_in6 address_info; + socklen_t address_info_len = sizeof( address_info ); char address_buffer[ 3 * 4 + 3 + 1 ]; socket_fd serve_socket = accept( listen_socket -> descriptor, - ( sockaddr* )&client_address, - &client_address_len + ( sockaddr* )&address_info, + &address_info_len ); if( @@ -1326,15 +1338,15 @@ namespace show || ( inet_ntop( AF_INET, - &client_address.sin6_addr, + &address_info.sin6_addr, address_buffer, - client_address_len + address_info_len ) == NULL && inet_ntop( AF_INET6, - &client_address.sin6_addr, + &address_info.sin6_addr, address_buffer, - client_address_len + address_info_len ) == NULL ) ) @@ -1350,10 +1362,30 @@ namespace show ); } + std::string client_address = std::string( address_buffer ); + unsigned int client_port = ntohs( address_info.sin6_port ); + + if( + getsockname( + serve_socket, + ( sockaddr* )&address_info, + &address_info_len + ) == -1 + ) + { + auto errno_copy = errno; + throw socket_error( + "could not get port information from socket: " + + std::string( std::strerror( errno_copy ) ) + ); + } + return connection( serve_socket, - std::string( address_buffer ), - client_address.sin6_port, + client_address, + client_port, + listen_socket -> address, + ntohs( address_info.sin6_port ), timeout() ); }