Index
These classes implement a framework for HTTP servers. They are not meant to be building blocks for high speed, generic web content servers, but rather as application servers that use HTTP as a standard means of communication. Here's an example of a generic, bare-bones HTTP server. Bonus: it also handles HTTP over TLS. When started, it starts listening on two randomly chosen ports, a plain HTTP, and an encrypted HTTP, with a temporary, on the fly generated, self-signed certificate:
#include <x/fdlistener.H> #include <x/netaddr.H> #include <x/http/fdserver.H> #include <x/http/fdserverimpl.H> #include <x/http/fdtlsserver.H> #include <x/http/fdtlsserverimpl.H> #include <x/http/form.H> #include <x/gnutls/session.H> #include <x/xml/escape.H> #include <iterator> class listenersObj : virtual public x::obj { public: x::gnutls::session::base::factory factory; x::fdlistener http_listener; x::fdlistener https_listener; static x::fdlistener create_listener(const char *port) { std::list<x::fd> socketlist; x::netaddr::create("", 0)->bind(socketlist, false); std::cout << "Listening on " << port << " port " << socketlist.front()->getsockname()->port() << std::endl; return x::fdlistener::create(socketlist); } static x::gnutls::session::base::factory create_factory() { std::cout << "Creating certificate" << std::endl; x::gnutls::credentials::certificate cred(x::gnutls::credentials::certificate::create()); x::gnutls::x509::privkey key(x::gnutls::x509::privkey::create()); key->generate(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_NORMAL); key->fix(); x::gnutls::x509::crt cert(x::gnutls::x509::crt::create()); time_t now=time(NULL); cert->set_basic_constraints(true, 1); cert->set_activation_time(now-60); cert->set_expiration_time(now + 60 * 60 * 24 * 30); cert->set_dn_by_oid(GNUTLS_OID_X520_COMMON_NAME, "localhost"); cert->set_issuer_dn_by_oid(GNUTLS_OID_X520_COMMON_NAME, "localhost"); cert->set_key(key); cert->set_serial(1); cert->set_version(); cert->sign(cert, key); std::list<x::gnutls::x509::crt> certchain; certchain.push_back(cert); cred->set_key(certchain, key); x::gnutls::session::base::factory f(x::gnutls::session::base ::factory::create()); f->credentials_set(cred); return f; } listenersObj() : factory(create_factory()), http_listener(create_listener("http")), https_listener(create_listener("https")) { } ~listenersObj() {} }; typedef x::ref<listenersObj> listeners; template<typename impl_class> class myserverimpl : public impl_class, virtual public x::obj { public: listeners daemons; myserverimpl(const listeners &daemonsArg) : daemons(daemonsArg) { } ~myserverimpl() { } void received(const x::http::requestimpl &req, bool hasbody) { std::pair<x::http::form::parameters, bool> form=this->getform(req, hasbody); if (!form.second && hasbody) { impl_class::received_unknown_request(req, hasbody); // Throws an exception. return; } std::stringstream o; o << "<html><head><title>Your request</title></head><body>"; bool stop=form.first->find("stop") != form.first->end(); if (stop) { o << "<h1>Goodbye</h1>"; } else { o << "<h1>Your request headers:</h1>" "<p>URI: " << x::xml::escapestr(x::to_string(req.get_URI())) << "</p><table><thead><tr><th>Header</th><th>Contents</th></tr></thead><tbody>"; for (auto hdr:req) { o << "<tr><td>" << x::xml::escapestr(hdr.first) << "</td><td>" << x::xml::escapestr(hdr.second.begin(), hdr.second.end()) << "</td></tr>"; } o << "</tbody></table>" << "<h1>Your form parameters</h1>" "<table><thead><tr><th>Parameter</th><th>Value</th></tr></thead><tbody>"; for (auto p:*form.first) { o << "<tr><td>" << x::xml::escapestr(p.first) << "</td><td>" << x::xml::escapestr(p.second) << "</td></tr>"; } o << "</tbody></table><h2>Submit a form, and see what happens:</h2><form method='post'><table><tbody><tr><td>Text input:</td><td><input type=text name=textfield /></td></tr>" "<tr><td>Checkbox:</td><td><input type=checkbox name=yesno /></td></tr>" "<tr><td colspan=2><input type=submit name=submit value='Submit Form' /></td></tr>" "<tr><td colspan=2><hr /></td></tr><tr><td colspan=2><input type=submit name=stop value='Stop server' /></td></tr></tbody></table>"; } o << "</body></html>"; this->send(req, "text/html; charset=utf-8", std::istreambuf_iterator<char>(o.rdbuf()), std::istreambuf_iterator<char>()); if (stop) { daemons->http_listener->stop(); daemons->https_listener->stop(); } } }; template<typename server_type> class myhttpserver : virtual public x::obj { public: listeners daemons; myhttpserver(const listeners &daemonsArg) : daemons(daemonsArg) { } ~myhttpserver() {} server_type create() { return server_type::create(daemons); } }; typedef x::ref<myhttpserver< x::ref<myserverimpl<x::http::fdserverimpl> > > > http_instance; typedef x::ref<myhttpserver< x::ref<myserverimpl<x::gnutls::http ::fdtlsserverimpl> > > > https_instance; int main() { try { auto daemons(listeners::create()); daemons->http_listener ->start(x::http::fdserver::create(), http_instance::create(daemons)); daemons->https_listener ->start(x::gnutls::http::fdtlsserver::create(), daemons->factory, https_instance::create(daemons)); daemons->http_listener->wait(); daemons->https_listener->wait(); } catch (const x::exception &e) { std::cerr << e << std::endl; exit(1); } return 0; }
x::http::fdserverObj
and
x::gnutls::http::fdtlsserverObj
implement the appropriate run
() method that
makes them directly usable with
x::fdlistenerObj
.
x::http::fdserverObj
instantiates an
HTTP server,
x::gnutls::http::fdtlsserverObj
instantiates an
HTTP over TLS server.
The above example instantiates a separate subclass for both of them. The general approach is:
Derive a subclass from x::http::fdserverimpl
or x::gnutls::http::fdtlsserverimpl
,
that multiply-inherits from
x::obj
, making it a reference-counted class.
An optional non-default
x::gnutls::http::fdtlsserverObj
constructor
takes a x::gnutls::sessioncache
.
The default constructor creates a default TLS session cache object
that's described in the section called “TLS session caching, server side”.
The non-default constructor allows using non-default TLS session
caching.
Define a reference-counted factory class with a
create
() method that constructs a new instance
of the server class.
For TLS servers, a second factory class needs to be constructed, whose
create
() takes two parameters, the first one
is always a GNUTLS_SERVER
, the second parameter is
the file descriptor for the connected socket.
The create() method must return a suitable
TLS session, with an installed
server certificate.
x::gnutls::session::base::factory
implements a suitable TLS session factory, as in this example, but you
still must install a suitable certificate, before it can be used.
Instantiate either a
x::http::fdserver
or a
x::gnutls::http::fdtlserver
.
Pass it to the
listener object's
start
(), in addition to the
session factory object reference (for TLS servers only) and the
server class factory object reference.
The above example uses templates to instantiate
both the
x::http::fdserverObj
/x::http::fdserverimpl
and
the x::gnutls::http::fdtlsserverObj
/x::gnutls::http::fdtlsserverimpl
implementions from
a single code base, resulting in an identical server on both the plain
and the encrypted HTTP port.
A new instance of the server class implementation object gets instantiated
for each connection.
The implementation object's received
() method
gets invoked when the thread receives the HTTP request
from the client.
Clients that support persistent HTTP 1.1 connections
may send multiple requests.
After received
() returns, it may get called
against in the same thread and for the same implementation object, upon
receiving the next request from the client over the same persistent
connection. However, this is not to be relied upon, since clients may
employ multiple connections, which will use different threads.
class myserverimpl : public x::http::fdserverimpl, virtual public x::obj { public: // ... void received(const x::http::requestimpl &req, bool hasbody) { // ... send(req, "text/html; charset=utf-8", container.begin(), container.end()); } };
received
(), in a subclass of a
x::http::fdserverimpl
or a
x::gnutls::http::fdtlsserverimpl
,
processes an HTTP
request. It receives an instance of
x::http::requestimpl
and a flag indicating whether the request included content.
Before it returns, received
() must invoke
send
() exactly once, specifying the response to
the request.
The content of an HTTP request is obtained by
invoking begin
() and
end
():
void received(const x::http::requestimpl &req, bool hasbody) { if (hasbody) { iterator b=begin(), e=end(); // ...
begin
() and
end
() define an input sequence over the request
content. They may be invoked only once. They read from the underlying
network connection, which may experience delays.
For the most common use case of HTTP content
consisting of form input, getform
() provides
a convenient way to parse it.
void received(const x::http::requestimpl &req, bool hasbody) { std::pair<x::http::form::parameters, bool> form=getform(req, hasbody); if (form.second) hasbody=false; //...
getform
() returns a reference to a
form parameter object, and a flag.
getform
() sets the flag to
true
if it processed the contents of the request, so
its content has been read and is no longer available.
A false
is not necessarily an indication that the
request is not a form submission. An HTTP
GET
request places form parameters in the
query string portion of its URI, which
getform
() retrieves.
This getform
() example does not process file
uploads, they will throw an exception that results in a
“404 not found” response. See
the section called “Processing file uploads” for a version of
getform
() that handles file
uploads.
Most applications will want to check “req.get_method()” to
obtain the type of the request (x::http::GET
,
x::http::POST
,
x::http::HEAD
, and others), first, before
processing response.
The headers of the HTTP request are in
req
. They are generally as they
were received from the client. For convenience, the
“Host” header, if present, is removed and incorporated
into the
URI returned by get_URI
().
The heavily-overloaded send
() sends the
response to the HTTP request, and must be called
exactly once, before received
() terminates.
Its parameters are:
The original request, a three digit HTTP status code, and a brief reason phrase. This is used to send canned error messages, like 404, that do not require any additional headers:
send(req, 404, "Not found");
The original request, and a
x::http::response_exception
object. This is typically used to handle thrown standard exceptions:
// ... catch (const x::http::response_exception &e) { send(req, e); }
This would handle the canned exceptions thrown by the static
member methods in x::http::responseimpl
.
The original request, a “content type” string, and additional arguments described below. This formats an HTTP 200 response:
send(req, "text/plain; charset=utf-8", buffer);
A reference to a
x::http::responseimpl
object, the original request,
and additional arguments described below. This gives complete
control over the response message:
x::http::responseimpl resp(307, "Temporarily redirected"); resp.append("Location", "http://example.com"); send(resp, req, buffer);
The response object gets passed by reference, and may be modified
during sending. send
() typically adds
headers such as “Content-Length”, or
“Transfer-Encoding”, depending on the response.
The latter two versions of send
() sends
content of the response, specified either as a container, or
as a beginning iterator and an ending iterator. The
response to a HEAD
should be prepared as if
it was to a GET
or a POST
.
send
() sets the HTTP
protocol headers based on the container or the iterators, but
will not actually send the content.