Tutorial¶
This shows the basic usage of SHOW; see the examples for a more thorough introduction.
Including & Compiling¶
The preferred method of including SHOW is via the CMake package. Once installed somewhere CMake can find it, import and use SHOW in your CMakeLists.txt with:
FIND_PACKAGE( SHOW REQUIRED COMPONENTS show )
ADD_EXECUTABLE( my_server my_server.cpp )
TARGET_LINK_LIBRARIES( my_server PRIVATE SHOW::show )
You should also switch your compiler to C++11 mode with:
SET( CMAKE_CXX_STANDARD 11 )
SET( CMAKE_CXX_STANDARD_REQUIRED ON )
SET( CMAKE_CXX_EXTENSIONS OFF )
For GCC and Clang, you can either link show.hpp to one of your standard include search paths, or use the -I
flag to tell the compiler where too find the header:
clang++ -I "SHOW/src/" ...
SHOW is entirely contained in a single header file, you have to do then is include SHOW using #include <show.hpp>
. With either compiler you’ll also need to specify C++11 support with -std=c++11
.
Creating a Server¶
To start serving requests, first create a server
object:
show::server my_server{
"0.0.0.0", // IP address on which to serve
9090, // Port on which to serve
};
That’s it, you’ve made a server that sits there forever until it gets a connection, then hangs. Not terribly useful, but that’s easy to fix.
Handling a Connection¶
For each call of my_server.serve()
a single connection
object will be returned or a connection_timeout
thrown. You may want to use something like this:
while( true )
try
{
show::connection connection{ my_server.serve() };
// handle request(s) here
}
catch( const show::connection_timeout& ct )
{
std::cout
<< "timed out waiting for a connection, looping..."
<< std::endl
;
continue;
}
The server listen timeout can be a positive number, 0, or -1. If it is -1, the server will continue listening until interrupted by a signal; if 0, server::serve()
will throw a connection_timeout
immediately unless connections are available.
The connection is now independent from the server. You can adjust the connection’s timeout independently using connection::timeout()
. You can also pass it off to a worker thread for processing so your server can continue accepting other connections; this is usually how you’d implement a real web application.
Reading Requests¶
request
objects have a number of const
fields containing the HTTP request’s metadata; you can see descriptions of them all in the docs for the class.
Note that these fields do not include the request content, if any. This is because HTTP allows the request content to be streamed to the server. In other words, the server can interpret the headers then wait for the client to send data over a period of time. For this purpose, request
inherits from std::streambuf
, implementing the read/get functionality. You can use the raw std::streambuf
methods to read the incoming data, or create a std::istream
from the request object for std::cin
-like behavior.
For example, if your server is expecting the client to POST a single integer, you can use:
show::request request{ test_server.serve() };
std::istream request_content_stream{ &request };
int my_integer;
request_content_stream >> my_integer;
Please note that the above is not terribly safe; production code should include various checks to guard against buggy or malignant clients.
Also note that individual request operations may timeout, so the entire serve code should look like this:
while( true )
try
{
show::connection connection{ my_server.serve() };
try
{
show::request request{ connection };
std::istream request_content_stream{ &request };
int my_integer;
request_content_stream >> my_integer;
std::cout << "client sent " << my_integer << "\n";
}
catch( const show::client_disconnected& ct )
{
std::cout << "got a request, but client disconnected!" << std::endl;
}
catch( const show::connection_timeout& ct )
{
std::cout << "got a request, but client timed out!" << std::endl;
}
}
catch( const show::connection_timeout& ct )
{
std::cout << "timed out waiting for a connection, looping..." << std::endl;
continue;
}
If this feels complicated, it is. Network programming like this reveals the worst parts of distributed programming, as there’s a lot that can go wrong between the client and the server.
Another thing to keep in mind is that HTTP/1.1 — and HTTP/1.0 with an extension — allow multiple requests to be pipelined on the same TCP connection. SHOW can’t know with certainty where on the connection one request ends and another starts — it’s just the nature of pipelined HTTP. Sure, the Content-Length header could be used, and chunked transfer encoding has well-established semantics, but if the client uses neither it is up to your application to figure out the end of the request’s content. In general, you should reject requests whose length you can’t readily figure out, but SHOW leaves that decision up to the programmer. But you should never try to create a request
from a connection
before you’ve finished reading the content from a previous request.
See also
std::streambuf
on cppreference.comstd::istream
on cppreference.comstd::cin
on cppreference.com
Sending Responses¶
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" };
Next, create a headers object to hold the content type and length headers (note that header values must be strings):
show::headers_t headers{
{ "Content-Type", { "text/plain" } },
{ "Content-Length", {
std::to_string( response_content.size() )
} }
};
Since it’s a std::map
, you can also add headers to a headers_t
like this:
headers[ "Content-Type" ].push_back( "text/plain" );
Then, set the HTTP status code for the response to the generic 200 OK:
show::response_code code{
200,
"OK"
};
Creating a response object requires the headers and response code to have been decided already, as they are marshalled (serialized) and buffered for sending as soon as the object is created. A response object also needs to know which request it is in response to. While there’s nothing preventing you from creating multiple responses to a single request this way, most of the time that will break your application.
Create a response like this:
show::response response{
connection,
show::http_protocol::HTTP_1_0,
code,
headers
};
Finally, send the response content. Here, a std::ostream
is used, as response
inherits from and implements the write/put functionality of std::streambuf
:
std::ostream response_stream{ &response };
response_stream << response_content;
See also
std::map
on cppreference.comstd::ostream
on cppreference.comstd::streambuf
on cppreference.com