GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

C++ Klassen-Template für HTTP-Kommunikation

C++ HTTP communication class template

Kafreitagsprojekt, um das HTTP Protokoll ein Bisschen mehr zu verinnerlichen (manche Sachen muss man einfach mal selbst implementiert haben). Die statischen Funktionen und Teile des Parsers hatte ich schon im voraus, jedoch galt es diese in eine "nette Struktur" zu packen, mit der HTTP Anfragen vergleichsweise komfortabel direkt in C++ bearbeitet werden können - ohne Schnittstellen für LUA oder JS. Zudem sollte die Klasse selbst weitgehend unabhängig von plattformspezifischen Mechanismen sein (Datei-IO, Dateipfadkonventionen, usw), auch Eingabe- und Ausgabestream sollte wählbar sein (d.h. egal ob Dateien, stdin/stdout=GCI oder Socket). Erste Tests mit Apache Benchmark und 128 gleichzeitigen Anfragen ergaben (GET / HTTP1.1) in einer virtuellen Maschine (VirtualBox mit Debian und einem CPU) ca. 8000 Accepts pro Sekunde und durchschnittlich 200us Antwortzeit. Das ist natürlich nicht repräsentativ, da die Anfragen direkt im RAM behandelt wurden - und die CPU-Auslastung der VM 100% war. Es geht hier nicht darum, Apache oder Ngnx wirderzuerfinden, Zielanwendung ist vielmehr eine Konfigurationsschnittstelle für netzwerkangebundene ("CNC") Steuerungen, bei der z.B. Regler über eine HTML5-Schnittstelle eingestellt werden können. Dazu müssen Echtzeitdaten recht schnell aus RO-Registern oder Fifos über Websockets an den Browser gesendet werden - das wird besser mit einem kompiliertem Programm bewerkstelligt.

Zur Klasse

sw::http ist die Standardspezialisierung des Templates sw::templates::basic_http<...>, welchem als Argument eine Dateispeicherungsklasse übergeben wird.

Temporary File Storage

Diese Speicherklasse ist abgeleitet von sw::templates::http_tmpstorage_provider<>, und dient ausschließlich dem Zweck, in eine temporäre Datei zu schreiben, daraus zu lesen und sie beim Schließen zu löschen. Sie wird nur dann benötigt, wenn PUT oder POST-Anfragen vom Client zu groß sind, um sie direkt im RAM zu behandeln - z.B. wenn Dateien hochgeladen werden. Ansonsten sind alle Eingaben inklusive der Dateninhalte in den Instanzobjekten gespeichert. Die zu überladenen Methoden des Storage Providers sind vom Muster so gewählt, dass sie den Konventionen von üblichen "low-level" C I/O Funktionen entsprechen, z.B. kann read direkt an ::read oder ::fread geklebt werden, d.h. es wird mit Binärpuffern und deren Größen gearbeitet. Die Rückgabewerte sind demnach die Anzahl geschriebener/gelesener Bytes oder negativ bei Fehlern. Ansonsten gibt es open, close, eof, size, seek usw - die bekannte Pallette. Wo die temporäre Datei liegt (bzw. ob es überhaupt eine Datei ist), wie sie heißt, und ob sie "gelocked" wird (was definitiv zu empfehlen ist), ist der http-Klasse egal - wenn es Fehler gibt wird ein "500 Internal server error" an den Client gesendet. Die default-Spezialisierung sw::http verwendet direkt die Basis sw::templates::http_tmpstorage_provider<>, und diese gibt bei allen Operationen -1 == Fehler zurück, d.h. sie erlaubt Client-Eingaben, die im RAM behandelt werden können. Noch eine Anmerkung: Die http-Klasse fängt keine Exceptions aus dem Storage-Provider ab. Exceptions sollten nur bei wirklich üblen Fehlern geworfen werden, nicht wenn z.B. kein Platz auf der Platte ist - dazu kann open negative Ergebnisse zurückgeben. Als Argument wird beim Aufruf von open direkt mit angegeben, wieviele Bytes benötigt werden (Content-Length Angabe im Requestheader, und mehr wird auch nicht geschrieben). Auf das Storage-Objekt einer http-Instanz kann von außen mit der Methode inline storage_t & storage() throw(), also z.B.

  • if(my_http_instance.storage().eof()) { ... }

zugegriffen werden.

I/O Streams

Um die Ein- und Ausgabekanäle der basic_http<> Klasse zu bestimmen müssen nur zwei Methoden überladen werden:

  • virtual long read(void* data, unsigned long sz) und
  • virtual long write(const void* data, unsigned long sz).

Diese Methoden entsprechen den Mustern der SysCalls ::read und ::write ohne File-Descriptor, und per Default wird von stdin gelesen und auf stdout geschrieben ("CGI Programm"). Für direkte Verwendung mit Sockets müssen diese Methoden überladen werden, und einfach an ::send und ::recv geklebt werden. Diese Implementierung macht es deutlich einfacher und übersichtlicher als eigene c++ iostreams zu schreiben. Anmerkung: Wie üblich bedeutet der Rückgabewert 0 bei read End Of Stream. Der Fehlercode EGAGAIN bzw. EWOULDBLOCK wird berücksichtigt. Der Klasse ist es egal, ob die Eingabe "blocking" oder "nonblocking" ist. Seitens des Parsers gibt es keine Zeitabhängigkeit.

Statische Funktionen

Der eigentliche Grund, warum die Standard-Spezialisierung sw::http existiert, sind die statischen Funktionen, für die nie ein Storage-Provider benötigt wird. D.h. man kann diese Funktionen verwenden ohne einen eigenen typdef ... my_http; anlegen zu müssen. Es sind meist HTML bzw. HTTP-Funktionen:

  • string urldecode(const string &in, bool form=false) // "hello%20" --> "hello " etc
  • string urlencode(const string &in) // "hello " --> "hello%20" etc
  • string htmlspecialchars(const string &s, bool with_quotes=true) // "<" --> "&lt;" etc
  • string mime_content_type(const string &file_extension) // png --> image/png
  • string mime_content_type_add(const char *ext, const char *mime)
  • const char * reponse_status_text(unsigned code) // 404 -> "404 Not found"
  • int parse_url_encoded(const string & inp, str_map_t & out, ..) // string --> std::map<>
  • string timestamp2string(time_t t) // Local time --> RFC(GMT)
  • time_t string2timestamp(const string &stime) // RFC(GMT) --> local time

Hier kommen vielleicht noch ein paar hinzu.

Instanz und Methoden

Letztendlich will man sich weniger um das Protokoll kümmern und viel mehr mit den "Payload" Daten arbeiten. Es bleibt einem natürlich nicht alles erspart, dazu ist das HTTP-Protokoll zu vielfältig, aber die Basics dürften schon recht gut abgedeckt sein. Auch hier wurden bei allen Methoden auf die direkte Verwendung von Exceptions verzichtet - d.h. wenn eine Exception aus der Klasse kommt ist es ein "fataler Fehler" (z.B. kein Speicher bekommen oder ein STL Container beschwert sich) - oder ein Bug. Letztendlich führt es dazu, dass vielleicht noch ein 500 weggeschickt werden kann. Exceptions können gerne durch Ableiten der Klasse hinzugefügt werden.

Da wäre zunächst einmal die Konfiguration static basic_http<...>::config. Dabei handelt es sich um eine struct Instanz, in der Einstellungen für alle Klassenobjekte angegeben werden, z.B. Log-Level, welche log-Funktion verwendet werden soll, maximale Größe der Client-Anfragen, usw. Dieses Konfigurationsobjekt ist nicht thread-safe, also nicht durch Mutex/Critical Sections abgesperrt. Es sollte vor dem Starten des Server-Threads geschrieben und dann nur noch gelesen werden. Wenn man sich die Einstellungen ansieht wird klar, dass es tatsächlich konstante "write-once" Werte sind. Details siehe im Quelltext struct basic_http::config_t. Beispiel:

  • http::config.log_level = http::log_warn; // Log critical, error and warning
  • http::config.max_content_length = 2ul *(1ul<<20); // 2MB requests allowed, not more

Dann hat jede Instanz natürlich ein "Request-Objekt" und ein "Response-Objekt", auf die mit instance.request() und instance.response() zugegriffen wird. Die Details sind im Quelltext dokumentiert (struct request_t und struct response_t). Sie enthalten in erster Linie HTTP-Headerinformationen, die beim Parsen des Requestheaders bzw. Zusammenstellen des Response-Headers verwendet werden. Die Daten sind in einem für C++ verwendbarem Format, d.h. Zeiten sind keine RFC Strings, sondern ein Zeitstempel, Cache-Control-Daten sind auf eine enum abgebildet, usw. Vor allem wenn IDEs mit Auto-Complete (wie Netbeans oder Eclipse) verwendet werden ist die Handhabung schnell und einfach. Unbekannte HTTP-Headerdaten werden in std::map<string, string>'s gespeichert und können von dort abgerufen werden, also beiepielsweise:

  • string auth=inst.request().headers["authorization"]; oder
  • inst.response().headers["WWW-Authenticate"] = "Basic realm=\"My realm\"".

Um die Verwendung zu beschleunigen gibt es noch ein paar typische Zugriffsmethoden, die auch von anderen Programmiersprachen bekannt sind. Dabei ist str_t std::string und str_map_t std::map<std::string, std::string>:

GET-Variablen

  • const str_map_t & get() gibt alle GET variablen als std::map zurück.

  • const str_t & get(const str_t & var) gibt eine GET Variable "by-name" zurück, oder "" falls nicht in der Map. (Achtung: Namen sind immer kleingeschrieben, auch wenn in der Anfrage Großbuchstaben dabei sind).

  • double get_dbl(const str_t & var), das ganze als double oder NAN wenn nicht gefunden oder nicht in double konvertierbar. (Achtung: Namen sind immer kleingeschrieben)

POST-Variablen

  • const str_map_t & post() const throw(), alle POST-Variablen AUßER DATEIEN.

  • const str_t & post(const str_t & var), Stringwert einer bestimmten POST Variablen (Namen kleingeschrieben).

  • double post_dbl(const str_t & var), double-Wert einer POST Variablen oder NAN, falls nicht gefunden oder keine Zahl. (Namen kleingeschrieben)

  • const str_map_t & cookie(): Alle COOKIE Werte von der Anfrage (Request-Header) als map.

COOKIE-Variablen

  • const str_t & cookie(const str_t & var): Request-COOKIE-Wert "by-name", auch hier: Namen kleingeschrieben.

  • double cookie_dbl(const str_t & var): Request-COOKIE-Wert "by-name" als double oder NAN. (Namen kleingeschrieben)

  • basic_http & cookie(const str_t & name, const str_t & value, time_t expires=0, const str_t & domain="", const str_t & path="", bool secure=false, bool httponly=false): Setzt ein Response-Header-Cookie (mit den bekannten Attributen, die noch hinzu kommen können). expires ist ein lokaler Zeitstempel, GMT-Konvertierung kommt dann beim Abschicken des Headers.

  • basic_http & cookie(const str_t & name, double d, time_t expires=0, const str_t & domain="", const str_t & path="", bool secure=false, bool httponly=false): Derselbe Spaß, aber statt string wird ein double-Wert gesetzt.

FILES-Variablen

  • const upload_files_t & files(): Map aller hochgeladenen Dateien. upload_files_t ist dabei eine std::map<string, upload_file_t>, und upload_file_t Objekte, in denen Informationen über die Dateien stehen (Name, Größe, und wo sie im storage() zu finden ist).

HEADER-Variablen

  • basic_http & header(const str_t& key, const str_t& value): Setzt einen Reponse-Header oder löscht ihn wenn value=="".

  • const str_t & header(const str_t& key): Gibt einen Reponse-Header zurück, oder "" wenn nicht gefunden.

  • str_t header(): Erstellt den kompletten Text-Header für die Antwort, greift dabei auf Daten von config, response() und request() zurück. Normalerweise muss diese Methode nie aufgerufen werden. Die send Methode tut das implizit.

DUMP

  • str_t dump(): Alle Objektinformationen als JSON-ähnlicher text. Kann hilfreich sein.

SEND, RECV und STATE Hier wird es nochmals interessant. Wenn eine eingehende Verbindung akzeptiert und ein neues http-Objekt erstellt wurde, muss erstmal der Requestheader empfangen werden bevor weitere Schritte folgen (Achtung: Read-Timeouts müssen dabei extern implementiert werden). Wenn beim Parsen des Headers kein Fehler oder "Bad Request" festgestellt wurde, wird auch der "Message Body" empfangen, andernfalls direkt ein Fehler zum Rücksenden signalisiert. Beginnen wir mit recv - zwei Möglichkeiten gibt es:

  • int recv(const char* rq, sz_t sz): Empfängt sz Bytes aus dem Puffer rq.

  • int recv(): Empfängt mit Hilfe der Methode virtual long read(...) - so viel wie geht, also kein Fehler, kein 0 (EOF) und kein EAGAIN kommt.

Der Rückgabewert von recv ist entweder

  • 1: "ich brauche mehr Daten, hatte EAGAIN bzw. alle sz Bytes gelesen.",

  • 0: "ich brauche keine Daten mehr, egal ob noch welche da wären" (z.B. wenn Content-Length erreicht ist oder ein GET/HEAD/DELETE usw. geschickt wurde).

  • <0: "Fehler!", Dabei sind Werte > -100 fatal (bedeutet es kann nicht einmal eine Fehlermeldung an den Client geschickt werden. Das passiert z.B. wenn der Socket einfach geschlossen wird). Werte < -100 sind invertierte HTTP Status Codes. Wenn also -400 zurückgegeben wird, dann stimmt etwas mit dem Requestheader nicht (400="Bad Request"). Die Fehler werden zusätzlich mit Meldungen geloggt. Gleichzeitig wird der Response-Header-Status auf den Fehlercode gesetzt. Beim Zusammenbau des Response-Headers wird nochmals der Code geprüft und in 500 umgewandelt falls der Fehlercode kein richtiger HTTP Statuscode ist.

Zum Senden gibt es:

  • int send(): Die zuvor festgelegten Daten senden bzw. weitersenden ("flush").

  • int send(int status): Response-Header Statuscode setzen und senden. Das geht nur wenn der Header noch nicht gesendet wurde, ansonsten wird der Status einfach ignoriert.

  • int send(const str_t & s, int status=200, bool implicit_content_length=true): Einen String senden, mit Status. Nun kann man sich entscheiden, ob Content-Length gleich mitgesetzt werden soll oder nicht. Wenn ja ist der Transfer nach dem Senden dieses Strings abgeschlossen. Wenn nein wird im Header keine Angabe über die Größe der Daten gemacht. Der Client erwartet dann so lange Daten bis der Server den Socket schließt. Wenn der Antwortheader schon gesendet ist haben status und implicit_content_length keinen Effekt mehr.

  • int send_binary(const void *data, sz_t sz, int status=200, bool implicit_content_length=true) Dasselbe für Binärdaten.

  • int send_file(const str_t & filename, int status=200): Sendet eine Datei, 404 falls nicht gefunden, 403 falls nicht lesbar, 500 bei anderen Fehlern. Prüft "If-Modified-Since" und "Etag", sendet 304 falls nicht geändert. MIME Content-Type anhand der Dateiendung, "application/octet-stream" für unbekannte Endungen. Sendet 204 falls die Datei leer ist.

Wenn recv ein Fehler aufgetreten ist, kann direkt send() ohne Argumente aufgerufen werden. Da der Statuscode schon gesetzt ist, aber kein Inhalt existiert, wird automatisch eine kleine Meldung (z.B. <h1>Not found</h1>) hinzugefügt. Allerdings (und das mit Absicht) ist der Status code erstmal 0, und wenn eine Antwort ohne Setzen eines Status Codes abgesendet wird, so wird automatisch ein 500 ausgelöst - d.h. der Default-Status ist nicht 200.

Eigentlich wär's das schon. Weitere Features sind chunked-Encoding, was vor allem dann sinvoll ist (bzw. ehr nur dann), wenn kontinuierliche Daten ohne Client-Polling gesendet werden sollen. Weitere Details in den Beispielen.

Originally a good Friday project composing a single file template for HTTP server features. Basic functionality was already implemented quite a bit ago for several applications, features like multipart support added recently.

Purpose: To emphasise what is it not: Apache, gnix, or mongoose/civet. It is a template to provide functionality to parse requests, compose responses in a socket or stdin/stdout compatible way. So the class can be used as CGI, as well as direct server socket processor. How the communication channel is implemented is not part of this class - instead you simply glue the I/O to your system by overloading the class methods read and write, which are compatible to ::read() and ::write() (except that errno does not apply). You also have full control of the process flow, as you call the I/O methods of the http class actively - so it is flexibly up to you weather threads, callback based methodology or sync i/o applies.

Simply take a glance at the examples to see the usage.

Dateien

Files

http.hh Microtest: microtest.cc Examples: http.cc http1.cc http_multipart.cc httpd.cc

Beispiele

Examples

#include <http.hh>
#include "test.hh"
 
using sw::http;
 
// <editor-fold desc="static functions" defaultstate="collapsed">
static bool strieq(std::string s1, std::string s2)
{
  std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower);
  std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower);
  return s1 == s2;
}
 
static void test_static_fn()
{
  {
    test_expect( http::htmlspecialchars("", true) == ""  );
    test_expect( http::htmlspecialchars("<a>", true) == "&lt;a&gt;" );
    test_expect( http::htmlspecialchars("<a name=\"name\">", true) == "&lt;a name=&quot;name&quot;&gt;" );
    test_expect( http::htmlspecialchars("<a name=\"name\">", false) == "&lt;a name=\"name\"&gt;" );
    test_expect( http::htmlspecialchars("<a name=\"&name\">", false) == "&lt;a name=\"&amp;name\"&gt;" );
    test_expect( http::htmlspecialchars("<>&\"'", false) == "&lt;&gt;&amp;\"'" );
    test_expect( http::htmlspecialchars("<>&\"'", true) == "&lt;&gt;&amp;&quot;&apos;" );
  }
  {
    test_expect( strieq(http::mime_content_type(""), "text/plain") );
    test_expect( strieq(http::mime_content_type("notregistered"), "text/plain") );
    test_expect( strieq(http::mime_content_type("aac"), "audio/aac") );
    test_expect( strieq(http::mime_content_type("asf"), "video/x-ms-asf") );
    test_expect( strieq(http::mime_content_type("avi"), "video/x-msvideo") );
    test_expect( strieq(http::mime_content_type("bmp"), "image/bmp") );
    test_expect( strieq(http::mime_content_type("css"), "text/css") );
    test_expect( strieq(http::mime_content_type("csv"), "text/csv") );
    test_expect( strieq(http::mime_content_type("doc"), "application/msword") );
    test_expect( strieq(http::mime_content_type("eps"), "application/postscript") );
    test_expect( strieq(http::mime_content_type("exe"), "application/octet-stream") );
    test_expect( strieq(http::mime_content_type("gif"), "image/gif") );
    test_expect( strieq(http::mime_content_type("gz"), "application/x-gunzip") );
    test_expect( strieq(http::mime_content_type("htm"), "text/html") );
    test_expect( strieq(http::mime_content_type("html"), "text/html") );
    test_expect( strieq(http::mime_content_type("ico"), "image/x-icon") );
    test_expect( strieq(http::mime_content_type("jpeg"), "image/jpeg") );
    test_expect( strieq(http::mime_content_type("jpg"), "image/jpeg") );
    test_expect( strieq(http::mime_content_type("js"), "application/javascript") );
    test_expect( strieq(http::mime_content_type("json"), "application/json") );
    test_expect( strieq(http::mime_content_type("m4v"), "video/x-m4v") );
    test_expect( strieq(http::mime_content_type("mid"), "audio/x-midi") );
    test_expect( strieq(http::mime_content_type("mov"), "video/quicktime") );
    test_expect( strieq(http::mime_content_type("mp3"), "audio/mpeg") );
    test_expect( strieq(http::mime_content_type("mp4"), "video/mp4") );
    test_expect( strieq(http::mime_content_type("mpeg"), "video/mpeg") );
    test_expect( strieq(http::mime_content_type("mpg"), "video/mpeg") );
    test_expect( strieq(http::mime_content_type("oga"), "audio/ogg") );
    test_expect( strieq(http::mime_content_type("ogg"), "audio/ogg") );
    test_expect( strieq(http::mime_content_type("ogv"), "video/ogg") );
    test_expect( strieq(http::mime_content_type("pdf"), "application/pdf") );
    test_expect( strieq(http::mime_content_type("png"), "image/png") );
    test_expect( strieq(http::mime_content_type("ps"), "application/postscript") );
    test_expect( strieq(http::mime_content_type("rar"), "application/x-arj-compressed") );
    test_expect( strieq(http::mime_content_type("rtf"), "application/rtf") );
    test_expect( strieq(http::mime_content_type("sgm"), "text/sgml") );
    test_expect( strieq(http::mime_content_type("shtm"), "text/html") );
    test_expect( strieq(http::mime_content_type("shtml"), "text/html") );
    test_expect( strieq(http::mime_content_type("svg"), "image/svg+xml") );
    test_expect( strieq(http::mime_content_type("swf"), "application/x-shockwave-flash") );
    test_expect( strieq(http::mime_content_type("tar"), "application/x-tar") );
    test_expect( strieq(http::mime_content_type("tgz"), "application/x-tar-gz") );
    test_expect( strieq(http::mime_content_type("tif"), "image/tiff") );
    test_expect( strieq(http::mime_content_type("tiff"), "image/tiff") );
    test_expect( strieq(http::mime_content_type("txt"), "text/plain") );
    test_expect( strieq(http::mime_content_type("wav"), "audio/x-wav") );
    test_expect( strieq(http::mime_content_type("webm"), "video/webm") );
    test_expect( strieq(http::mime_content_type("wrl"), "model/vrml") );
    test_expect( strieq(http::mime_content_type("xhtml"), "application/xhtml+xml") );
    test_expect( strieq(http::mime_content_type("xls"), "application/x-msexcel") );
    test_expect( strieq(http::mime_content_type("xml"), "text/xml") );
    test_expect( strieq(http::mime_content_type("xsl"), "application/xml") );
    test_expect( strieq(http::mime_content_type("xslt"), "application/xml") );
    test_expect( strieq(http::mime_content_type("zip"), "application/x-zip-compressed") );
  }
  {
    test_expect( http::urlencode("") == "" );
    test_expect( http::urlencode("a") == "a" );
    test_expect( http::urlencode(" ") == "%20" );
    test_expect( http::urldecode("") == "" );
    test_expect( http::urldecode("a") == "a" );
    test_expect( http::urldecode("%20") == " " );
    for(int i=0; i<100; i++) {
      std::string s = sw::utest::random<string>(100);
      if(!test_expect_cond( http::urldecode(http::urlencode(s)) == s )) {
        test_comment("test string = " << s);
      }
    }
  }
  {
    test_expect( strieq(http::reponse_status_text(0  ), "500 internal server error") );
    test_expect( strieq(http::reponse_status_text(-1 ), "500 internal server error") );
    test_expect( strieq(http::reponse_status_text(999), "500 internal server error") );
    test_expect( strieq(http::reponse_status_text(100), "100 Continue") );
    test_expect( strieq(http::reponse_status_text(101), "101 Switching Protocols") );
    test_expect( strieq(http::reponse_status_text(102), "102 Processing") );
    test_expect( strieq(http::reponse_status_text(200), "200 OK") );
    test_expect( strieq(http::reponse_status_text(201), "201 Created") );
    test_expect( strieq(http::reponse_status_text(202), "202 Accepted") );
    test_expect( strieq(http::reponse_status_text(203), "203 Non-Authoritative Information") );
    test_expect( strieq(http::reponse_status_text(204), "204 No Content") );
    test_expect( strieq(http::reponse_status_text(205), "205 Reset Content") );
    test_expect( strieq(http::reponse_status_text(206), "206 Partial Content") );
    test_expect( strieq(http::reponse_status_text(207), "207 Multi-Status") );
    test_expect( strieq(http::reponse_status_text(208), "208 Already Reported") );
    test_expect( strieq(http::reponse_status_text(226), "226 IM Used") );
    test_expect( strieq(http::reponse_status_text(300), "300 Multiple Choices") );
    test_expect( strieq(http::reponse_status_text(301), "301 Moved Permanently") );
    test_expect( strieq(http::reponse_status_text(302), "302 Found") );
    test_expect( strieq(http::reponse_status_text(303), "303 See Other") );
    test_expect( strieq(http::reponse_status_text(304), "304 Not Modified") );
    test_expect( strieq(http::reponse_status_text(305), "305 Use Proxy") );
    test_expect( strieq(http::reponse_status_text(306), "306 Switch Proxy") );
    test_expect( strieq(http::reponse_status_text(307), "307 Temporary Redirect") );
    test_expect( strieq(http::reponse_status_text(308), "308 Permanent Redirect") );
    test_expect( strieq(http::reponse_status_text(400), "400 Bad Request") );
    test_expect( strieq(http::reponse_status_text(401), "401 Unauthorized") );
    test_expect( strieq(http::reponse_status_text(402), "402 Payment Required") );
    test_expect( strieq(http::reponse_status_text(403), "403 Forbidden") );
    test_expect( strieq(http::reponse_status_text(404), "404 Not Found") );
    test_expect( strieq(http::reponse_status_text(405), "405 Method Not Allowed") );
    test_expect( strieq(http::reponse_status_text(406), "406 Not Acceptable") );
    test_expect( strieq(http::reponse_status_text(407), "407 Proxy Authentication Required") );
    test_expect( strieq(http::reponse_status_text(408), "408 Request Timeout") );
    test_expect( strieq(http::reponse_status_text(409), "409 Conflict") );
    test_expect( strieq(http::reponse_status_text(410), "410 Gone") );
    test_expect( strieq(http::reponse_status_text(411), "411 Length Required") );
    test_expect( strieq(http::reponse_status_text(412), "412 Precondition Failed") );
    test_expect( strieq(http::reponse_status_text(413), "413 Request Entity Too Large") );
    test_expect( strieq(http::reponse_status_text(414), "414 Request-URI Too Long") );
    test_expect( strieq(http::reponse_status_text(415), "415 Unsupported Media Type") );
    test_expect( strieq(http::reponse_status_text(416), "416 Requested Range Not Satisfiable") );
    test_expect( strieq(http::reponse_status_text(417), "417 Expectation Failed") );
    test_expect( strieq(http::reponse_status_text(419), "419 Authentication Timeout") );
    test_expect( strieq(http::reponse_status_text(420), "420 Enhance Your Calm") );
    test_expect( strieq(http::reponse_status_text(422), "422 Unprocessable Entity") );
    test_expect( strieq(http::reponse_status_text(423), "423 Locked") );
    test_expect( strieq(http::reponse_status_text(424), "424 Failed Dependency") );
    test_expect( strieq(http::reponse_status_text(425), "425 Unordered Collection") );
    test_expect( strieq(http::reponse_status_text(426), "426 Upgrade Required") );
    test_expect( strieq(http::reponse_status_text(428), "428 Precondition Required") );
    test_expect( strieq(http::reponse_status_text(429), "429 Too Many Requests") );
    test_expect( strieq(http::reponse_status_text(431), "431 Request Header Fields Too Large") );
    test_expect( strieq(http::reponse_status_text(440), "440 Login Timeout") );
    test_expect( strieq(http::reponse_status_text(444), "444 No Response") );
    test_expect( strieq(http::reponse_status_text(449), "449 Retry With") );
    test_expect( strieq(http::reponse_status_text(450), "450 Blocked by Windows Parental Controls") );
    test_expect( strieq(http::reponse_status_text(451), "451 Unavailable For Legal Reasons") );
    test_expect( !strieq(http::reponse_status_text(451), "451 Redirect") ); // Microsoft only, collides with standard draft 451
    test_expect( strieq(http::reponse_status_text(494), "494 Request Header Too Large") );
    test_expect( strieq(http::reponse_status_text(495), "495 Cert Error") );
    test_expect( strieq(http::reponse_status_text(496), "496 No Cert") );
    test_expect( strieq(http::reponse_status_text(497), "497 HTTP to HTTPS") );
    test_expect( strieq(http::reponse_status_text(499), "499 Client Closed Request") );
    test_expect( strieq(http::reponse_status_text(500), "500 Internal Server Error") );
    test_expect( strieq(http::reponse_status_text(501), "501 Not Implemented") );
    test_expect( strieq(http::reponse_status_text(502), "502 Bad Gateway") );
    test_expect( strieq(http::reponse_status_text(503), "503 Service Unavailable") );
    test_expect( strieq(http::reponse_status_text(504), "504 Gateway Timeout") );
    test_expect( strieq(http::reponse_status_text(505), "505 HTTP Version Not Supported") );
    test_expect( strieq(http::reponse_status_text(506), "506 Variant Also Negotiates") );
    test_expect( strieq(http::reponse_status_text(507), "507 Insufficient Storage") );
    test_expect( strieq(http::reponse_status_text(508), "508 Loop Detected") );
    test_expect( strieq(http::reponse_status_text(509), "509 Bandwidth Limit Exceeded") );
    test_expect( strieq(http::reponse_status_text(510), "510 Not Extended") );
    test_expect( strieq(http::reponse_status_text(511), "511 Network Authentication Required") );
    test_expect( strieq(http::reponse_status_text(520), "520 Origin Error") );
    test_expect( strieq(http::reponse_status_text(521), "521 Web server is down") );
    test_expect( strieq(http::reponse_status_text(522), "522 Connection timed out") );
    test_expect( strieq(http::reponse_status_text(523), "523 Proxy Declined Request") );
    test_expect( strieq(http::reponse_status_text(524), "524 A timeout occurred") );
    test_expect( strieq(http::reponse_status_text(598), "598 Network read timeout error") );
    test_expect( strieq(http::reponse_status_text(599), "599 Network connect timeout error") );
  }
}
// </editor-fold>
 
// <editor-fold desc="test_timestamp2string" defaultstate="collapsed">
static void test_timestamp2string()
{
  test_expect( http::timestamp2string(0) == "Thu, 01 Jan 1970 00:00:00 GMT");
  test_expect( http::string2timestamp("Thu, 01 Jan 1970 00:00:00 GMT") == 0);
  test_expect( http::timestamp2string(1337374161) == "Fri, 18 May 2012 20:49:21 GMT" );
  for(int i=0; i<10; ++i)  {
    time_t t_random = sw::utest::random<time_t>(0, std::numeric_limits<int32_t>::max());
    test_note("Random time = " << long(t_random));
    std::string s = http::timestamp2string(t_random);
    test_note("Stringyfied time = '" << s << "'");
    time_t t_parsed = http::string2timestamp(s);
    test_note("Back-parsed time = '" << t_parsed << "'");
    if(!test_expect_cond( t_parsed == t_random )) {
      test_comment("t_random= " << (long long) t_random << ", str=" << s << ", t_parsed=" << t_parsed);
    }
  }
}
// </editor-fold>
 
// <editor-fold desc="class test_http" defaultstate="collapsed">
 
class test_http : public http
{
public:
 
  typedef void (*rq_callback_t)(test_http&); // for std=98 no lambda
  typedef void (*chk_callback_t)(std::string& s); // for std=98 no lambda
 
  std::string* rq_input;
  std::string* rq_output;
 
public:
 
  static void log_comment(const string & s, int level)
  { (void) level; test_comment(s); }
 
  static void test_request(const string & s, bool is_invalid = false, rq_callback_t fn=0, chk_callback_t chk=0)
  {
    test_http ht;
    std::string inp = s;
    std::string out;
    ht.rq_input = &inp;
    ht.rq_output = &out;
    int n_read = 1;
    while(n_read > 0) n_read = ht.recv();
    if(n_read < 0) {
      test_expect( is_invalid );
      test_expect( ht.error() != 0 );
      test_comment( "ok, expected error, which is: " << ht.error() << " == " << ht.error_msg() );
      if(!test_expect_cond( n_read != -1 )) {
        test_fail("Fatal error was not expected.");
        return;
      }
      if(fn) fn(ht);
      while(ht.send() > 0) ;
    } else {
      test_expect( !is_invalid );
      if(fn) fn(ht); else ht.response().status = 200;
      while(ht.send() > 0) ;
      test_expect ( !ht.error() ); // that would mean send error or invalid response generated.
    }
    test_expect( !out.empty() );
    test_comment("http response is:" << endl << out );
    if(chk) chk(out);
  }
 
protected:
 
  virtual long read(void* data, unsigned long sz)
  {
    if(!sz) return 0;
    if(rq_input->length() <= sz) {
      sz = rq_input->length();
      for(unsigned long i=0; i<sz; ++i) ((char*)data)[i] = (*rq_input)[i]; // without memcpy
      rq_input->clear();
      return sz;
    } else {
      for(unsigned long i=0; i<sz; ++i) ((char*)data)[i] = (*rq_input)[i];
      *rq_input = rq_input->substr(sz);
      return sz;
    }
  }
 
  virtual long write(const void* data, unsigned long sz)
  {
    if(!sz) return 0;
    rq_output->reserve(rq_output->length()+sz);
    for(unsigned long i=0; i<sz; ++i) *rq_output += (char)(((const char*)data)[i]);
    return sz;
  }
};
// </editor-fold>
 
// <editor-fold desc="test_simple_request" defaultstate="collapsed">
 
static void get_01_rq(test_http & ht)
{
  test_expect( ht.response().status == 0 );
  ht.send("test ok");
}
static void get_01_chk(string& s)
{
  std::transform(s.begin(), s.end(), s.begin(), ::tolower);
  test_expect( s.find("\r\n\r\ntest ok") != s.npos );
  test_expect( s.find("200 ok") != s.npos );
  test_expect( s.find("http/1.0") != s.npos );
  std::string::size_type p = s.find("content-length: ");
  if(test_expect_cond( p != string::npos )) {
    while(!::isdigit(s[p])) p++;
    test_expect(s[p] == '7');
  }
}
 
static void get_badrq_chk(string& s)
{
  std::transform(s.begin(), s.end(), s.begin(), ::tolower);
  test_expect( s.find("400 bad request") != string::npos);
}
 
static void post_rq(test_http & ht)
{
  test_expect( ht.get("b") == " spaces " );
  test_expect( ht.get_dbl("a") == 1 );
  test_expect( ht.cookie().size() == 3 );
  test_expect( ht.cookie_dbl("a") == 1 );
  test_expect( ht.cookie("b") == "a string" );
  test_expect( ht.cookie("c") == "a string" );
  test_expect( ht.request().accept == "" );
  test_expect( ht.request().content_length == 10 );
  test_expect( ht.request().content_type.find("text/plain") != string::npos );
 
  test_expect( ht.request().content_type_id == ht.cont_tp_text_plain );
  test_expect( ht.ram_handled() );
  test_expect( ht.request().data.empty() );
  test_expect( ht.post().size() == 1 );
  test_expect( ht.post("text") == "0123456789" );
 
  test_expect( ht.request().files.empty() );
 
  ht.cookie("counter", "123321", time(0)+3600);
  ht.send("post test body");
}
static void post_chk(string& s)
{
  std::transform(s.begin(), s.end(), s.begin(), ::tolower);
  test_expect( s.find("counter=123321") != s.npos );
  test_expect( s.find("200 ok") != s.npos );
  test_expect( s.find("post test body") != s.npos );
}
 
static void test_simple_request()
{
  http::config.keep_alive = true;
  http::config.server = "localhost";
  http::config.log_level = http::log_debug;
  http::config.log_callback = test_http::log_comment;
  http::config.implicit_clear = false;
  http::config.max_ram_content_length = 10000;
  http::config.append_error_messages = true;
 
  test_http::test_request("GET / HTTP/1.0\n\n", false, get_01_rq, get_01_chk);
  test_http::test_request("", true, 0, get_badrq_chk);
  test_http::test_request("GET / HTTP/1.0\n", true, 0, get_badrq_chk);
  test_http::test_request("GET / HTTP/2.0\n\n", true, 0, get_badrq_chk);
 
  test_http::test_request(
    "POST http://www.example.com/post/?a=1&b=%20spaces%20 HTTP/1.1\n"
    "Content-length: 10\n"
    "Content-Type: text/plain\n"
    "Cookie: a=1;   b=\"a string\"  ;  c=a%20string"
    "\n\n"
    "0123456789" , false, post_rq, post_chk
  );
}
// </editor-fold>
 
void test()
{
  test_static_fn();
  test_simple_request();
  test_timestamp2string();
}
#include <http.hh>
#include <filesystem.hh>
#include <string.hh>
#include <iomanip>
#include <fcntl.h>
 
#if defined __MSC_VER
namespace { namespace std { template <typename T> bool isnan(T d) { return !!_isnan(d); } } }
#endif
 
 
namespace sw { namespace math {
template <typename vt>
vt random(vt min, double max) throw()
{
  static bool is_init = false;
  if(!is_init) { ::srand(::time(0)); is_init=true; }
  double mn = min, mx = max;
  if(mn>mx) { double t=mn; mn=mx; mx=t; }
  mn += ((mx-mn)/(double)RAND_MAX * ::rand());
  return (vt)mn;
}
}}
 
 
using namespace std;
using sw::fs;
using sw::trim;
using sw::rtrim;
using sw::to_lower;
 
typedef sw::detail::basic_http<sw::detail::http_ram_storage_provider<> > http;
 
// <editor-fold desc="statics" defaultstate="collapsed">
int test_content_mime_type()
{
  #define chk(X,Y) { string s=http::mime_content_type(X); \
    cout << "'" << X << "' --> '" << s << "' |--> " \
         << (s==Y ? "ok" : "FAILED") << endl; }
 
  chk(string(""),"text/plain");
  chk("z", "text/plain");
  chk("_", "text/plain");
  chk("txt", "text/plain");
  chk("html", "text/html");
  chk("mp3", "audio/mpeg");
  chk("aac", "audio/aac");
  chk("zip", "application/x-zip-compressed");
 
  http::mime_content_type_add("aaa", "singsang");
  chk("aaa", "singsang");
 
  #undef chk
  return 0;
}
 
int test_urlencode_urldecode()
{
  // php -r 'echo urlencode("!@£$%^&") . "\n";'
  cout << http::urlencode("abcdefghij_~.;,$(!@$%^&)") << endl;
  cout << http::urldecode("abcdefghij_%7e.%3b%2c%24%28%21%40%24%25%5e%26%29") << endl;
 
  for(int i=0; i<100; ++i) {
    string in, enc, dec;
    in.reserve(100);
    for(int j=0; j<100; j++) {
      in += sw::math::random<char>(30, 127);
      if(sw::math::random<int>(0, 100) > 80) break;
    }
    enc = http::urlencode(in);
    dec = http::urldecode(enc);
    if(in != dec) {
      cout << "Missmatch urlencode/decode [" << i <<"]: '" << in << "' --> '"
           << enc << "' --> '" << dec << "'" << endl;
    }
  }
  return 0;
}
 
// </editor-fold>
 
// <editor-fold desc="log" defaultstate="collapsed">
string logstr;
void dolog(const string & s, int level)
{
  (void) level;
  cerr << "LOG" << s << endl;
  logstr += s;
  logstr += "\n";
}
// </editor-fold>
 
int test_http(bool getindex)
{
  http::config.keep_alive = true;
  http::config.server = "localhost";
  http::config.log_level = http::log_debug;
  http::config.log_callback = dolog;
  http::config.implicit_clear = false;
  http::config.max_ram_content_length = 10000;
 
  //http::connection_t prov; http ht(prov);
  http ht;
 
  string base_path;
  int r=1;
 
  if(getindex) {
    cout << "GET / HTTP/1.1" << endl << endl;
 
    base_path = "/http/htdocs";
    stringstream ss;
    #define ssrecv(X) { string s = X; r = ht.recv(s.data(), s.length()); }
//    ss << "GET /test/chunked/ HTTP/1.1\n\n";
//    ss << "GET /index.html HTTP/1.1\n\n";
//    ss << "GET /test/chunked/ HTTP/1.1\nCookie: test=aber\n\n";
 
    // 3.1415926535897932384626433832795029
 
 
    ss << "POST /test/post/?a=1&b=%20no%20 HTTP/1.1" << endl
       << "Content-length:100" << endl << endl << "1234";
    ssrecv(ss.str());
    ssrecv("56789012345678901234567890123456789012");
    ssrecv("34567890123456789012345");
    ssrecv("67890123456789012345678901234567890");
    ssrecv("IGNORED_IGNORED_IGNORED_IGNORED_IGNORED");
    #undef ssrecv
  //cerr << endl << ht.dump() << endl;
 
  } else {
    while(r>0) r = ht.recv();
  }
 
  if(r == -1) return 1; // Fatal -> exit immediately, nothing we can send to the client
  if(r < 0) { ht.send(); return 1; } // Error where we can inform the client
 
  string path = fs::cwd() + base_path + ht.request().path;
  path = fs::realpath(path);
  if(fs::is_dir(path)) path = rtrim(path, "/") + "/index.html";
 
  if(path.find("/.") != string::npos) {
    ht.send(404);
  } else if(!fs::is_file(path)) {
    if(ht.request().path == "/test/post/") {
      ht.response().content_type = "text/html; charset=utf-8";
      ht.response().cache_ctrl = http::cache_ctrl_no_store;
      stringstream ss;
      double d = ht.post_dbl("post_double");
      string s = http::htmlspecialchars(ht.post("post_string"));
      ss << "<html><head></head><body>" << endl
         << "<h1>Post test</h1>" << endl
         << "<form method=\"post\" action=\"" << ht.request().path <<  "\">" << endl
         << "  <input type=\"text\" name=\"post_string\" value=\"" << s << "\"/>" << endl
         << "  <input type=\"text\" name=\"post_double\" value=\"" << setprecision(12) << d << "\"/>" << endl
         << "  <input type=\"submit\" name=\"post_submit\" value=\"submit\"/>" << endl
         << "</form>" << endl
         << "<br/><br/><pre>" << endl
         << "post_string = " << s << endl
         << "post_double = " << setprecision(12) << d << endl
         << endl
         << "is RAM handled = " << ht.modified() << endl
         << "</pre></body></html>" << endl;
      ht.send(ss.str(), 200);
    } else if(ht.request().path == "/test/chunked/") {
      ht.response().transf_encoding = http::tfe_chunked;
      ht.response().content_type = "text/plain; charset=utf-8";
      ht.response().cache_ctrl = http::cache_ctrl_no_store;
      double d = ht.cookie_dbl("count");
      if(std::isnan(d)) d = 0;
      d += 1;
      ht.cookie("count", d, time(0)+3600);
      stringstream ss;
      ss << "Counter = " << d << endl
         << "get[a] = " << ht.get_dbl("a") << endl
         << "get[b] = " << ht.get_dbl("b") << endl
         << logstr
       ;
 
      ht.send(ss.str(), 200);
    } else {
      ht.send(404);
    }
  } else if(!fs::is_readable(path)) {
    ht.send(403);
  } else {
    ht.send_file(path);
  }
  while(ht.send() > 0); // Flush buffer
 
  return 0;
}
 
 
int main(int argc, char** argv)
{
  if(argc < 2 || !argv[1]) {
    return test_http(true); // default: GET index.html
  } else {
    string cmd = trim(to_lower(string(argv[1])));
    if(cmd == "--urlencode") {
      return test_urlencode_urldecode();
    } else if(cmd == "--mime") {
      return test_content_mime_type();
    } else if(cmd == "--httpd") {
      return test_http(false);
    } else {
      cout << "Usage: " << argv[0] << " [--urlencode|--mime|--httpd]" << endl;
    }
  }
  return 1;
}
#include <http.hh>
#include <string.hh>
#include <iomanip>
#include <cassert>
 
using namespace std;
using namespace sw;
 
//
// Here some requests you can pipe in with cat.
//
#ifdef FILE_CONTENTS_TO_PIPE_INTO_THIS_EXAMPLE1
POST /test1/?a=1&b=hello&c=%20space%20 HTTP/1.1
Content-length:100
 
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
#endif
#ifdef FILE_CONTENTS_TO_PIPE_INTO_THIS_EXAMPLE2
POST /test2/?a=1&b=hello&c=%20space%20 HTTP/1.1
Content-length: 11
Content-type: application/x-www-form-urlencoded
Cookie: var1=10; var2=%20NONO%20
 
ccc=1&bbb=3
#endif
 
// Logging function, used with sw::http::config.log_callback = dolog;
void dolog(const string &txt, int level) {
  cerr << "[" << level << "]:" << txt << endl;
}
 
int main()
{
  // The http class has a static configuration that applies to
  // all instances. You need to set this stuff only once.
  // Note we are using namespace sw, http is in that namespace.
  http::config.keep_alive = true;               // Default header Connection: keep-alive
  http::config.server = "localhost";            // Default header Server: localhost
  http::config.log_level = http::log_debug;     // Log level
  http::config.log_callback = dolog;            // Assign logging callback
  http::config.implicit_clear = false;          // Free memory as soon as possible
  http::config.max_ram_content_length = 10000;  // Maximum POST/PUT length directly handled in RAM
 
  // Instantiate a new http object for a new request.
  sw::http ht;
 
  // The object has a state that describes what phase of the request we're in,
  // that starts with receiving the header. When ht.recv() is called, the object
  // reads from the specified source stream using the virtual read(...) method.
  // When the header is received, or on error, the state switches accordingly.
  assert(ht.state() == http::st_r_header);
 
  // The default specialisation sw::http reads from STDIN and writes to STDOUT, but using it with
  // a socket is simple: overload the methods
  //
  //  - virtual long read(void* data, unsigned long sz)
  //  - virtual long write(const void* data, unsigned long sz)
  //
  // to implement the data source/sink to whatever you like (for sockets send and recv).
  // But important: The methods must behave like ::read and ::write, means return the number
  // of bytes read/written, 0 on EOF, <0 on error. The http class checks EWOULDBLOCK/EAGAIN.
 
  // recv returns 1 when it wants more data to proceed, 0 when it does not need more data, and
  // negative on error. If the error is <-100 it is a HTTP error code that can be responded to
  // directly.
 
  int n_received;
  do { n_received = ht.recv(); } while(n_received > 0); // Reads stdin until EOF, enough or error
 
  if(n_received < 0) {
    if(n_received < 100) {
      // The response code is already set, an error page will be sent automatically
      // Send returns > 0 when more data are in the buffer that shall be sent, so we
      // loop. Also, the error will be logged.
      while(ht.send() > 0);
      return ht.response().status;
    } else {
      // Nothing we can send back, the error is fatal. E.g. socket closed, broken pipe etc.
      // The error is logged. So, here we just exit with 1 === fatal.
      return 1;
    }
  }
 
  // At this point the request read. Either it is completely in RAM (e.g. if it is a GET
  // request or the request content-length smaller than http::config.max_ram_content_length)
  // OR the request body is stored using the storage_provider (more about that later).
  // We definitely have the request header, and we can set the response header:
  assert(ht.state() >= http::st_r_done);
 
  // The dump() method helps to analyse what we got now:
//  cerr << "ht object after input: " << ht.dump() << endl << endl;
 
  // Let's work with some request values:
  if(ht.request().method == http::rq_get) { // it's a GET request
 
    // That is the default if the client accepts it
    ht.response().content_type = "text/html";
 
    // That is also default, we don't need to set it. Local time!
    ht.response().date = time(NULL);
 
    // E.g. we set Cache-Control: private
    ht.response().cache_ctrl = http::cache_ctrl_private;
 
    // If that nonempty 'Etag: "...."' will be set in the response header, otherwise omitted
    ht.response().etag = "0123-abcd-0815";
 
    // Local time of last modified, GMT conversion is done by the http class
    ht.response().last_modified = time(NULL);
 
    // Force "Connection: close". Normally http checks the HTTP version and what the
    // client prefers, and sets "Connection: keep-alive" or "Connection: close" automatically.
    // Note that if you use a socket, set the keepalive option, too. Otherwise that makes no sense.
    ht.response().keep_alive = false;
 
    // You can enable or disable if you accept content ranges
    ht.response().accept_ranges = false;
 
    // The status is something you MUST set the send() methods allow to do this implicitly.
    ht.response().status = 200;
    // Here we set "Transfer-Encoding: chunked". You can also say http::tfe_gzip, deflate etc,
    // but the http class does not zip/deflate it for you - the data you push in must already be.
    ht.response().transf_encoding = http::tfe_chunked;
 
    // You can set it explicitly or leave it -1 (it is already -1)
    ht.response().content_length = -1;
 
    // ht.response().headers is a string map. But you better use the header() method:
    ht.response().headers["x-poweredby"] = "me";
 
    // We overwrite it with the proper header method. Note that the keys are always STORED
    // LOWERCASE. When sending the header they will be converted to "Proper-Case".
    ht.header("x-poweredby", "you");
    // And that's the getter.
    assert(ht.header("x-poweredby") == "you");
 
    // COOKIE getters FROM THE REQUEST
    string text = ht.cookie("text");            // Empty if not set
    double counter = ht.cookie_dbl("counter");  // NAN if not set
    if(std::isnan(counter)) counter = 0;
    if(text.empty()) text = "a text";
 
    // COOKIE setters TO RESPONSE HEADER
    ht.cookie("text", text, time(0)+3600);        // Expires in an hour, set string
    ht.cookie("counter", counter, time(0)+3600);  // Expires in an hour, set double
 
    // GET getters from request:
    string a = ht.get("a");
    string b = ht.get("b");
    string c = ht.get("c");
 
    // Our response body:
    std::stringstream ss;
    // http::htmlspecialchars escapes html characters, ht.request().path is the requested
    // resource without query string
    ss << "<h1>Index of " << http::htmlspecialchars( ht.request().path ) <<  " </h1><br/>" << endl
       << "Query string is '" << ht.request().query_string << "' <br/>" << endl
       << "Referer is '" << ht.request().referer << "' <br/>" << endl
       << "if-modified-since is '" << ht.request().if_modified_since << "' <br/>" << endl
       << endl
       << "GET[a] = '" << http::htmlspecialchars(a) << "' <br/>" << endl
       << "GET[b] = '" << http::htmlspecialchars(b) << "' <br/>" << endl
       << "GET[c] = '" << http::htmlspecialchars(c) << "' <br/>" << endl
       ;
 
    // And we send it
    ht.send(ss.str(), 200);
  } else if(ht.request().method == http::rq_post) {
    // it's a POST request
 
    // GET getters from request:
    string a = ht.get("a");
    string b = ht.get("b");
    string c = ht.get("c");
 
    // POST
    string text = ht.post("text"); // If the content type not form-urlencoded, the data is in "text".
    double ccc  = ht.post_dbl("bbb"); // Fetch double from POST (NAN if missing or invalid)
    string ddd  = ht.post("ccc");     // Fetch string from POST
 
    // Response body
    std::stringstream ss;
    ss << "<h1>POST of " << http::htmlspecialchars( ht.request().path ) <<  " </h1><br/>" << endl
       << "GET[a]    = '" << http::htmlspecialchars(a) << "' <br/>" << endl
       << "GET[b]    = '" << http::htmlspecialchars(b) << "' <br/>" << endl
       << "GET[c]    = '" << http::htmlspecialchars(c) << "' <br/>" << endl
       << endl
       << "POST[text]= '" << http::htmlspecialchars(text) << "' <br/>" << endl
       << "POST[ccc]= " << ccc << " <br/>" << endl
       << "POST[ddd]= '" << http::htmlspecialchars(ddd) << "' <br/>" << endl
       ;
 
    ht.response().cache_ctrl = http::cache_ctrl_no_store;
    ht.send(ss.str(), 200);
  }
 
  while(ht.send() > 0); // We ensure that all data are sent. (flush buffer)
  return 0;
}

Beispiel: Mit Temp-Storage-Provider und Dateiuplod (multipart POST)

Example: With temp storage provider and file upload (multipart POST)

#include <http.hh>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sstream>
 
using namespace std;
 
string default_input = string()
+ "POST http://localhost/test/multipart/ HTTP/1.1\n"
+ "User-Agent: Mozilla/5.0 Firefox/12.0\n"
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n"
+ "Accept-Language: en-us,en;q=0.5\n"
+ "Accept-Encoding: gzip, deflate\n"
+ "Content-Type: multipart/form-data; boundary = ---------------------------41184676334\n"
+ "Content-Length:636\n"
+ "\n"
+ "-----------------------------41184676334\n"
+ "Content-Disposition: form-data; name=\"caption\"\n"
+ "\n"
+ "Images to send\n"
+ "-----------------------------41184676334\n"
+ "Content-Disposition: form-data; name=\"description\"\n"
+ "Content-Type: text/plain\n"
+ "\n"
+ "Those are images that are sent ...\n"
+ "-----------------------------41184676334\n"
+ "Content-Disposition: form-data; name=\"image1\"; filename=\"The-Image1.png\"\n"
+ "Content-Type: image/png\n"
+ "\n"
+ "01234567890\n"
+ "-----------------------------41184676334\n"
+ "Content-Disposition: form-data; filename=\"The-Image2.png\" ; name=\"image2\"\n"
+ "Content-Type: image/png\n"
+ "Content-Transfer-Encoding: binary\n"
+ "\n"
+ "01234567890\n"
+ "-----------------------------41184676334--";
 
 
// We use the http class with the http_ram_storage_provider, even for big files.
typedef sw::detail::basic_http<sw::detail::http_ram_storage_provider<> > http;
 
// Logging function
std::stringstream logstream;
void dolog(const string & s, int level)
{ logstream << "[" << level << "] " << s << endl; }
 
// And go
int main(int argc, char** argv)
{
  // Config
  http::config.keep_alive = true;               // Default header Connection: keep-alive
  http::config.server = "localhost";            // Default header Server: localhost
  http::config.implicit_clear = false;          // Free memory as soon as possible
  http::config.max_ram_content_length = 10000;  // Maximum POST/PUT length directly handled in RAM
  http::config.log_level = http::log_debug;     // Log level
  http::config.log_callback = dolog;            // Used logging function
  http::config.append_error_messages = true;    // Append error messages to error pages.
 
  // Instance
  http ht;
 
  // Input
  if(argc >= 2 && argv[1]) {
    // file input
    FILE* fp = ::fopen(argv[1], "rb");
    if(!fp) {
      ht.send("Failed to open input file", 404);
      return 404;
    }
    int r=1, fr=1;
    char buf[256];
    while(fr>0 && r>0) {
      fr = ::fread(buf, sizeof(char), 256, fp);
      if(fr < 0) break;
      r = ht.recv(buf, fr);
    }
    ::fclose(fp);
    r = ht.recv(buf, 0); // Ensure the object sees that it is EOF.
    if(r < 0) { ht.send(); return -r; };
  } else {
    // default input
    ht.recv(default_input.data(), default_input.length());
    int r = ht.recv(default_input.data(), 0); // Ensure the object sees the EOF.
    if(r < 0) { ht.send(); return -r; }
  }
 
  // Dump and send
  ht.response().content_type = "text/plain";            // Content-Type: text/plain
  ht.response().cache_ctrl = http::cache_ctrl_no_store; // Cache-Control: no-cache, no-store ...
 
  std::stringstream out;
  out << "Dump after receiving a multi part request:" << endl
      << endl
      << ht.dump() << endl
      << endl;
 
  for(http::upload_files_t::const_iterator it=ht.files().begin();
      it != ht.files().end(); ++it)
  {
    const http::upload_file_t &file = it->second;
    out << endl << "========= DATA OF " << file.name << " ======================" << endl;
    if(file.start < 0) {
      // Data is in the instance variable "data"
      out << file.data << endl;
    } else {
      // Data is in the storage, means normally in a file we can copy these data into
      // a target file or, as here, print it.
      char c;
      ht.storage().seek(file.start);
      for(unsigned long i=0; i<file.size; ++i) {
        ht.storage().read(&c, sizeof(char));
        out << c;
      }
      out << endl;
    }
  }
 
  out << endl << "========= LOG ====================================" << endl << logstream.str();
 
  ht.send(out.str(), 200);
  return 0;
}

Beispiel: Kleiner Server

Example: Small server

#include <http.hh>
#include <filesystem.hh>
#include <string>
#include <string.hh>
#include <socket.hh>
#include <iostream>
#include <iomanip>
#include <fcntl.h>
#include <signal.h>
 
#define log_verbose(X) { std::cerr << X << std::endl; }
#define log_verbose2(X) { std::cerr << X << std::endl; }
 
using namespace std;
using sw::fs;
using sw::rtrim;
using sw::trim;
 
#ifdef __MSC_VER
namespace { template <typename T> static int isnan(T d) { return _isnan(d); } }
#else
namespace { template <typename T> static int isnan(T d) { return std::isnan(d); } }
#endif
 
 
// <editor-fold desc="signal" defaultstate="collapsed">
volatile bool stop = false;
void signal_stop(int sig) {
  if(stop) ::exit(1);
  stop = true;
  switch(sig) {
    case SIGINT:  log_verbose("\n[SIGINT]\n"); break;
    case SIGQUIT: log_verbose("\n[SIGQUIT]\n"); break;
    case SIGTRAP: log_verbose("\n[SIGTRAP]\n"); break;
    case SIGABRT: log_verbose("\n[SIGABRT]\n"); ::exit(1); break;
  }
}
// </editor-fold>
 
// <editor-fold desc="log" defaultstate="collapsed">
//
// Logging function for http::config
//
void dolog(const string & s, int level)
{ (void) level; log_verbose(s); }
// </editor-fold>
 
// <editor-fold desc="acceptor_stats" defaultstate="collapsed">
//
// This class is only for statistics. It is not really related to the functionality
// of the http class.
//
class acceptor_stats
{
public:
 
  acceptor_stats(): nreceived(0), nsent(0), status(0)
  { id = curr_id++; gettime(created); connected=created; received=created; closed=created; }
 
  /**
   * Destructor
   */
  virtual ~acceptor_stats()
  { ; }
 
  /**
   * Get the time
   */
  static void gettime(struct timeval & what)
  { ::gettimeofday(&what, NULL); }
 
  /**
   * Evaluate and write the statistic
   */
  void push_me()
  {
    connected.tv_sec -= created.tv_sec; connected.tv_usec -= created.tv_usec;
    received.tv_sec -= created.tv_sec; received.tv_usec -= created.tv_usec;
    closed.tv_sec -= created.tv_sec; closed.tv_usec -= created.tv_usec;
    double t0 =  ((double)connected.tv_sec + ((double) connected.tv_usec)/1e6) ;
    double t1 =  ((double)received.tv_sec + ((double) received.tv_usec)/1e6) ;
    double t2 =  ((double)closed.tv_sec + ((double) closed.tv_usec)/1e6) ;
    {
      sw::sync::autolock al(_lock); // we lock before writing to ensure everything is line-by-line
      printf(
        "%6lu, %10.3f, %10.3f, %10.3f, %10u, %10u, %4d\n",
        id, t0*1000, t1*1000, t2*1000, nreceived, nsent, status
      );
      fflush(stdout);
    }
  }
 
  // And some variables to save stuff in until it's written
  unsigned long id;
  unsigned nreceived;
  unsigned nsent;
  int status;
  struct timeval created;
  struct timeval connected;
  struct timeval received;
  struct timeval closed;
  static unsigned long curr_id;
  static sw::sync::lock _lock;
};
unsigned long acceptor_stats::curr_id;
sw::sync::lock acceptor_stats::_lock;
// </editor-fold>
 
// <editor-fold desc="http_acceptor" defaultstate="collapsed">
//
// This is the http class we refer to, not sw::http, but this one. The difference is
// that we use our own storage provider:
//
typedef sw::detail::basic_http<sw::detail::http_ram_storage_provider<> > http;
 
//
// Here the interesting stuff takes place: This class inherits from the class http
// and the class sw::sock::tcp_acceptor, which is a communication handler class for
// the server. For each request the server class will instantiate new objects of that
// class and shovels the client requests to them.
//
template <typename T=int>
class http_acceptor : public sw::sock::tcp_acceptor, protected http
{
  typedef sw::sock::tcp_acceptor socket_t; // Convenience typedef
  typedef http http_t;                     // Convenience typedef
 
public:
 
  /**
   * Constructor
   */
  http_acceptor() : socket_t(), http_t()
  { }
 
  /**
   * Destructor, here we write out out statistics:
   */
  virtual ~http_acceptor()
  { stats.push_me(); }
 
protected:
 
  /**
   * This is the overloaded of our http read() method:
   * All we need to do is to glue to the recv method of the socket class.
   */
  long read(void* data, unsigned long sz)
  {
    long r = socket_t::recv(data, sz);
    if(r > 0) stats.nreceived += r; // And we set some statistics
    return r;
  }
 
  /**
   * This is the overload of our htttp write(): We pass the data to the
   * socket send() method:
   */
  long write(const void* data, unsigned long sz)
  {
    long r = socket_t::send(data, sz);
    if(r > 0) stats.nsent += r;
    return r;
  }
 
  /**
   * That one is called when the socket is connected.
   * We just save when.
   */
  void on_connected()
  { stats.gettime(stats.connected); }
 
  /**
   * This is a socket overload, too - and our main handler ... read below
   */
  void on_receive()
  {
    long r;
    // When this method is called, there are data to receive, so
    // we invoke the http recv method, which then invokes our glue code
    // read() above, and fetches the data internally. It tells us with
    // the return code if there is an error, or if no more data are
    // required.
    if((r=http_t::recv()) > 0) {
      // http_t::recv() has read all avaliable data and wants more
      return;
    } else if(r < -100) {
      // Error where we can inform the client (http status)
      stats.gettime(stats.received);
      http_t::send();
      socket_t::close();
      return;
    } else if(r < 0) {
      // That would be -1 == fatal, nothing to send, just quit
      stats.gettime(stats.received);
      socket_t::close();
      return;
    }
    // For the stats
    stats.gettime(stats.received);
 
    // Here we set the socket keepalive according to the client's preferences and HTTP version.
    socket_t::keepalive(http_t::request().keep_alive);
 
    // And here we evaluate the request itself:
    string path = base_path + request().path;
    path = fs::realpath(path);
    if(fs::is_dir(path)) path = rtrim(path, "/") + "/index.html";
    if(path.find("/.") != string::npos) {
      // Rudimentary prevention of escaping the doocument root with ../../ or
      // the like. It's really basic security and I do not recommend relying
      // only on that.
      http_t::send(404);
      socket_t::close();
    } else if(request().path == "/") {
      // Server root location path
      // We send a simple POST form back:
      response().content_type = "text/html; charset=utf-8";
      response().cache_ctrl = http::cache_ctrl_no_store;
      stringstream ss;
      ss << "<html><head></head><body>" << endl
         << "<h1>MEM /</h1>" << endl
         << "<form method=\"post\" action=\"" << request().path <<  "\">" << endl
         << "  <input type=\"text\" name=\"post_string\" value=\"" << "\"/>" << endl
         << "  <input type=\"text\" name=\"post_double\" value=\"" << "\"/>" << endl
         << "  <input type=\"submit\" name=\"post_submit\" value=\"submit\"/>" << endl
         << "</form>" << endl
         << "<br/><br/><pre>" << endl
         << endl
         << "is RAM handled = " << modified() << endl
         << "</pre></body></html>" << endl;
      http_t::send(ss.str(), 200);
    } else if(request().path == "/test/post/") {
      // Location /test/post/
      response().content_type = "text/html; charset=utf-8";
      response().cache_ctrl = http::cache_ctrl_no_store;
      stringstream ss;
      double d = post_dbl("post_double");
      string s = http::htmlspecialchars(post("post_string"));
      ss << "<html><head></head><body>" << endl
         << "<h1>Post test</h1>" << endl
         << "<form method=\"post\" action=\"" << request().path <<  "\">" << endl
         << "  <input type=\"text\" name=\"post_string\" value=\"" << s << "\"/>" << endl
         << "  <input type=\"text\" name=\"post_double\" value=\"" << setprecision(12) << d << "\"/>" << endl
         << "  <input type=\"submit\" name=\"post_submit\" value=\"submit\"/>" << endl
         << "</form>" << endl
         << "<br/><br/><pre>" << endl
         << "post_string = " << s << endl
         << "post_double = " << setprecision(12) << d << endl
         << endl
         << "is RAM handled = " << modified() << endl
         << "</pre></body></html>" << endl;
      http_t::send(ss.str(), 200);
    } else if(request().path == "/test/chunked/") {
      // Here we send chunked data back
      response().transf_encoding = http::tfe_chunked;
      response().content_type = "text/plain; charset=utf-8";
      response().cache_ctrl = http::cache_ctrl_no_store;
      double d = cookie_dbl("count");
      if(std::isnan(d)) d = 0;
      d += 1;
      cookie("count", d, time(0)+3600);
      stringstream ss;
      ss << "Counter = " << d << endl
         << "get[a] = " << get_dbl("a") << endl
         << "get[b] = " << get_dbl("b") << endl
       ;
      http_t::send(ss.str(), 200);
    } else if(!fs::is_file(path)) {
      // File not found
      http_t::send(404);
      socket_t::close();
    } else if(!fs::is_readable(path)) {
      // File not readable -> send 403
      http_t::send(403);
      socket_t::close();
    } else {
      // Found and readable: send the file
      http_t::send_file(path);
    }
    // Flush buffer
    while(http_t::send() > 0);
 
    // And close when we're done.
    socket_t::close();
  }
 
  /**
   * Socket overload. Called after the socket was closed and the thread is just about to exit.
   */
  void on_close()
  {
    stats.gettime(stats.closed);
    stats.status = response().status;
  }
 
public:
 
  /**
   * Instance variables
   */
  acceptor_stats stats;
  static string base_path;
};
 
template <typename T>
string http_acceptor<T>::base_path = string();
// </editor-fold>
 
// <editor-fold desc="http_server" defaultstate="collapsed">
//
// The server class. This one only listens and accepts with instances
// of our http acceptor implementation.
//
// To see what happens some tracings are added:
//
template <typename acceptor_type=http_acceptor<> >
class http_server : public sw::sock::tcp_server<acceptor_type>
{
  typedef acceptor_type acceptor_t;
  typedef sw::sock::tcp_server<acceptor_type> server_t;
 
public:
 
  http_server(): server_t()
  { log_verbose("http_server::http_server()"); }
 
  virtual ~http_server()
  { log_verbose("http_server::~http_server()"); }
 
private:
 
  void on_started()
  { log_verbose("http_server::on_started()"); }
 
  void on_stopped()
  { log_verbose("http_server::on_stopped()"); }
 
  bool on_request(acceptor_t & a)
  {
    // Special case: We don't refuse HTTP requests actively, instead we leave them
    // in the queue and clean after finished requests. If the server is overencumbered
    // the systems socket subsystem will let the client know.
    // The only thing we do it to wait one ms, so that we can maybe catch this
    // accept in the current loop.
    if(server_t::num_connected() >= server_t::max_clients()) {
      server_t::thread_t::usleep(1000);
      server_t::clear_closed();
    }
    log_verbose2("http_server::on_request(" << a.ip().str() << ")");
    return true;
  }
 
  void on_error(long no, const char* text)
  { (void) no; log_verbose("http_server::on_error(" << text << ")\n"); }
 
  void on_update()
  { server_t::clear_closed(); }
 
};
// </editor-fold>
 
// <editor-fold desc="main" defaultstate="collapsed">
//
// Last and least: main().
//
int main(int argc, char** argv)
{
  (void) argc; (void) argv;
  // Process signals
  ::signal(SIGINT, signal_stop);
  ::signal(SIGQUIT, signal_stop);
 
  // Http config
  http::config.keep_alive = true;
  http::config.server = "localhost";
  http::config.log_level = http::log_debug;
  http::config.log_callback = dolog;
  http::config.implicit_clear = false;
  http::config.max_ram_content_length = 1<<20;
  http_acceptor<>::base_path = fs::cwd() + "/http/htdocs";
 
  // Instance of our server
  http_server<> svr;
 
  // With IP and port
  svr.ip("127.0.0.1:8080");
 
  // Maximum simultaneously running acceptors
  svr.max_clients(128);
 
  // The number of clients that the system keeps in the queue until refusing new connections.
  svr.max_clients_pending(256);
 
  // And GO
  svr.start();
 
  // Wait until server stopped / or process signal
  while(svr.running()) { if(stop) svr.stop(); sw::thread::usleep(1000); }
 
  // Tell if we had an error on the listening socket
  if(svr.error()) log_verbose("EXIT ERROR: " << svr.error_text());
 
  // And that was it.
  return 0;
}
// </editor-fold>

Quelltext

Source code

// <editor-fold desc="Doc/license" defaultstate="collapsed">
/**
 * @package de.atwillys.cc.swl
 * @license BSD (simplified)
 * @author Stefan Wilhelm (stfwi)
 *
 * @file http.hh
 * @ccflags
 * @ldflags
 * @platform linux, bsd, windows
 * @standard >= c++11
 *
 * -----------------------------------------------------------------------------
 *
 * Simple HTTP protocol handling class for use in modules that shall be able
 * to read HTTP requests and generate HTTP responses.
 *
 * This is NOT a web server, more a capability base to implement request handlers
 * or CLI programs that can act optionally as CGI.
 *
 * Annotations:
 *
 *  - Headers are no string-vector-maps, but string-maps, means you always replace
 *    headers. Use comma separation (RFC2616) like "Cache-Control: no-cache, no-store"
 *    to concatenate header fields.
 *
 *  - Data are read and written binary. Possible encodings in header and body are
 *    therefore forwarded to the API / the output stream.
 *
 * -----------------------------------------------------------------------------
 * +++ BSD license header +++
 * Copyright (c) 2010-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: (1) Redistributions
 * of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer. (2) Redistributions in binary form must reproduce
 * the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * (3) Neither the name of atwillys.de nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * -----------------------------------------------------------------------------
 */
// </editor-fold>
#ifndef SW_HTTP_HH
#define SW_HTTP_HH
 
// <editor-fold desc="Includes/macros" defaultstate="collapsed">
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <limits>
#include <sstream>
#include <deque>
#include <algorithm>
#include <cctype>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <cmath>
 
#ifdef __MSC_VER
#include <sys\stat.h>
#include <sys\types.h>
#include <stdint.h>
namespace { namespace std { template <typename T> bool isnan(T d) { return !!_isnan(d); } } }
#ifndef ssize_t
#define ssize_t int
#endif
#ifndef S_ISREG
#define S_ISREG(M) (((M)&_S_IFREG)==_S_IFREG)
#endif
#else
#include <sys/stat.h>
#include <unistd.h>
#include <inttypes.h>
#endif
 
// </editor-fold>
 
namespace sw { namespace detail {
 
// <editor-fold desc="http_tmpstorage_provider" defaultstate="collapsed">
 
/**
 * Storage class for POST/PUT input exceeding the RAM storage limit.
 * THIS IS THE TEMPLATE FOR EXTERNALLY IMPLEMENTED STORAGE CLASSES.
 * The functions are similar to standard C basic I/O functions, except that the
 * basic_http<> class is not interested in internal variables like file pointers,
 * stream refs or handles. It's basically an interface not requiring RTTI.
 *
 * The main reason requiring this provider template is the platform dependent disk i/o
 * handling, more specifically locking the temporary files.
 *
 * Note: The basic_http<> class does not catch exceptions of this provider. The methods must not
 *       throw. Instead return negative.
 *
 * Note: The template return_t must be signed.
 */
template <typename return_t=int>
class http_tmpstorage_provider
{
public:
  /**
   * Constructors / Destructor
   */
  inline http_tmpstorage_provider()
  { ; }
 
  /**
   * Will not be used by basic_http<>
   */
  inline http_tmpstorage_provider(const http_tmpstorage_provider&)
  { ; }
 
  /**
   * Will not be used by basic_http<>
   */
  inline http_tmpstorage_provider & operator = (http_tmpstorage_provider&)
  { return *this; }
 
  /**
   * Note: close and delete the tmp file.
   */
  virtual ~http_tmpstorage_provider()
  { close(); }
 
  /**
   * Open a storage, return a negative value on error, 0 on success.
   * The argument sz is informational and tells how many bytes will be required - good to
   * e.g. deny (return e.g -1) or reserve space.
   *
   * Note: You should always lock the file.
   * Note: I/O MUST NOT be non-blocking. EAGAIN/EWOULDBLOCK is not differed from other errors.
   * @return return_t
   */
  virtual return_t open(unsigned long) throw()
  { return -1; }
 
 /**
  * Write to the storage, return negative on error, positive or 0 for number of bytes written.
  * This function must write all bytes or return an error (not non-blocking).
  * @param const void* d
  * @param unsigned long sz
  * @return return_t
  */
  virtual return_t write(const void*, unsigned long) throw()
  { return -1; }
 
  /**
   * Read from storage, return negative on error, positive or 0 for number of bytes read.
   * @param void* d
   * @param unsigned long sz
   * @return return_t
   */
  virtual return_t read(void*, unsigned long) throw()
  { return -1; }
 
  /**
   * Set the current position the byte specified with pos from the beginning of the file.
   * Return negative on error, 0 on success.
   * @return return_t
   */
  virtual return_t seek(unsigned long) throw()
  { return -1; }
 
  /**
   * Return the size in bytes of the storage, 0 on error.
   * @return unsigned long
   */
  virtual unsigned long size() throw()
  { return 0; }
 
  /**
   * Has to return true if the end of storage is reached or the storage is not open / invalid state.
   * @return bool
   */
  virtual bool eof() const throw()
  { return true; }
 
  /**
   * Close and cleanup (e.g. delete temporary file). Must not throw
   * @return void
   */
  virtual void close() throw()
  { }
};
// </editor-fold>
 
// <editor-fold desc="http_ram_storage_provider" defaultstate="collapsed">
template <typename return_t=int>
class http_ram_storage_provider : public http_tmpstorage_provider<return_t>
{
public:
 
  /**
   * Constructor
   */
  inline http_ram_storage_provider() : buf_(), pos_(0)
  { ; }
 
  /**
   * Copy constuctor
   */
  inline http_ram_storage_provider(const http_ram_storage_provider & o) : buf_(o.buf_), pos_(o.pos_)
  { ; }
 
  /**
   * Copy assignment
   */
  inline http_ram_storage_provider & operator = (http_ram_storage_provider & o)
  { buf_=o.buf_; pos_=o.pos_; return *this; }
 
  /**
   * Destructor
   */
  virtual ~http_ram_storage_provider()
  { http_ram_storage_provider::close(); }
 
  /**
   * Open the storage
   */
  return_t open(unsigned long sz) throw()
  { try { buf_.clear(); buf_.reserve(sz); return 0; } catch(...) { return -1; } }
 
  /**
   * Write to the storage
   */
  return_t write(const void* data, unsigned long sz) throw()
  {
    if(!data) return -1;
    if(sz == 0) return 0;
    try { buf_.reserve(buf_.length()+sz); } catch(...) { return -1; }
    const char* end = (const char*) data+sz;
    const char* p = (const char*) data;
    for(; p<end; p++) buf_ += (char)(*p);
    return sz;
  }
 
  /**
   * Read from the storage
   */
  return_t read(void* data, unsigned long sz) throw()
  {
    if(!data) return -1;
    if(!sz) return 0;
    if(pos_ >= buf_.length()) return 0;
    if(pos_+sz > buf_.length()) sz = buf_.length() - pos_;
    memcpy(data, buf_.data()+pos_, sz);
    pos_ += sz;
    return (return_t) sz;
  }
 
  /**
   * Seek to a position for reading / writing
   */
  return_t seek(unsigned long pos) throw()
  { pos_ = pos; if(pos_ >= buf_.length()) { pos_ =buf_.length(); return -1; } else return 0; }
 
  /**
   * Check if EOF
   */
  bool eof() const throw()
  { return pos_ >= buf_.length(); }
 
  /**
   * Get the total size of the data in the storage
   */
  unsigned long size() throw()
  { return buf_.length(); }
 
  /**
   * Close it
   */
  void close() throw()
  { std::string s; buf_.swap(s); pos_=0; }
 
public:
 
  // This method is not part of the storage_provider interface, but
  // a special case used in basic_http POST parsing.
  void swap_string_buffer_with(std::string & s)
  { buf_.swap(s); pos_=0; }
 
private:
 
  /**
   * The buffer used
   */
  std::string buf_;
 
  /**
   * Current position
   */
  unsigned long pos_;
};
// </editor-fold>
 
/**
 * Http main class template, uses detail for a connection provider class (e.g. for
 * stdio/socket/unix socket) and storage provider class (normally for disk i/o).
 */
template <class Storage_Type = http_tmpstorage_provider<int> >
class basic_http
{
public:
 
  // <editor-fold desc="read/write overloads" defaultstate="collapsed">
  /**
   * Reading method, overload e.g. for socket use. Return the number of bytes read
   * or negative on error.
   * @param void* data
   * @param unsigned long sz
   * @return long
   */
  virtual long read(void* data, unsigned long sz)
  {
    #ifdef __MSC_VER
    return _read(_fileno(stdin), data, sz);
    #else
    return ::read(::fileno(stdin), data, sz);
    #endif
  }
 
  /**
   * Writing method, overload e.g. for socket use. Return the number of bytes written
   * or negative on error.
   * @param const void* data
   * @param unsigned long sz
   * @return long
   */
  virtual long write(const void* data, unsigned long sz)
  {
    #ifdef __MSC_VER
    return _write(_fileno(stdout), data, sz);
    #else
    return ::write(::fileno(stdout), data, sz);
    #endif
  }
  // </editor-fold>
 
public:
 
  // <editor-fold desc="Types/enums" defaultstate="collapsed">
  //////////////////////////////////////////////////////////////////////////////
  // Types
 
  typedef std::string str_t;
  typedef char char_t;
  typedef std::map<str_t, str_t> str_map_t;
  typedef typename str_t::size_type sz_t;
  typedef std::basic_ostream<char_t> os_t;
  typedef std::basic_istream<char_t> is_t;
  typedef std::basic_stringstream<char_t> ss_t;
  typedef std::vector<std::pair<int64_t,int64_t> > range_t; // range elements: from -> to
  typedef Storage_Type storage_t;
  struct upload_file_t;
  typedef std::map<str_t, upload_file_t> upload_files_t;
 
  /**
   * HTTP request methods
   */
  typedef enum {
    rq_get=0, rq_head, rq_post, rq_put, rq_delete, rq_options, rq_move, rq_copy, rq_mkcol,
    rq_lock, rq_unlock, rq_trace, rq_invalid
  } request_method_t;
 
  /**
   * Processor state
   */
  typedef enum { st_r_header=0, st_r_body, st_r_done, st_r_error, st_s_header,
    st_s_body, st_done, st_error } state_t;
 
  /**
   * Logging level note: you need to set a callback in the config, too in order to log.
   */
  typedef enum {
    log_crit=0, log_error, log_warn, log_notice, log_debug
  } log_level_t;
 
  /**
   * Content type identifier. Used to choose how parse POST.
   */
  typedef enum {
    cont_tp_any=0, cont_tp_text_plain, cont_tp_urlenc, cont_tp_multipart
  } content_type_id_t;
 
  /**
   * Response cache control
   */
  typedef enum  {
    cache_ctrl_none=0,       // No info
    cache_ctrl_public=0x1,   // Cache-Control: public
    cache_ctrl_private=0x2,  // Cache-Control: private
    cache_ctrl_no_cache=0x4, // Cache-Control: no-cache --> max_age=0
    cache_ctrl_no_store=0x8  // Cache-Control: no-store --> no-cache max_age=0
  } cache_ctrl_t;
 
  /**
   * Response transfer-encoding setting. Note: Chunked is done in this class, deflate and
   * gzip are only set in the response header, the input data has to be zipped/deflated already.
   */
  typedef enum  {
    tfe_omit=0, tfe_chunked, tfe_deflate, tfe_gzip
  } transfer_enc_t;
 
  // </editor-fold>
  // <editor-fold desc="config_t" defaultstate="collapsed">
 
  /**
   * Configuration
   */
  struct config_t
  {
    /**
     * Log level
     */
    log_level_t log_level;
 
    /**
     * Log callback
     */
    void (*log_callback)(const str_t &, int);
 
    /**
     * Maximum total size of the header. Note that the header is always copied into the
     * RAM before processing it.
     */
    unsigned max_request_header_size;
 
    /**
     * Maximum length of the URI including query string and hash string
     */
    unsigned max_request_path_length;
 
    /**
     * The maximum string size of GET/POST variable names
     */
    unsigned max_arg_key_size;
 
    /**
     * The maximum string size of GET/POST variable values
     */
    uint32_t max_arg_val_size;
 
    /**
     * Defines the maximum specified request content length
     */
    uint64_t max_content_length;
 
    /**
     * Defines the maximum specified request content length that the parser will implicitly
     * store in RAM. If exceeded, the body content will be redirected into a temporary file.
     * Note: max_content_length is still respected, so max_ram_content_length must be smaller than
     * max_content_length, otherwise temporary files are never used.
     */
    uint32_t max_ram_content_length;
 
    /**
     * Default max-age for Cache-Control (-1 == omit, 0 --> "no-cache" if cache_ctrl not set)
     */
    int32_t max_age;
 
    /**
     * Default response cache control setting
     */
    cache_ctrl_t cache_ctrl;
 
    /**
     * Host that will be shown in the header field. Note, if you like to check the host matching
     * the requested host, you have to do this yourself.
     */
    str_t server;
 
    /**
     * Default setting for Connection: keep-alive/close header field.
     */
    bool keep_alive;
 
    /**
     * Specifies if the request and response structures shall be freed (as much as possible)to
     * save memory when sending the header information to the client.
     */
    bool implicit_clear;
 
    /**
     * Specifies weather error messages - if they are set before - shall be added to
     * error pages sent. That applies only if no custom error pages are sent (response data
     * empty).
     */
    bool append_error_messages;
 
    /**
     * Initialisation
     */
    inline config_t() : log_level(log_crit), log_callback(0), max_request_header_size(1u<<14),
      max_request_path_length(1u<<13), max_arg_key_size(64), max_arg_val_size(1ul<<14),
      max_content_length(1ul<<30), max_ram_content_length(1ul<<15), max_age(-1),
      cache_ctrl(cache_ctrl_none), server(), keep_alive(0), implicit_clear(0),
      append_error_messages(false)
    { ; }
 
    /**
     * Checks and sanatises config entries. Returns 0 if all was ok, >0 if entries were modified,
     * and <0 on error.
     * @return int
     */
    int check_sanatize()
    {
      /// stfwi: add logging for individual errors
      size_t sz;
      int nerrs = 0, nchanged=0;
      if(log_level < log_crit) { ++nerrs; log_level = log_crit; }
      if(log_level > log_debug) { ++nerrs; log_level = log_debug; }
      if(max_request_header_size < 256) { ++nerrs; max_request_header_size = 256; }
      if(max_request_path_length < 32)  { ++nerrs; max_request_path_length = 32; }
      if(max_arg_key_size < 8) { ++nchanged; max_arg_key_size = 32; }
      if(max_arg_val_size < 8) { ++nchanged; max_arg_val_size = 32; }
      if(max_age <-1) { ++nerrs; max_age=-1; }
      if(cache_ctrl < 0) { ++nerrs; cache_ctrl = cache_ctrl_none; }
      // max_ram_content_length can be 0 or > max_content_length
      // max_content_length can be 0 if e.g. only get/head shall be accepted
      sz = server.length(); server = tr(server); if(sz != server.length()) nchanged++;
      return nerrs > 0 ? -nerrs : nchanged;
    }
 
  };
  // </editor-fold>
  // <editor-fold desc="buffer_t" defaultstate="collapsed">
  /**
   * Small buffer class allowing to quickly push data and implicitly freeing allocated memory.
   */
  class buffer_t
  {
  private:
    typedef std::deque<std::vector<char> > buf_t;
    buf_t buf_;
    long sz_;
    long r_;   // read position
    char no_;  // used to always provide a valid pointer, not NULL
  public:
 
    buffer_t() : buf_(), sz_(0), r_(0), no_(0)
    { ; }
 
    virtual ~buffer_t()
    { ; }
 
    inline bool empty() const
    { return buf_.empty(); }
 
    inline void clear()
    { buf_.clear(); sz_=r_=0; }
 
    inline sz_t size() const
    { return (sz_t) sz_; }
 
    inline const char* chunk() const
    { return (!sz_) ? (&no_) : buf_.front().data()+r_; }
 
    inline sz_t chunk_size() const
    { return (sz_<=0) ? 0 : buf_.front().size() - r_; }
 
    void advance(sz_t n)
    {
      if(!sz_) return;
      if((long)n >= sz_) { clear(); return; }
      long bs = (long) buf_.front().size(); // already caught by sz_ type limits above
      sz_ -= n;
      if((r_ += n) < bs) return;
      buf_.pop_front();
      r_ -= bs; // 0 or positive, read position in next chunk
      if(buf_.empty()) clear(); // double check
    }
 
    void chunk(const char *c, sz_t sz)
    {
      if(!c || !sz) return; // no error, we just don't do it
      buf_.push_back(std::vector<char>(sz));
      memcpy(buf_.back().data(), c, sz);
      sz_ += sz;
    }
 
    void chunk(const str_t & s)
    { chunk(s.data(), s.length()); }
 
    void prepend(const str_t & s)
    {
      if(!s.length()) return;
      buf_.push_front(std::vector<char>(s.length()));
      memcpy(buf_.front().data(), s.c_str(), s.length());
      sz_ += s.length();
    }
  };
  // </editor-fold>
  // <editor-fold desc="upload_file_t" defaultstate="collapsed">
  struct upload_file_t
  {
    inline upload_file_t() : start(-1), size(0), name(), filename(), content_type(),
      content_encoding(), data()
    {;}
 
    virtual ~upload_file_t()
    {;}
 
    inline upload_file_t(const upload_file_t& f) : start(f.start), size(f.size), name(f.name),
        filename(f.filename), content_type(f.content_type), content_encoding(), data(f.data)
    {;}
 
    upload_file_t& operator=(const upload_file_t& f)
    { start=f.start; size=f.size; name=f.name; filename=f.filename; content_type=f.content_type;
      content_encoding=f.content_encoding; data=f.data; return *this; }
 
    int32_t start;
    unsigned long size;
    str_t name, filename, content_type, content_encoding, data;
 
  };
  // </editor-fold>
  // <editor-fold desc="request_t" defaultstate="collapsed">
  /**
   * Request data type
   */
  struct request_t
  {
    request_method_t method;     // Enum parsed request method ("GET" = rq_get etc)
    str_t service;               // If given, e.g. "http", "https", "ws" ...
    str_t host;                  // Host part, from URL or header "Host:"
    str_t path;                  // Path of URI
    str_t query_string;          // URI after "?" and before "#"
    str_t query_hash;            // URI after "#"
    int http_version;            // Int of METHOD URI/PATH HTTP1.<http_version>
    str_t accept;                // Header "Accept:"
    long content_length;         // Header "Content-Length:"
    str_t content_type;          // Header "Content-Type:"
    content_type_id_t content_type_id; // Content type identifier for parsing POST
    str_map_t headers;           // Additional headers not listed above
    str_map_t get;               // GET query variables
    str_map_t post;              // POST variables
    str_map_t cookie;            // Cookie from header, to response
    upload_files_t files;        // Information about uploaded files
    time_t if_modified_since;    // For sending a 304 header (Not modified)
    time_t if_unmodified_since;  // For sending a 412 header (Precondition failed) (force abort request)
    range_t range;               // Range: bytes ...
    str_t referer;               // Referer/Referrer:
    str_t user_agent;            // User-agent:
    std::string data;            // POST/PUT, use str_t::data() function for binary access
    bool keep_alive:1;           // Connection: keep-alive (or close), what the client prefers
    bool no_cache:1;             // Cache-Control: no-cache set by client
 
    /**
     * Default constructor
     */
    inline request_t() : method(rq_invalid), service(), host(), path(), query_string(), query_hash(),
      http_version(), accept(), content_length(0), content_type(), content_type_id(cont_tp_any),
      headers(), if_modified_since(0), if_unmodified_since(0), range(), data(), keep_alive(false),
      no_cache(false)
    { ; }
 
    /**
     * Frees container memory where possible.
     */
    void clear()
    {
      headers.clear(); get.clear(); post.clear(); cookie.clear(); files.clear(); sc(content_type);
      sc(accept); sc(query_string); sc(path); sc(service); sc(host); sc(referer); sc(user_agent);
      sc(query_hash);
    }
  };
  // </editor-fold>
  // <editor-fold desc="response_t" defaultstate="collapsed">
  /**
   * Response data type
   */
  struct response_t
  {
    uint16_t status;                // Response status (default=0 ---> will send 500 )
    time_t date;                    // Date (default -1 === auto)
    time_t last_modified;           // Local last modified timestamp of the resource (-1===omit)
    int32_t content_length;         // Content length in bytes of the resource
    str_t content_type;             // Mime content type
    str_t etag;                     // Etag of the resource
    str_map_t cookie;               // Cookies to set keys are variable names
    str_map_t headers;              // Additional headers not listed above
    buffer_t data;                  // Buffer for header and text data
    int32_t max_age;                // Cache-Control: max-age=<seconds>
    cache_ctrl_t cache_ctrl;        // Cache-Control: ... settings
    transfer_enc_t transf_encoding; // Compression used in Content-Encoding header (read compression_t !)
    bool keep_alive:1;              // Connection: keep-alive (or close)
    bool accept_ranges:1;           // Add "Accept-Ranges: bytes" to header if true
 
    inline response_t() : status(0), date(0), last_modified(0), content_length(-1), content_type(),
      etag(), headers(), data(), max_age(config.max_age), cache_ctrl(config.cache_ctrl),
      transf_encoding(tfe_omit), keep_alive(config.keep_alive), accept_ranges(0)
    { ; }
 
    void clear(bool with_data=false)
    { headers.clear(); sc(content_type); cookie.clear(); if(with_data) data.clear(); }
  };
  // </editor-fold>
 
public:
 
  // <editor-fold desc="Constructors/destructor" defaultstate="collapsed">
 
  //////////////////////////////////////////////////////////////////////////////
  // Instance
 
  /**
   * Constructors
   */
  basic_http() : state_(st_r_header), rlen_(0), lch_(0), n_sent_(0), rx_(), rq_(), rs_(),
    storage_(), error_(0), error_text_()
  { ; }
 
  explicit basic_http(const storage_t & st) :
    state_(st_r_header), rlen_(0), lch_(0), n_sent_(0), rq_(), rs_(), rx_(), storage_(st),
    error_(0), error_text_()
  { ; }
 
  /**
   * Destructor
   */
  virtual ~basic_http()
  { ; }
 
  // No copy
  basic_http(const basic_http & ht) = delete;
  basic_http& operator = (const basic_http & ht) = delete;
 
  // </editor-fold>
 
public:
 
  // <editor-fold desc="Getters/setters" defaultstate="collapsed">
 
  /**
   * Returns the process state
   * @return state_t
   */
  inline state_t state() const throw()
  { return state_; }
 
  /**
   * Returns the currently set error code, 0 if no error is set.
   * @return int
   */
  inline int error() const throw()
  { return error_; }
 
  /**
   * Returns the currently set error message or empty if not set yet.
   * @return const str_t &
   */
  inline const str_t & error_msg() const throw()
  { return error_text_; }
 
  /**
   * Returns the request
   * @return const request_t &
   */
  inline const request_t & request() const throw()
  { return rq_; }
 
  /**
   * Returns the request
   * @return request_t &
   */
  inline request_t & request() throw()
  { return rq_; }
 
  /**
   * Returns the response
   * @return const response_t &
   */
  inline const response_t & response() const throw()
  { return rs_; }
 
  /**
   * Returns the response
   * @return response_t &
   */
  inline response_t & response() throw()
  { return rs_; }
 
  /**
   * Returns a reference to the storage provider object
   * @return storage_t &
   */
  inline storage_t & storage() throw()
  { return storage_; }
 
  /**
   * Returns a reference to the storage provider object
   * @return const storage_t &
   */
  inline const storage_t & storage() const throw()
  { return storage_; }
 
  /**
   * Returns the list of url-decoded GET variables
   * @return const str_map_t &
   */
  inline const str_map_t & get() const throw()
  { return rq_.get; }
 
  /**
   * Returns the value of a GET variable or an empty string if not found
   * @param const str_t & var
   * @return const str_t &
   */
  inline const str_t & get(const str_t & var) const throw()
  {
    try {
      str_map_t::const_iterator it = rq_.get.find(var);
      return it==rq_.get.end() ? nstr : it->second;
    } catch(...) {
      return nstr;
    }
  }
 
  /**
   * Convenience numeric return of get(const str_t & var). Returns NAN if key not found or
   * conversion failed.
   * @param const str_t & var
   * @return double
   */
  inline double get_dbl(const str_t & var) const throw()
  { const str_t & s = get(var); return s.empty() ? nan : s2d(s); }
 
  /**
   * Returns the list of url-decoded POST variables
   * @return const str_map_t &
   */
  inline const str_map_t & post() const throw()
  { return rq_.post; }
 
  /**
   * Returns the value of a POST variable or an empty string if not found
   * @param const str_t & var
   * @return const str_t &
   */
  inline const str_t & post(const str_t & var) const throw()
  {
    try {
      str_map_t::const_iterator it = rq_.post.find(var);
      return it==rq_.post.end() ? nstr : it->second;
    } catch(...) {
      return nstr;
    }
  }
 
  /**
   * Convenience numeric return of post(const str_t & var). Returns NAN if key not found or
   * conversion failed.
   * @param const str_t & var
   * @return double
   */
  inline double post_dbl(const str_t & var) const throw()
  { const str_t & s = post(var); return s.empty() ? nan : s2d(s); }
 
  /**
   * Returns the list of cookies sent with the request
   * @return const str_map_t &
   */
  inline const str_map_t & cookie() const throw()
  { return rq_.cookie; }
 
  /**
   * Returns the value of a COOKIE variable or an empty string if not found
   * @param const str_t & var
   * @return const str_t &
   */
  inline const str_t & cookie(const str_t & var) const throw()
  {
    str_map_t::const_iterator it = rq_.cookie.find(var);
    return (it==rq_.cookie.end()) ? nstr : it->second;
  }
 
  /**
   * Convenience numeric return of cookie(const str_t & var). Returns NAN if key not found or
   * conversion failed.
   * @param const str_t & var
   * @return double
   */
  inline double cookie_dbl(const str_t & var) const throw()
  { const str_t & s = cookie(var); return s.empty() ? nan : s2d(s); }
 
  /**
   * Set a cookie in the response header
   */
  basic_http & cookie(const str_t & name, const str_t & value, time_t expires=0, const str_t & domain="",
    const str_t & path="", bool secure=false, bool httponly=false)
  {
    if(!name.length()) return *this;
    str_t ck;
    ck.reserve(name.length()+value.length()+32);
    ck += urlencode(name); ck += "="; ck += urlencode(value);
    if(expires > 0) { ck += "; expires="; ck += timestamp2string(expires); }
    if(domain.length() > 0) { ck += "; domain="; ck += domain; }
    if(path.length() > 0) { ck += "; path="; ck += path; }
    if(secure) { ck += "; Secure"; }
    if(httponly) { ck += "; HttpOnly"; }
    rs_.cookie[name] = ck;
    return *this;
  }
 
  /**
   * Set a cookie in the response header
   */
  basic_http & cookie(const str_t & name, double d, time_t expires=0, const str_t & domain="",
    const str_t & path="", bool secure=false, bool httponly=false)
  { return std::isnan(d) ? cookie(name,"") : cookie(name,d2s(d),expires,domain,path,secure,httponly); }
 
  /**
   * Returns the map of uploaded files
   * @return const upload_files_t &
   */
  const upload_files_t & files() const
  { return rq_.files; }
 
  /**
   * Returns the number of bytes that have to be still pushed into the response().data buffer
   * to reach the amount of bytes specified in the Content-Length response header field.
   * If no Content-Length is specified (-1) returns 0.
   * @return sz_t
   */
  inline sz_t num_send_bytes_expected_left() const throw()
  { return (rs_.content_length<0) ? 0 : (n_sent_<=0 ? rs_.content_length :
    (rs_.content_length-(n_sent_+rs_.data.size()))); }
 
  /**
   * Returns the number of bytes the parser expects until the request data is completely submitted.
   * @return sz_t
   */
  inline sz_t num_read_bytes_expected_left() const throw()
  { return rlen_ < 0 ? 0 : (sz_t)rlen_; }
 
  /**
   * Returns if the request content body did not have to be stored with the storage provider
   * object.
   * @return bool
   */
  inline bool ram_handled() const throw()
  { return (unsigned long)rq_.content_length <= config.max_ram_content_length; }
 
  /**
   * Returns false if the `response().last_modified` is set and matches the conditions of
   * if-modified-since/if-unmodified-since OR `response().etag` is set and matches the conditions
   * of If-None-Match.
   */
  bool modified() const throw()
  {
    if(rq_.no_cache) {
      return true; // Client wants a fresh version
    }
    if(rs_.last_modified && rq_.if_modified_since) {
      return rs_.last_modified > rq_.if_modified_since;
    }
    if(!rs_.etag.empty()) {
      try {
        typename str_map_t::const_iterator it;
        if((it = rq_.headers.find("if-none-match")) != rq_.headers.end()) {
          return tr(it->second, "\"\t ") != rs_.etag;
        }
      } catch(...) {
        return true;
      }
    }
    return true;
  }
 
  // </editor-fold>
 
  // <editor-fold desc="header()" defaultstate="collapsed">
 
  /**
   * Set or remove a response header, so that it will appear in the header as
   * "key: value\n". Empty keys are ignored, empty values cause to delete a header.
   *
   * @param const str_t& key
   * @param const str_t& value
   * @return basic_http &
   */
  inline basic_http & header(const str_t& key, const str_t& value)
  {
    if(key.empty()) return *this;
    if(!value.empty()) { rs_.headers[lc(key)] = value; } else { rs_.headers.erase(lc(key)); }
    return *this;
  }
 
  /**
   * Returns a header setting or "" if not found.
   * @param const str_t& key
   * @return const str_t &
   */
  inline const str_t & header(const str_t& key) const throw()
  {
    try {
      str_map_t::const_iterator it = rs_.headers.find(lc(key));
      return it == rs_.headers.end() ? nstr : it->second;
    } catch(...) {
      return nstr;
    }
  }
 
  /**
   * Compose the response header text
   * @return str_t
   */
  str_t header() const
  {
    const char* nl = "\r\n";
    str_t hd; hd.reserve(256+128);
    hd += "HTTP/1."; hd += ('0' + rq_.http_version); hd += " ";
    hd += reponse_status_text(rs_.status); hd += nl;
    hd += str_t("Date: ") + timestamp2string((!rs_.date) ? time(0) : rs_.date) + nl;
    if(config.server.length()) hd += str_t("Server: ") + config.server + nl;
    if(rs_.status < 200 || rs_.status == 304 || rs_.status == 204) {
      hd += str_t("Connection: close") + nl;
    } else {
      if(rs_.content_type.length()) hd += str_t("Content-Type: ") + rs_.content_type + nl;
      if(rs_.content_length >= 0) hd += str_t("Content-Length: ") + l2s(rs_.content_length) + nl;
      if(rs_.last_modified > 0) hd += str_t("Last-Modified: ") + timestamp2string(rs_.last_modified) + nl;
      if(!rs_.etag.empty()) hd += str_t("Etag: \"") + rs_.etag + '\"' + nl;
      if(rs_.max_age >= 0 || rs_.cache_ctrl) {
        str_t max_age = rs_.max_age >= 0 ? (str_t(", max-age=") + l2s(rs_.max_age)) : str_t();
        cache_ctrl_t cc = rs_.cache_ctrl;
        if(!cc) cc = (rs_.max_age==0) ? cache_ctrl_no_cache : cache_ctrl_public;
        switch(cc) {
          case cache_ctrl_no_store:
            hd += str_t("Cache-Control: no-cache, no-store, max-age=0, must-revalidate") + nl;
            if(rq_.http_version == 0) hd += str_t("pragma: no-cache") + nl;
            break;
          case cache_ctrl_no_cache:
            hd += str_t("Cache-Control: no-cache, max-age=0") + nl;
            if(rq_.http_version == 0) hd += str_t("pragma: no-cache") + nl;
            break;
          case cache_ctrl_private:
            hd += str_t("Cache-Control: private") + max_age + nl;
            break;
          case cache_ctrl_public:
            hd += str_t("Cache-Control: public") + max_age + nl;
            break;
          default:
            // Default public with max-age
            if(rs_.max_age > 0) hd += str_t("Cache-control: public") + max_age + nl;
        }
      }
      if(rq_.http_version > 0 && rs_.accept_ranges) {
        hd += str_t("Accept-Ranges: Bytes") + nl;
      }
      if(rs_.transf_encoding) {
        hd += str_t("Transfer-Encoding: ");
        switch(rs_.transf_encoding) {
          case tfe_chunked: hd += str_t("chunked")+nl; break;
          case tfe_gzip:    hd += str_t("gzip")+nl; break;
          case tfe_deflate: hd += str_t("deflate")+nl; break;
          default:          hd += nl;
        }
      }
      if(rq_.http_version < 1 || !rs_.keep_alive) { // HTTP/1.1 is default keep-alive
        hd += str_t("Connection: ") + (rs_.keep_alive ? "keep-alive" : "close") + nl;
      }
      for(str_map_t::const_iterator it=rs_.cookie.begin(); it != rs_.cookie.end(); ++it) {
        if(it->second.length()) { hd += "Set-Cookie: "; hd += it->second + nl; }
      }
      for(str_map_t::const_iterator it=rs_.headers.begin(); it != rs_.headers.end(); ++it) {
        if(it->first.length() > 3 && !it->second.empty()) {
          str_t s(it->first);
          s[0] = ::toupper(s[0]);
          str_t::iterator c = s.begin()+1, e = s.end()-1;
          while(c != e) if(*c == '-') *(++c) = ::toupper(*(c)); else ++c;
          hd += s + ": " + it->second + nl;
        }
      }
    }
    hd += nl;
    return hd;
  }
  // </editor-fold>
 
  // <editor-fold desc="recv()" defaultstate="collapsed">
  /**
   * Process by given file descriptor of an opened file/socket/pipe. If that is blocking or not etc
   * needs to be set before calling this function. Returns ...
   *
   *   0 when all data are processed (EOF),
   *   1 if more data are waited for (read said EAGAIN/EWOULDBLOCK), so you have to ::select()
   *  -1 on a connection or fatal read error - means you have to close the connection.
   *  -> Other negative values are inverted HTTP error status codes.
   *
   * @return int
   */
  int recv()
  {
    if(error_ != 0) return -error_; // Don't continue doing anything if an error has occurred.
    char s[128];
    ssize_t n=0;
    for(n = this->read(s, sizeof(s)); n > 0; n = this->read(s, sizeof(s))) {
      int r = recv(s,n);
      if(r <= 0) return r;
    }
    if(n > 0 || n == EWOULDBLOCK) {
      return 1;
    } else if(n < 0) {
      str_t s = str_t("recv(): read() failed, error code is ") + l2s(n);
      return -err(500, s.c_str()); // From EINTR over EBADF,EINVAL,..., they seem to be all fatal.
    } else if(state_ < st_r_body) {
      return -err(400, "recv(): read eof before header parsed");
    } else {
      return 0;
    }
  }
 
  /**
   * Process binary/text input data read  from the socket or CGI pipe. Returns 1 if it needs more
   * data, 0 if the input is complete, or negative on error. The input length sz==0 will be
   * interpreted as end of input. Negative numbers are error codes corresponding to (negated)
   * HTTP errors.
   * @param const char* rq
   * @param sz_t sz
   * @return int
   */
  int recv(const char* rq, sz_t sz)
  {
    if(error_ != 0) return -error_; // Don't continue doing anything if an error has occurred.
    sz_t i = 0;
    switch(state_) {
      case st_r_header:
        if(!rq || !sz) return -err(500);
        rx_.reserve(rx_.length()+sz);
        while(i < sz) {
          char c = rq[i++];
          rx_ += c;
          if((!(i & 0xff)) && rx_.length() > config.max_request_header_size) {
            return -err(413); // Entity Too Large, we check all 256 adding steps
          } else if(c != '\n' && c != '\r') {
            lch_ = 0;
          } else {
            // Check double newline --> eoh
            const uint32_t cr='\r', lf='\n', crlf = (cr<<8)|lf;
            const uint32_t crcr=(cr<<8)|cr;
            const uint32_t lflf=(lf<<8)|lf;
            const uint32_t clcl=((crlf<<16)|crlf);
            lch_ = ((lch_<<8)|(c&0xff)) & 0xffffffff;
            if((lch_ == crcr)
            || (lch_ == lflf)
            || (lch_ == clcl)
            ) {
              // (PHASE 3) Header transferred
              switch(rq_.method) {
                case rq_get:
                case rq_head:
                case rq_delete:
                case rq_trace:
                  state_ = st_r_done;
                  rlen_ = 0;
                  sc(rx_);
                  return 0;
                case rq_post: // Body required
                case rq_put:
                  if(rq_.content_length < 0) {
                    return -err(411); // Length required
                  }
                case rq_mkcol: // Body optional
                case rq_unlock:
                case rq_lock:
                case rq_copy:
                case rq_move:
                case rq_options:
                  if(rq_.content_length <= 0) {
                    // Post, Put, ...: no content body.
                    state_ = st_r_done; sc(rx_);
                    return 0;
                  }
                  if(rq_.content_type.length() == 0) rq_.content_type = "text/plain; charset=us-ascii"; // RFC822
                  rlen_ = rq_.content_length;
                  if(rlen_ < 0) { sz -= (-rlen_); rlen_ = 0; } // Not more input bytes than needed
                  if((unsigned long)rq_.content_length <= config.max_ram_content_length) {
                    // Input is copied into the RAM until the request is completely received, then
                    // parsed if applicable or left in the binary buffer.
                    rq_.data.reserve(rq_.content_length);
                    for(; i<sz && rlen_>0; ++i,--rlen_) rq_.data += rq[i]; // Copy rest of the buffer
                  } else {
                    // Input is redirected into a storage defined externally (normally a file).
                    rq_.data.reserve(sz-i+2);
                    for(; i<sz && rlen_>0; ++i,--rlen_) rq_.data += rq[i];
                    if(storage_.open(rq_.content_length) < 0) { // rq_.content_length>0 here
                      sc(rq_.data);
                      return -err(500, "Failed to open storage");
                    } else if(storage_.write(rq_.data.data(), rq_.data.length()) < 0) {
                      sc(rq_.data);
                      return -err(500, "Failed to write storage");
                    }
                    sc(rq_.data);
                  }
                  state_ = st_r_body;
                  break;
                default:
                  log(str_t("Invalid request method enum ")+l2s(rq_.method));
              }
            } else if(rq_.method == rq_invalid) {
              // (PHASE 1) That would be a head line ...
              rx_ = rtr(rx_, "\r\n\t ");
              std::string s; s.reserve(32);
              std::string::size_type k,l, len=rx_.length();
              for(k=0; k<len && !::isspace(rx_[k]); k++) s += ::toupper(rx_[k]);
              if(k == len) return -err(400, "missing path and version");
              if(s == "GET") rq_.method = rq_get;
              else if(s == "POST") rq_.method = rq_post;
              else if(s == "HEAD") rq_.method = rq_head;
              else if(s == "PUT") rq_.method = rq_put;
              else if(s == "DELETE") rq_.method = rq_delete;
              else if(s == "OPTIONS") rq_.method = rq_options;
              else if(s == "MOVE") rq_.method = rq_move;
              else if(s == "COPY") rq_.method = rq_copy;
              else if(s == "MKCOL") rq_.method = rq_mkcol;
              else if(s == "LOCK") rq_.method = rq_lock;
              else if(s == "UNLOCK") rq_.method = rq_unlock;
              else if(s == "TRACE") rq_.method = rq_trace;
              else return -err(400, "unknown method");
              for(; k<len && ::isspace(rx_[k]); k++); // ommit spaces between method and path
              rq_.path.reserve(32);
              for(; k<len && !::isspace(rx_[k]); k++) rq_.path += rx_[k];
              if(rq_.path.length() > config.max_request_path_length) return -err(414); // URI Too Long
              if((l=rq_.path.find_first_of('#')) != str_t::npos) { // Also RFC3986 delim
                rq_.query_hash = (l<rq_.path.length()-1) ? rq_.path.substr(l+1) : str_t();
                rq_.path.erase(l);
              }
              if((l=rq_.path.find_first_of('?')) != str_t::npos) {
                rq_.query_string = (l<rq_.path.length()-1) ? rq_.path.substr(l+1) : str_t();
                rq_.path.erase(l);
                int i;
                if((i = parse_url_encoded(rq_.query_string, rq_.get, false, config.max_arg_key_size,
                  config.max_arg_val_size)) < 0) return -err(-i, "Parsing url-encoded GET failed");
              }
              if((l=rq_.path.find("://")) != str_t::npos) { // If full URI given
                if(l < 1 || l > rq_.path.length()-4) return -err(400, "URI: Invalid use of '://)'");
                rq_.service = lc(rq_.path.substr(0, l));
                if(rq_.service == "http") sc(rq_.service); // That is the default anyway.
                rq_.path = rq_.path.substr(l+3);
                if((l=rq_.path.find('/')) == str_t::npos || l == 0 || l >= rq_.path.length()-1) {
                  return -err(400, "URI: Missing path or host after service");
                }
                rq_.host = rq_.path.substr(0, l);
                rq_.path = rq_.path.substr(l); // Path not empty because "rq_.path.length()-4"
              }
              if(k == len || rq_.path.length() == 0) return -err(400, "Path length zero");
              rq_.path = urldecode(rq_.path);
              for(; k<len && ::isspace(rx_[k]); k++); // omit spaces
              s.clear(); s.reserve(32);
              for(; k<len && !::isspace(rx_[k]); k++) s += ::toupper(rx_[k]);
              if(s.length() != 8 || s.find("HTTP/1.") == str_t::npos) {
                return -err(400, "http version invalid"); // on http 1.x that will be wrong
              }
              rq_.http_version = (s[7]-'0');
              if(rq_.http_version > 9) {
                return -err(400, "http version invalid"); // in case someone sends HTTP/1.Z or the like
              }
              rq_.keep_alive = rq_.http_version > 0; // HTTP/1.1 default preferred is keep-alive
              rx_.clear();
            } else {
              // (PHASE 2) Header field line
              rx_ = tr(rx_, "\r\n\t ");
              std::string::size_type k=0, len=rx_.length();
              str_t key; key.reserve(32);
              str_t val; val.reserve(64);
              for(; k<len && rx_[k] != ':'; k++) key += rx_[k];
              key = lc(tr(key, "\t "));
              for(++k; k<len; k++) val += rx_[k];
              if(!key.empty()) {
                val = tr(val, "\t ");
                switch(key[0]) {
                  case 'a':
                    if(key == "accept") {
                      rq_.accept = lc(val);
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  case 'c':
                    if(key == "content-length") {
                      if(!isuint(val)) return -err(400, "content-length no uint");
                      rq_.content_length = s2l(val);
                      if(rq_.content_length > (long)config.max_content_length) return -err(413); // Entity too long
                    } else if(key == "content-type") {
                      val = lc(val);
                      rq_.content_type = val;
                      if(rq_.method == rq_post) {
                        if(val.find("text") == 0)  {
                          rq_.content_type_id = cont_tp_text_plain;
                        } else if(val.find("application/x-www-form-urlencoded") != str_t::npos) {
                          rq_.content_type_id = cont_tp_urlenc;
                        } else if(val.find("multipart/form-data") != str_t::npos) {
                          rq_.content_type_id = cont_tp_multipart;
                        }
                      }
                    } else if(key == "cookie") {
                      k = 0;
                      len = val.length();
                      str_t ck, cv; ck.reserve(len/2); cv.reserve(len/2);
                      while(k<len) {
                        ck.clear(); cv.clear();
                        while(k<len && val[k]!='=' && val[k]!=';') ck += val[k++];
                        if(k>=len) break;
                        if((ck = lc(tr(urldecode(ck)))).length() > 0) {
                          if(val[k++] != ';') {
                            if(val[k] == '"') {
                              // version 1 cookie, quoted string
                              for(++k; k<len-1; ++k) {
                                if(val[k] == '\\' && val[k+1] == '"') {
                                  k += 2; cv += '"';
                                } else if(val[k] == '"') {
                                  break;
                                } else {
                                  cv += val[k];
                                }
                              }
                              rq_.cookie[ck] = cv;
                              while(k<len && val[++k] != ';') ; // No errors, ignore tailing chars
                            } else {
                              // version 0 cookie, urldecode
                              while(k<len && val[k]!=';') cv += val[k++];
                              rq_.cookie[ck] = urldecode(tr(cv), false);
                            }
                          }
                        } else {
                          k++;
                        }
                      }
                    } else if(key == "cache-control") {
                      val = lc(val);
                      if(val.find("no-cache") != str_t::npos) {
                        rq_.no_cache = true;
                      } else {
                        rq_.headers[key] = val;
                      }
                    } else if(key == "connection") {
                      if(!val.empty()) rq_.keep_alive = ::tolower(val[0]) != 'c'; // "[c]lose" == !keep-alive
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  case 'h':
                    if(key == "host") {
                      if(!val.empty()) rq_.host = tr(val);
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  case 'i':
                    if(key == "if-modified-since") {
                      rq_.if_modified_since = string2timestamp(val);
                    } else if(key == "if-unmodified-since") {
                      rq_.if_unmodified_since = string2timestamp(val);
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  case 'r':
                    if(key == "referrer" || key == "referer") {
                      rq_.referer = val;
                    } else if(key == "range") {
                      if(val.find("bytes=") != 0 || val.length() < 7) {
                        rq_.headers[key] = val; // that would be principally a bad request
                      } else {
                        bool bad = false;
                        std::deque<str_t> q;
                        do {
                          str_t s; s.reserve(val.length());
                          bool hasdash = false;
                          for(str_t::const_iterator it=val.begin()+6; it!=val.end(); ++it) {
                            if(::isspace(*it)) continue;
                            if(*it == ',') {
                              if(!hasdash || s.length()<2) { bad=true; break; }
                              q.push_back(s);
                              s.clear();
                              hasdash = false;
                            } else if(*it == '-') {
                              if(hasdash) { bad=true; break; }
                              hasdash = true;
                              s += *it;
                            } else if(!::isdigit(*it)) {
                              bad = true; break;
                            } else {
                              s += (*it == ',') ? ' ' : *it;
                            }
                          }
                          if(!s.empty()) q.push_back(s);
                        } while(0);
                        rq_.range.reserve(q.size());
                        while(!q.empty()) {
                          str_t s; s.swap(q.front()); q.pop_front();
                          if(s[0] == '-') {
                            std::basic_stringstream<char_t> ss(s.substr(1));
                            int64_t l; ss >> l; // That should be ok, pre-checked
                            rq_.range.push_back(typename range_t::value_type(-1, l));
                          } else if(s[s.length()-1] == '-') {
                            s.resize(s.length()-1);
                            std::basic_stringstream<char_t> ss(s);
                            int64_t l; ss >> l;
                            rq_.range.push_back(typename range_t::value_type(l, -1));
                          } else {
                            sz_t i = s.find('-');
                            int64_t l0, l1;
                            { std::basic_stringstream<char_t> ss(s.substr(0,i)); ss >> l0; }
                            { std::basic_stringstream<char_t> ss(s.substr(i+1)); ss >> l1; }
                            if(l0 > l1) { bad=true; break; }
                            rq_.range.push_back(typename range_t::value_type(l0, l1));
                          }
                        }
                        if(bad) return -err(400, "range header field invalid");
                      }
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  case 'u':
                    if(key == "user-agent") {
                      rq_.user_agent = lc(val);
                    } else {
                      rq_.headers[key] = val;
                    }
                    break;
                  default:
                    rq_.headers[key] = val;
                }
              }
              rx_.clear();
            }
          }
        }
        if(state_ > st_r_body) return 0; // all current sz processed, and we don't need more
        if(state_ < st_r_body) return 1;
      case st_r_body:
        if(this->ram_handled()) {
          for(; i<sz && rlen_>0; ++i,--rlen_) rq_.data += rq[i]; // Copy rest of the buffer
          if(sz == 0 && rlen_ > 0) return -err(400, "Client did not send all announced body bytes.");
          if(rlen_ > 0) return 1;
          state_ = st_r_done;
          // Parse input in case of known text data
          if(rq_.method == rq_post) {
            switch(rq_.content_type_id) {
              case cont_tp_text_plain:
                rq_.post["text"] = rq_.data;
                sc(rq_.data); // free memory of request data
                break;
              case cont_tp_urlenc:
                do { // Block for local int l.
                  int l = parse_url_encoded(rq_.data, rq_.post, true, config.max_arg_key_size,
                    config.max_arg_val_size); // Parse all in one go
                  if(l < 0) return -err(-l, "Parsing POST urlencoded failed");
                  sc(rq_.data);
                } while(0);
                break;
              case cont_tp_multipart:
                do {
                  http_ram_storage_provider<> ram;
                  ram.swap_string_buffer_with(rq_.data);
                  int l = parse_body_post_multipart_formdata(ram);
                  ram.close();
                  if(l < 0) return l; // error already set
                } while(0);
                break;
              default:
                break; // no parsing
            }
          }
        } else {
          if((long)sz > rlen_) sz = rlen_;
          if(storage_.write(rq, sz) < 0) return -err(500, "Failed to write storage");
          if(sz == 0 && rlen_ > 0) return -err(400, "Client did not send all announced body bytes.");
          if((rlen_ -= sz) > 0) return 1;
          // All data received, parse what can be parsed. That is only
          // POST multipart. Other cases are either short enough to be RAM handled (above),
          // or the method is not POST, or the content length is too large to parse url-encoded
          // data to RAM (menas the rq_.post map).
          if(rq_.method == rq_post && rq_.content_type_id == cont_tp_multipart) {
            do {
              int l = parse_body_post_multipart_formdata(storage_);
              if(l < 0) return l; // error already set
            } while(0);
            break;
          }
        }
        // return 0 branch
        state_ = st_r_done;
      case st_r_done:
        return 0;
      case st_r_error:
      case st_error:
        return -error_;
      default:
        return -err(500, "recv: invalid state");
    }
    return -err(500, "recv: state block did not return");
  }
 
  /**
   * Parses form url encoded data and adds the results to the POST map of this object.
   * @param const str_t &
   * @return int
   */
  int parse_body_post_form_url_encoded(const str_t & s)
  {
    int r = parse_url_encoded(s, rq_.post, true, config.max_arg_key_size, config.max_arg_val_size);
    if(r < 0) return -err(-r, "Failed to parse form-url-encoded data"); else return r; // num added
  }
 
  /**
   * Parses POST multipart formdata, adds
   * @param const str_t &
   * @return int
   */
  int parse_body_post_multipart_formdata(http_tmpstorage_provider<> & st)
  {
    std::deque<upload_file_t> files;    // List if found boundaries
    bool with_cr = false;               // correction indicator for sizes
 
    // (1) Find boundaries
    do {
      str_t boundary = rq_.content_type;
      do {
        str_t::iterator it = std::remove_if(boundary.begin(), boundary.end(), ::isspace);
        boundary.erase(it, boundary.end());
      } while(0);
      sz_t boundlen = boundary.find("boundary="); // e.g. boundary=aBcDe
      if(boundlen == str_t::npos || boundlen+10 >= boundary.length())  {
        return -err(400, "Form multipart data: missing 'boundary='");
      }
      boundary = str_t("--") + boundary.substr(boundlen+9); // e.g. --> --aBcDe
      do {
        rq_.content_type.resize(boundlen);
        boundlen = rq_.content_type.find(';');
        if(boundlen != str_t::npos && boundlen > 0) rq_.content_type.resize(boundlen);
        rq_.content_type = tr(rq_.content_type); // you never know
      } while(0);
 
      boundlen = boundary.length();
      if(boundlen > 255) {
        return -err(400, "Form multipart data: boundary string too long");
      }
      st.seek(0);
      if(!st.size() || st.eof()) {
        return -err(500, "Form multipart data parser: no content in tmp storage");
      } else if(st.size() < ((2*boundlen)+3)) { // start '--aBcDe', end '\n--aBcDe--'
        return -err(400, "Form multipart data parser: content too short to hold boundaries");
      }
 
      // Maybe not the most efficient way, but reading goes byte-per-byte
      char c; // read character
      long r; // read result
      str_t buf; // compare buffer for matching boundaries
      unsigned long end=(st.size()-boundlen-2); // not including '\n--aBcDe--'
      unsigned long p;   // current position
      buf.reserve(boundlen+3);
 
      // Double boundary at check end of stream
      st.seek(end); // position after last \n --> must be the boundary string + '--'
      while((r=st.read(&c, sizeof(char))) > 0) {
        if(!::isspace(c)) buf += c;
      }
      if(buf.find(boundary) != 0) return -err(400, "multipart parser: missing closing boundary");
 
      // Check start of stream
      st.seek(0);
      buf.clear();
      for(p=0; (r=st.read(&c, sizeof(char)))>0 && p<boundlen+5 && c!='\n'; ++p) {
        if(c == '\r') with_cr = true;
        if(!::isspace(c)) buf += c;
      }
      if(buf != boundary) return -err(400, "multipart parser: missing starting boundary");
 
      // Search more boundaries
      upload_file_t file;
      file.start = p+1;
      buf.clear();
      for(; (r=st.read(&c, sizeof(char)))>0 && ++p<end;) {
        if(!::isspace(c) && buf.length() < boundlen) buf += c;
        if(c == '\n') {
          if(buf.length() == boundlen && buf == boundary) {
            file.size = p - boundlen - file.start - (with_cr ? 2 : 1);
            files.push_back(file);
            file.size = 0;
            file.start = p+1;
          }
          buf.clear();
        }
      }
      if(r < 0) return -err(500, "multipart parser: storage i/o failed");
      file.size = p - file.start - 1; // push last item, cr/lr already chopped because of 'end'
      files.push_back(file);
    } while(0);
 
    // (2) Split boundary header and start seek correction
    do {
      str_t header, line;
      header.reserve(256);
      line.reserve(64);
      for(typename std::deque<upload_file_t>::iterator it= files.begin(); it != files.end(); ++it) {
        header.clear();
        line.clear();
        st.seek(it->start);
        char c;
        bool was_nl = false;
        unsigned long i;
        for(i=0; i < it->size; ++i) {
          st.read(&c, sizeof(char));
          if(c=='\r') continue;
          if(c=='\n' && was_nl) break;
          was_nl = c=='\n';
          if(was_nl || !::isspace(c)) header += c;
        }
        it->start += i+1;
        if(i+1+(with_cr ? 1:0) >= it->size) it->size = 0; else it->size -= i+1+(with_cr ? 1:0);
        sz_t p = header.find_last_not_of('\n');
        if(p != str_t::npos) header.resize(p+1);
        while(!header.empty()) {
          if((p = header.rfind('\n')) == str_t::npos) {
            line = header;
            header.clear();
          } else {
            line = header.substr(p+1); // \n is trimmed already, so can't be at the end
            header.resize(p);
          }
          str_t s = lc(line);
          if(s.find("content-disposition:") == 0) {
            // Before modifying s check for filename=, so that it is case correct.
            if((p = s.find(";filename=")) != str_t::npos) { // quote checked separately
              p += 10;
              if(p >= s.length()-2 || s[p] != '"') { // two quotes, at least one char for the name
                return -err(400, "Multipart header: file name not quoted.");
              }
              sz_t p1 = s.find('"', p+1);
              if(p1 == str_t::npos || p1 < p+3 ) {
                return -err(400, "Multipart header: file name not quoted.");
              }
              it->filename = line.substr(p+1, p1-p-1);
            }
            // The rest is lowercase, name inclusively
            line = s.substr(20);
            if(line.find("form-data;") != 0 ) {
              return -err(400, "Multipart: Disposition not form-data.");
            }
            line = line.substr(10);
            // Normally there are no leading / tailing ";" but you never know:
            while(!line.empty() && line[line.length()-1] == ';') line.resize(line.length()-1);
            while(!line.empty() && line[0] == ';') line = line.substr(1);
            // Line chunks can be in random order
            while(!line.empty()) {
              if((p = line.rfind(';')) != str_t::npos) {
                s = line.substr(p+1); line.resize(p);
              } else {
                s = line; line.clear();
              }
              if(s.find("name=\"") == 0) {
                if(s.length() < 8) return -err(400, "Multipart header: Bad entity name");
                s.resize(s.length()-1);
                s = s.substr(6);
                for(sz_t i=0; i<s.length(); ++i) {
                  if(s[i] < 30) return -err(400, "Multipart header: Bad entity name");
                }
                it->name = s;
              }
              // Others may follow
            }
          } else if(s.find("content-type:") == 0) {
            it->content_type = s.substr(13);
          } else if(s.find("content-transfer-encoding:") == 0) {
            it->content_encoding = s.substr(26);
          } else {
            log(str_t("Multipart header line ignored: ")+line, log_debug);
          }
        }
        if(it->name.empty()) return -err(400, "Multipart header: No entity name for boundary");
      }
    } while(0);
 
    // (3) Transfer data to rq_.post and rq_.files
    for(typename std::deque<upload_file_t>::iterator it= files.begin(); it != files.end(); ++it) {
      if(it->filename.empty()) {
        if(it->size > config.max_arg_val_size && !ram_handled()) {
          return -err(400, "Multipart header: Missing file name for large data");
        } else if(!it->content_type.empty() && (it->content_type.find("text")!=0)) {
          return -err(400, "Multipart header: Missing file name for non-text data");
        }
        // Stored in POST
        if(it->size == 0) {
          rq_.post[it->name] = str_t();
        } else {
          str_t s;
          s.reserve(it->size);
          st.seek(it->start);
          char c;
          for(unsigned long i=0; i<it->size; ++i) {
            st.read(&c, 1); // maybe check that, too
            s += c;
          }
          rq_.post[it->name] = s;
        }
      } else if(it->size < config.max_arg_val_size || ram_handled()) {
        // File data is saved in file.data
        if(it->size > 0) {
          str_t s;
          s.reserve(it->size);
          st.seek(it->start);
          char c;
          for(unsigned long i=0; i<it->size; ++i) {
            st.read(&c, sizeof(char)); s += c;
          }
          it->data.swap(s);
        }
        it->start = -1; // indicator that the data is in ram, not as storage position
        rq_.files[it->name] = *it;
      } else {
        // Data can be found in the storage, file.start and file.size say where to find it.
        rq_.files[it->name] = *it;
      }
    }
    return 0;
  }
  // </editor-fold>
 
  // <editor-fold desc="send()" defaultstate="collapsed">
  /**
   * Sends the header data and data set in the `response().data`. If a content_length is specified,
   * it will be used, weather it exceeds the data length or not.
   *
   * - The content length will be modified if it is not -1 and it is smaller than the
   *   `response().data` length.
   *
   * - If the request method is HEAD, all data will be omitted, but the content-length added to
   *   the header (if not -1). As well for 204 and 304, where no body is allowed.
   *
   * - If the status represents a status code >= 300 and no data nor content-length is given, a
   *   short message is generated if the accepted type is *%*, text/plain or text/html. Otherwise
   *   the message is sent without content.
   *
   * - Transforms invalid status codes (such as 0) into a 500 status.
   *
   * This function is generally used to send the header before sending other data, such as file
   * contents or any other information.
   *
   * @return int
   */
  int send()
  {
    if(state_ >= st_done) {
      return 0;
    } else if(state_ <= st_s_header) {
      if(rs_.status < 100 || rs_.status >= 600) {
        str_t s = str_t("Invalid response status code ") + l2s(rs_.status);
        err(500, s.c_str());
      }
      if(rq_.http_version < 1 && rs_.status < 200) {
        log(str_t("HTTP/1.0 does not know 1xx status"), log_warn);
      }
      if(rq_.method == rq_head) {
        // Do nothing, header will be added below
      } else if(rs_.status < 200 || rs_.status == 304 || rs_.status == 204) {
        // Codes where no body is allowed
        rs_.content_length = 0;
        rs_.content_type = "";
        rs_.keep_alive = false;
        rs_.transf_encoding = tfe_omit;
      } else if(rs_.status >= 300) {
        // Codes where content is allowed and may have to be automatically generated.
        // The content is not variable. Redirections and errors should close the connection.
        rs_.keep_alive = false;
        if(rs_.data.size() == 0) {
          // Messages are only generated if no response data is set and no
          // content length defined.
          rs_.transf_encoding = tfe_omit;
          if(rq_.accept.empty() || rq_.accept.find("html") != str_t::npos
          || rq_.accept.find("*/*") != str_t::npos) {
            rs_.content_type = "text/html";
            rs_.data.chunk(str_t("<h1>") + reponse_status_text(rs_.status) + "</h1>\n");
             if(config.append_error_messages && !error_text_.empty()) {
               rs_.data.chunk(str_t("<br/>") + htmlspecialchars(error_text_) + "\n");
             }
            rs_.content_length = rs_.data.size();
          } else if(rq_.accept.find("text/plain") != str_t::npos) {
            rs_.content_type = "text/plain";
            rs_.data.chunk(str_t(reponse_status_text(rs_.status)) + "\n");
             if(config.append_error_messages && !error_text_.empty()) {
               rs_.data.chunk(str_t("\n") + error_text_ + "\n");
             }
            rs_.content_length = rs_.data.size();
          }
        } else {
          // Data set, we leave the content-length as it is because we might want to send more than
          // only these data. -1 is ignored anyway in header(). Only exception: incorrect length:
          rs_.content_length = rs_.data.size();
        }
      } else {
        // 2xx codes, normally with content. Content length==-1 means we don't know the content length,
        // hence omitted.
        if(rs_.content_length >= 0 && (sz_t)rs_.content_length < rs_.data.size()) {
          rs_.content_length = rs_.data.size();
        }
      }
      rs_.data.prepend(header());
      // 1st chunk size == header size. n_sent_ passes 0 at the first body byte.
      n_sent_ = -((long)rs_.data.chunk_size());
      if(config.implicit_clear) { rq_.clear(); rs_.clear(false); }
      state_ = st_s_body;
    }
    // Send data contents. Return 1 until rs_.data is empty, then 0 (-1 = error).
 
    if(rs_.data.empty()) {
      if(n_sent_ >= rs_.content_length) {
        if(rs_.transf_encoding == tfe_chunked) this->write("0\r\n\r\n", 5); // Chunked: finish
        state_ = st_done;
      }
      return 0; // This function has no more data to send
    }
 
    ssize_t r = 1; // send return variable
    while(r > 0 && !rs_.data.empty()) {
      if(rs_.transf_encoding != tfe_chunked || n_sent_ < 0) { // not chunked or still sending header
        r = this->write(rs_.data.chunk(), rs_.data.chunk_size());
      } else {
        str_t s = ul2hex(rs_.data.chunk_size()) + str_t("\r\n");
        ssize_t r2;
        r = this->write(s.data(), s.length());
        if(r > 0) r = this->write(rs_.data.chunk(), rs_.data.chunk_size());
        if(r > 0 && (r2 = this->write("\r\n", 2)) < 0) r = r2;
      }
      if(r > 0) {
        n_sent_ += r;
        rs_.data.advance((sz_t)r);
      } else if(r != EWOULDBLOCK) {
        state_ = st_error;
        rs_.data.clear();
        str_t s = str_t("send(): Failed to write data: ") + l2s(r);
        return -err(1, s.c_str()); // == EBADF EFBIG EINTR EIO ENOSPC EPIPE EINVAL
      }
    }
    return 1; // This function did send data
  }
 
  /**
   * Send a status, dependent on the status code, a small notice body is added.
   * @param int status
   * @return int
   */
  int send(int status)
  {
    if(state_ <= st_s_header) {
      rs_.status = status;
      rs_.data.clear();
    } else {
      log("Headers already sent", log_warn);
    }
    return send();
  }
 
  /**
   * Send a string. Default content-type=text/plain.
   * @param const str_t & s
   * @return int
   */
  int send(const str_t & s, int status=200, bool implicit_content_length=true)
  { return send_binary(s.data(), s.length(), status, implicit_content_length); }
 
  /**
   * Send binary.
   * @param const void* data
   * @param sz_t sz
   * @param int status=200
   * @param bool implicit_content_length=true
   * @return int
   */
  int send_binary(const void *data, sz_t sz, int status=200, bool implicit_content_length=true)
  {
    if(state_ <= st_s_header) {
      rs_.status = status;
      rs_.data.chunk((const char*) data, sz);
      if(implicit_content_length) rs_.content_length = rs_.data.size();
      if(rs_.content_type.length() == 0) rs_.content_type = "text/plain"; // charset=client guess
    } else if(sz > 0) {
      if(rs_.content_length >= 0 && n_sent_+(long)sz > rs_.content_length) {
        log("send_binary(): Trimmed data size to fit content-length", log_warn);
        sz = rs_.content_length - n_sent_;
      }
      rs_.data.chunk((const char*) data, sz);
    }
    return send();
  }
 
  /**
   * Send a file.
   *
   * @param const str_t & filename
   * @param int status=200
   * @return int
   */
  int send_file(const str_t & filename, int status=200)
  {
    struct stat st;
    status = status <= 0 ? 500 : status;
    switch(stat(filename.c_str(), &st)) {
      case 0:
        if(!S_ISREG(st.st_mode)) { // no links
          return send(404);
        } else {
          {
            str_t::size_type n = filename.find_last_of('.');
            if(n != str_t::npos && n < filename.length()-1) {
              str_t s = mime_content_type(lc(tr(filename.substr(n+1))));
              rs_.content_type = (s.length() > 0) ? s : str_t("application/octet-stream");
            }
            rs_.last_modified = st.st_mtime;
            rs_.content_length = st.st_size;
            rs_.etag = mketag(filename, st);
          }
          if(!modified()) {
            send(304);
          } else {
            std::ifstream fs;
            fs.open(filename.c_str(), std::ifstream::binary);
            if(!fs.good()) {
              if(fs.eof()) { send(204); } else { send(403); } // That needs to be double checked ...
            } else {
              rs_.status = status; // check if 200 is always correct
              send();
              char s[256];
              sz_t n;
              while(fs.good()) {
                fs.read(s, sizeof(s));
                n = (sz_t)fs.gcount();
                if(n == 0) { while(send() > 0) {;} break; } // flush
                if(send_binary(s, n) < 0) break;
                while((!rs_.data.empty()) && (send() > 0)); // in case of EAGAIN
                if(state_ == st_done) break;
              }
              if(num_send_bytes_expected_left() > 0) {
                log(str_t("Sending '") + filename + "' did not send the last " +
                  l2s((long)num_send_bytes_expected_left()) + " bytes.");
              }
            }
            fs.close();
          }
        }
        break;
      case EACCES:
        return send(403);
      case ENOENT:
      case ENAMETOOLONG:
      case ENOTDIR:
        return send(404);
      case EOVERFLOW:
      default:
        return send(500);
    }
    return -err(1, "send_file: not all cases covered.");
  }
 
  // </editor-fold>
 
public:
 
  // <editor-fold desc="Public static auxiliaries" defaultstate="collapsed">
 
  //////////////////////////////////////////////////////////////////////////////
  // Public static functions
 
  /**
   * Log something
   * @param str_t txt
   * @return void
   */
  static void log(str_t txt, int level = log_notice)
  {
    static const char* lvls[] = { "CRT", "ERR ", "WRN", "INF", "DBG", "!!!" };
    if(!config.log_callback || level < 0 || level > log_debug || level > config.log_level) return;
    txt = str_t() + ul2s(::time(NULL)) + " [" + lvls[level] + "] " + txt + "\n";
    config.log_callback(txt, level);
  }
 
  /**
   * Decodes url encoded data (RFC 3986), normally for url/GET or optionally
   * for POST form data accepting "+" as well to mark spaces.
   * @param const str_t &in
   * @param bool form=false
   * @return str_t
   */
  static str_t urldecode(const str_t &in, bool form=false)
  {
    str_t out; out.reserve(in.length());
    int n = in.length();
    typename str_t::const_iterator p = in.begin();
    for(; --n >= 2; ++p) {
      if(*p == '+' && form) {
        out += ' ';
      } else if(*p == '%' && std::isxdigit(*(p+1)) && std::isxdigit(*(p+2))) {
        char_t c1=*(++p), c0=*(++p);
        out += hex2char(c1, c0); n-=2;
      } else {
        out += *p;
      }
    }
    for(; n-- >= 0; ++p) {
      out += (*p == '+' && form) ? '+' : *p;
    }
    return out;
  }
 
  /**
   * Encodes a string RFC 3986 compliant.
   * @param const str_t &in
   * @return str_t
   */
  static str_t urlencode(const str_t &in)
  {
    str_t out; out.reserve(in.length()*2);
    typename str_t::const_iterator p = in.begin();
    typename str_t::const_iterator e = in.end();
    for(; p!=e; ++p) {
      if(std::isalnum(*p) || *p=='-' || *p=='_' || *p=='.') {
        out += *p;
      } else {
        out += "%"; out += char2hex(*p);
      }
    }
    return out;
  }
 
  /**
   * Escapes HTML entities < > and &
   * @param const str_t & s
   * @param bool with_quotes=true
   * @return str_t
   */
  static str_t htmlspecialchars(const str_t & s, bool with_quotes=true)
  {
    str_t ss;
    if(with_quotes) {
      do {
        sz_t n = s.length();
        for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
          switch(*it) {
            case '>':               // &gt;
            case '<': n+= 3; break; // &lt;
            case '&': n+= 4; break; // &amp;
            case '"': n+= 5; break; // &quot;
            case '\'':n+= 5; break; // &apos;
          }
        }
        ss.reserve(n);
      } while(0);
      for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
        switch(*it) {
          case '>' : ss += str_t("&gt;"); break;
          case '<' : ss += str_t("&lt;"); break;
          case '&' : ss += str_t("&amp;"); break;
          case '"' : ss += str_t("&quot;"); break;
          case '\'': ss += str_t("&apos;"); break;
          default  : ss += *it;
        }
      }
    } else {
      do {
        sz_t n = s.length();
        for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
          switch(*it) {
            case '>':               // &gt;
            case '<': n+= 3; break; // &lt;
            case '&': n+= 4; break; // &amp;
          }
        }
        ss.reserve(n);
      } while(0);
      for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
        switch(*it) {
          case '>' : ss += str_t("&gt;"); break;
          case '<' : ss += str_t("&lt;"); break;
          case '&' : ss += str_t("&amp;"); break;
          default  : ss += *it;
        }
      }
    }
    return ss;
  }
 
  /**
   * Returns the mime type for a given extension (without additional tags
   * like ";charset=...").
   * @param const str_t &ext
   * @return str_t
   */
  static str_t mime_content_type(const str_t &ext)
  {
    static const char_t *text_plain = "text/plain";
    static const char_t *lut[] = { // sorted lookup table
      "aac","audio/aac", "asf","video/x-ms-asf", "avi","video/x-msvideo",
      "bmp","image/bmp", "css","text/css", "csv","text/csv",
      "doc","application/msword", "eps","application/postscript",
      "exe","application/octet-stream", "gif","image/gif",
      "gz","application/x-gunzip", "htm","text/html", "html","text/html",
      "ico","image/x-icon", "jpeg","image/jpeg", "jpg","image/jpeg",
      "js","application/javascript", "json","application/json",
      "m4v","video/x-m4v", "mid","audio/x-midi", "mov","video/quicktime",
      "mp3","audio/mpeg", "mp4","video/mp4", "mpeg","video/mpeg",
      "mpg","video/mpeg", "oga","audio/ogg", "ogg","audio/ogg",
      "ogv","video/ogg", "pdf","application/pdf", "png","image/png",
      "ps","application/postscript", "rar","application/x-arj-compressed",
      "rtf","application/rtf", "sgm","text/sgml",
      "shtm","text/html", "shtml","text/html", "svg","image/svg+xml",
      "swf","application/x-shockwave-flash", "tar","application/x-tar",
      "tgz","application/x-tar-gz", "tif","image/tiff", "tiff","image/tiff",
      "txt","text/plain", "wav","audio/x-wav", "webm","video/webm",
      "wrl","model/vrml", "xhtml","application/xhtml+xml",
      "xls","application/x-msexcel", "xml","text/xml", "xsl","application/xml",
      "xslt","application/xml", "zip","application/x-zip-compressed",
    };
    if(ext.empty()) return text_plain;
    { str_t s = mime_content_type_add(ext.c_str(), 0); if(!s.empty()) return s; }
 
    int n = (sizeof(lut)/sizeof(const char_t*)), p;
    #ifdef assert
    assert(!(sizeof(lut)/sizeof(const char_t*)&0x1));
    #endif
    while(n > 0) {
      n = (n)>>1u;
      p = n & 0xfffe;
      const char* lv = lut[p];
      if(ext.compare(lv)) { p+=n; continue; }
      if(ext == lv) return lut[p+1];
      p -= n;
    }
    for (p = (p-=2)<0 ? 0:p, n=sizeof(lut)/sizeof(const char_t*); p<n; p+=2) {
      if(ext==lut[p]) return lut[p+1];
    }
    return text_plain;
  }
 
  /**
   * Add/get a user defined mime type. Added mime types will be automatically used
   * in mime_content_type(). Setting mime to NULL will return a user added mime type
   * or an empty string if the lookup did not find it.
   * @param const char_t* ext
   * @param const char_t* mime
   * @return str_t
   */
  static str_t mime_content_type_add(const char_t* ext, const char_t* mime)
  {
    static std::map<str_t, str_t> lut;
    if(!ext || !ext[0]) return "";
    if(!mime) {
      typename std::map<str_t, str_t>::const_iterator it = lut.find(ext);
      return (it==lut.end()) ? "" : it->second;
    } else {
      str_t e = lc(ext);
      str_t m = lc(mime);
      if(!m.empty()) { lut[e] = m; }
      return m;
    }
  }
 
  /**
   * Returns the text version of a response code for use in the response header.
   * Contains two versions of the status text table. The compiler selects/drops one
   * dependent on the template parameter With_All_Status_Code_Strings.
   * @param unsigned code
   * @return const char *
   */
  static const char * reponse_status_text(unsigned code)
  {
    typedef struct { unsigned c; const char* h; } rc_t;
    static const rc_t rc[] = {
      {100,"100 Continue"},{101,"101 Switching Protocols"},{102,"102 Processing"},
      {200,"200 OK"},{201,"201 Created"},{202,"202 Accepted"},{203,"203 Non-Authoritative Information"},
      {204,"204 No Content"},{205,"205 Reset Content"},{206,"206 Partial Content"},
      {207,"207 Multi-Status"},{208,"208 Already Reported"},{226,"226 IM Used"},
      {300,"300 Multiple Choices"},{301,"301 Moved Permanently"},{302,"302 Found"},
      {303,"303 See Other"},{304,"304 Not Modified"},{305,"305 Use Proxy"},{306,"306 Switch Proxy"},
      {307,"307 Temporary Redirect"},{308,"308 Permanent Redirect"},{400,"400 Bad Request"},
      {401,"401 Unauthorized"},{402,"402 Payment Required"},{403,"403 Forbidden"},{404,"404 Not Found"},
      {405,"405 Method Not Allowed"},{406,"406 Not Acceptable"},{407,"407 Proxy Authentication Required"},
      {408,"408 Request Timeout"},{409,"409 Conflict"},{410,"410 Gone"},{411,"411 Length Required"},
      {412,"412 Precondition Failed"},{413,"413 Request Entity Too Large"},{414,"414 Request-URI Too Long"},
      {415,"415 Unsupported Media Type"},{416,"416 Requested Range Not Satisfiable"},
      {417,"417 Expectation Failed"},{419,"419 Authentication Timeout"},
      {420,"420 Enhance Your Calm"},{422,"422 Unprocessable Entity"},{423,"423 Locked"},
      {424,"424 Failed Dependency"},{425,"425 Unordered Collection"},{426,"426 Upgrade Required"},
      {428,"428 Precondition Required"},{429,"429 Too Many Requests"},
      {431,"431 Request Header Fields Too Large"},{440,"440 Login Timeout"},{444,"444 No Response"},
      {449,"449 Retry With"},{450,"450 Blocked by Windows Parental Controls"},
      {451,"451 Unavailable For Legal Reasons"},/*{451,"451 Redirect"},*/{494,"494 Request Header Too Large"},
      {495,"495 Cert Error"},{496,"496 No Cert"},{497,"497 HTTP to HTTPS"},{499,"499 Client Closed Request"},
      {500,"500 Internal Server Error"},{501,"501 Not Implemented"},{502,"502 Bad Gateway"},
      {503,"503 Service Unavailable"},{504,"504 Gateway Timeout"},{505,"505 HTTP Version Not Supported"},
      {506,"506 Variant Also Negotiates"},{507,"507 Insufficient Storage"},
      {508,"508 Loop Detected"},{509,"509 Bandwidth Limit Exceeded"},{510,"510 Not Extended"},
      {511,"511 Network Authentication Required"},{520,"520 Origin Error"},{521,"521 Web server is down"},
      {522,"522 Connection timed out"},{523,"523 Proxy Declined Request"},{524,"524 A timeout occurred"},
      {598,"598 Network read timeout error"},{599,"599 Network connect timeout error"},
      {0,"500 Internal Server Error"}
    };
    const unsigned rcsz = (sizeof(rc)/sizeof(rc_t))-1; // 81; {0,"500 error"}=82
     switch(code) {
      case 200: return "200 OK"; // that will be the same pointer as in the list.
      case 404: return "404 Not Found";
      case 500: return "500 Internal Server Error";
      case 501: return "501 Not Implemented";
      default:
        if(code >= 600 || code < 100) return rc[rcsz].h;
        unsigned i=rcsz>>1, k=rcsz>>1;
        while(rc[i].c != code && k>0) i = (code > rc[i].c) ? i+(k>>=1) : i-(k>>=1);
        while(code > rc[i].c) ++i;
        while(code < rc[i].c) --i;
        return code == rc[i].c ? rc[i].h : rc[rcsz].h;
    }
  }
 
  /**
   * Parses URL encoded data into a string map. Returns the number of added key-value pairs or
   * negative on error.
   * @param const str_t & inp
   * @param str_map_t & out
   * @param bool form_encoded=false
   * @param unsigned max_key_sz=64
   * @param unsigned max_val_sz=4096
   * @return int
   */
  static int parse_url_encoded(const str_t & inp, str_map_t & out, bool form_encoded=false,
          unsigned max_key_sz=64, unsigned max_val_sz=4096)
  {
    sz_t qlen = inp.length();
    int n=0;
    for(sz_t l=0; l<qlen; l++) {
      str_t key, val;
      key.reserve(32);
      val.reserve(64);
      for(; l<qlen && inp[l] != '&' && inp[l] != '='; l++) key += inp[l];
      if(inp[l] == '=') {
        for(++l; l<qlen && inp[l] != '&'; l++) val += inp[l];
      }
      key = urldecode(tr(key), form_encoded);
      val = urldecode(tr(val), form_encoded);
      if(key.length() > max_key_sz) return -431;
      if(val.length() > max_val_sz) return -431;
      if(!key.empty()) { out[key] = val; n++; }
    }
    return n;
  }
 
  /**
   * Returns a GMT time based string of a time stamp suitable to be used in header date information.
   * @param time_t t
   * @return str_t
   */
  static str_t timestamp2string(time_t t)
  {
    char s[128];
    struct ::tm tm;
    memset(s, 0, sizeof(s));
    memset(&tm, 0, sizeof(tm));
    #if !defined(_WINDOWS_) && !defined(_WIN32) && !defined(__MSC_VER)
    if(!gmtime_r(&t, &tm)) return "Thu, 01 Jan 1970 00:00:00 GMT";
    #else
    errno_t e = gmtime_s(&tm, &t);
    if(e != 0) {
      using namespace std;
      cerr << ::strerror(e) << endl;
      return "Thu, 01 Jan 1970 00:00:00 GMT";
    }
    #endif
    ::strftime(s, sizeof(s), "%a, %d %b %Y %H:%M:%S GMT", &tm);
    return s;
  }
 
  /**
   * Returns a GMT time based on a formatted string or 0 if the string could not be parsed.
   * @param const str_t & stime
   * @return time_t
   */
  static time_t string2timestamp(const str_t & stime)
  { // no c++11 crono requirement, no plain strptime (unsafe) --> detect pattern, parse then
    // bit of a pain, but well ...
    if(stime.empty()) return 0;
    str_t s, pattern;
    do {
      // Preconditioning the time string (lower, no double spaces, strip unknown characters)
      s.reserve(48);
      bool wassp=false;
      for(str_t::const_iterator it=stime.begin(); it!=stime.end(); ++it) {
        char_t c = *it;
        if(::isalnum(c)) { wassp=false; s += ::tolower(c); continue; }
        if(c == ',') c = ' '; // save some trouble later on
        if(::isspace(c)) { if(!wassp) { wassp = true; s += ' '; } continue; }
        if(str_t(",:+-./").find(c) != str_t::npos) wassp = true; else continue;
        s += c;
      }
      s = tr(s);
    } while(0);
    do {
      // Pattern
      pattern.reserve(32);
      char_t tl = 0;
      for(str_t::const_iterator it=s.begin(); it!=s.end(); ++it) {
        char_t tc = ::isdigit(*it) ? 'd' : (::isalpha(*it) ? 'a' : *it);
        if(tc != tl) { tl = tc; pattern += tc; }
      }
    } while(0);
    do {
      int y=-1,m=-1,d=-1,h=0,min=0,sec=0;
      str_t wds, tzs, ms;
      char_t c;
      if(pattern == "a d a d d:d:d a") { // Thu, 01 Jan 1970 00:00:00 GMT  : RFC 822, RFC 1123
        std::stringstream ss(s);
        ss >> wds >> std::dec >> d >> ms >> y >> h >> c >> min >> c >> sec >> tzs;
      } else if(pattern == "a d-a-d d:d:d a") { // Sunday, 06-Nov-94 08:49:37 GMT, RFC 850
        for(str_t::iterator it=s.begin(); it != s.end(); ++it) if(*it == '-') *it = ' ';
        std::stringstream ss(s);
        ss >> wds >> d >> ms >> y >> h >> c >> min >> c >> sec >> tzs;
      } else if(pattern == "a a d d:d:d d") { // Sun Nov 6 08:49:37 1994, asctime
        std::stringstream ss(s);
        ss >> wds >> ms >> d >> h >> c >> min >> c >> sec >> y;
      }
 
      if(y<0 || d<0 || h<0 || min<0 || sec<0 || ((m<0) && (ms.length()<3))) return 0;
      if((!tzs.empty()) && (tzs != "gmt")) return 0; // for now only specified timezone accepted.
      if(m<0) {
        ms.resize(3);
        const char* months[12] = {
          "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
        };
        for(int i=0; i<12; ++i) if(ms == months[i]) { m=i+1; break; }
      }
      //if(y < 100) { if(y >=70) y += 1900; else if(y<8) y += 2000; else return 0; }
      struct ::tm tm;
      memset(&tm, 0, sizeof(struct ::tm));
      tm.tm_year = y-1900;
      tm.tm_mon = m-1;
      tm.tm_mday = d;
      tm.tm_hour = h;
      tm.tm_min = min;
      tm.tm_sec = sec;
      #if !defined(_WINDOWS_) && !defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__MSC_VER)
      tm.tm_gmtoff = 0; // stfwi: double check
      return ::timegm(&tm);
      #else
      return time_t(_mkgmtime(&tm));
      #endif
    } while(0);
    return 0;
  }
  // </editor-fold>
 
protected:
 
  // <editor-fold desc="Protected auxiliaries" defaultstate="collapsed">
 
  //////////////////////////////////////////////////////////////////////////////
  // Internal functions / methods
 
  /**
   * Switch to error state, set response code. Returns int cast for return of negative numbers.
   * @param unsigned code
   * @return int
   */
  inline int err(unsigned code)
  {
    error_ = code;
    if(state_ < st_s_header) {
      state_ = st_r_error;
      rs_.status = code;
    } else {
      state_ = st_error;
    }
    rlen_ = 0; // ensure no additional read actions
    return (int) code;
  }
 
  /**
   * Switch to error state, set response code. Returns int cast for return of negative numbers.
   * @param unsigned code
   * @param const char *msg
   * @return int
   */
  inline int err(unsigned code, const char *msg)
  { if(msg) { error_text_ = msg; log(msg, log_error); } return err(code); }
 
  /**
   * Returns a character value from two hex digits. Does not check if the
   * digits are really hex.
   * @param char_t c1 (higher nibble)
   * @param char_t c0 (lower nibble)
   * @return char_t
   */
  static inline char_t hex2char(register char_t c1, register char_t c0)
  {
    c1 = std::tolower(c1); c1 -= (c1>'9' ? ('a'-10) : '0');
    c0 = std::tolower(c0); c0 -= (c0>'9' ? ('a'-10) : '0');
    return (char_t) ((c1)<<4) | ((c0)<<0);
  }
 
  /**
   * Transforms a character value to hex (upper case)
   * @param char_t c
   * @return str_t
   */
  static inline str_t char2hex(register char_t c)
  {
    static const char_t lut[17] = "0123456789abcdef";
    return str_t(1, lut[(c>>4)&0xf]) + lut[(c>>0)&0xf];
  }
 
  /**
   * Transforms positive number into a hex string
   * @param uint64_t n
   * @return str_t
   */
  static inline str_t ul2hex(unsigned long n)
  {
    static const char_t lut[17] = "0123456789abcdef"; // The same pointer as above after compiling
    static const sz_t l = 2*sizeof(unsigned long)+1; // note: one additional character in the buf
    if(!n) return str_t("0");
    str_t s(l, ' ');
    sz_t i = l;
    while(n && i>0) { s[--i] = lut[n & 0x0ful]; n >>= 4u; }
    return s.substr(i);
  }
 
  /**
   * Transform lower case
   * @param str_t s
   */
  static inline str_t lc(str_t s)
  { std::transform(s.begin(), s.end(), s.begin(), ::tolower); return s; }
 
  /**
   * Transform upper case
   * @param str_t s
   */
  static inline str_t uc(str_t s)
  { std::transform(s.begin(), s.end(), s.begin(), ::toupper); return s; }
 
  /**
   * Trim white spaces right
   * @param str_t s
   * @param const char *spacechars=" "
   */
  static str_t rtr(const str_t & s, const char* spacechars=" ")
  {
    if(s.empty()) return s;
    sz_t n = s.find_last_not_of(spacechars);
    return (n == str_t::npos) ? "" : (n >= s.length()-1 ? s : s.substr(0, n+1));
  }
 
  /**
   * Trim white spaces left
   * @param str_t s
   * @param const char *spacechars=" "
   */
  static str_t ltr(const str_t & s, const char* spacechars=" ")
  {
    if(s.empty()) return s;
    sz_t n = s.find_first_not_of(spacechars);
    return (n == str_t::npos) ? "" : (n >= s.length() ? s : s.substr(n, str_t::npos));
  }
 
  /**
   * Trim white spaces
   * @param str_t s
   * @param const char *spacechars=" "
   */
  static inline str_t tr(const str_t & s, const char* spacechars=" ")
  { return rtr(ltr(s, spacechars), spacechars); }
 
  /**
   * Returns true if a given string represents an unsigned integer
   * @param const str_t & s
   * @return bool
   */
  static inline bool isuint(const str_t & s)
  { return s.length()>0 && s.length()<11 && (unsigned) std::count_if(s.begin(), s.end(), ::isdigit)
      + (s[0]=='+' ? 1:0) == s.length(); }
 
  /**
   * Returns true if a given string represents an unsigned integer
   * @param const str_t & s
   * @return bool
   */
  static inline bool isint(const str_t & s)
  { return s.length()>0 && s.length()<11 && (unsigned) std::count_if(s.begin(), s.end(), ::isdigit)
          + ((s[0]=='-' || s[0]=='+') ? 1:0) == s.length(); }
 
  /**
   * Alpha to long wrapper
   * @param const str_t & s
   * @return long
   */
  static inline long s2l(const str_t & s)
  { return ::atol(s.c_str()); }
 
  /**
   * Long to string wrapper
   * @return str_t
   */
  static inline str_t l2s(long l)
  { std::stringstream ss; ss << l; return ss.str(); }
 
  /**
   * Unsigned long to string wrapper
   * @return str_t
   */
  static inline str_t ul2s(unsigned long ul)
  { std::stringstream ss; ss << ul; return ss.str(); }
 
  /**
   * String to double
   * @param const str_t & s
   * @return double
   */
  static inline double s2d(const str_t & s)
  { std::stringstream ss(s); double d; return (!(ss>>d) || !ss.eof()) ?
    std::numeric_limits<double>::quiet_NaN() : d; }
 
  /**
   * Double to string
   * @return str_t
   */
  static inline str_t d2s(double d)
  { std::stringstream ss; ss << d; return ss.str(); }
 
  /**
   * String clear with memory shrinking deallocation. c++11: shrinktofit, more general: swap.
   * @param str_t s
   */
  static inline void sc(str_t & s)
  { s.clear(); str_t().swap(s); }
 
  /**
   * Generate a simple etag from file path and data
   * @param filename
   * @param const struct stat & st
   * @return str_t
   */
  static str_t mketag(const str_t & filename, const struct stat & st)
  {
    static const char* enc = "0123456789abcdefghijklmnopqrstuv----";
    static const str_t::size_type l = 32;
    str_t s; s.reserve(l);
    uint64_t v;
    for(v=(uint64_t) st.st_mtime; v>0 && s.length()<l; v>>=5u) s += enc[v&((1u<<5)-1)];
    for(v=(uint64_t) st.st_size; v>0 && s.length()<l; v>>=5u) s += enc[v&((1u<<5)-1)];
    for(v=(uint64_t) st.st_ino; v>0 && s.length()<l; v>>=5u) s += enc[v&((1u<<5)-1)];
    for(v=(uint64_t) filename.length(); v>0 && s.length()<l; v>>=5u) s += enc[v&((1u<<5)-1)];
    v = 0;
    for(str_t::const_iterator it=filename.begin(); it!=filename.end(); ++it) v += (uint64_t) *it;
    for(; v>0 && s.length()<l; v>>=5u) s += enc[v&((1u<<5)-1)];
    return s;
  }
  // </editor-fold>
 
protected:
 
  // <editor-fold desc="Instance variables" defaultstate="collapsed">
 
  //////////////////////////////////////////////////////////////////////////////
  // Instance variables / internal types
 
  state_t state_;               // Current parser state
  long rlen_;                   // Temporary, remaining length, used to match request content length.
  uint32_t lch_;                // Last characters used for input parsing, temporary
  long n_sent_;                 // Number of sent content bytes (without header)
  str_t rx_;                    // "request header text", temporary
  request_t rq_;                // Request
  response_t rs_;               // Response
  storage_t storage_;           // The storage provider
  int error_;                   // Error number
  str_t error_text_;            // String of last error, if set
  // </editor-fold>
 
public:
 
  // <editor-fold desc="dump()" defaultstate="collapsed">
 
  str_t dump()
  {
    std::stringstream ss;
    const char endl = '\n';
 
    ss << "basic_http<...> = {" << endl
       << "  state           : ";
    switch(state()) {
      case st_r_header: ss << "rx header"; break;
      case st_r_body: ss << "rx body"; break;
      case st_r_done: ss << "rx done"; break;
      case st_r_error: ss << "rx error"; break;
      case st_s_header: ss << "tx header"; break;
      case st_s_body: ss << "tx body"; break;
      case st_done: ss << "done"; break;
      case st_error: ss << "error"; break;
    }
    ss << endl
       << "  modified()      : " << modified() << endl
       << "  num_read_bts_exp: " << num_read_bytes_expected_left() << endl
       << "  num_send_bts_exp: " << num_send_bytes_expected_left() << endl
       << "  request() {" << endl
       << "    method        : ";
      switch(rq_.method) {
        case rq_get: ss << "GET"; break;
        case rq_head: ss << "HEAD"; break;
        case rq_post: ss << "POST"; break;
        case rq_put: ss << "PUT"; break;
        case rq_delete: ss << "DELETE"; break;
        case rq_options: ss << "OPTIONS"; break;
        case rq_trace: ss << "TRACE"; break;
        case rq_mkcol: ss << "MKCOL"; break;
        case rq_move: ss << "MOVE"; break;
        case rq_copy: ss << "COPY"; break;
        case rq_lock: ss << "LOCK"; break;
        case rq_unlock: ss << "UNLOCK"; break;
        case rq_invalid: ss << "INVALID"; break;
      }
    ss << endl;
    if(!rq_.service.empty()) ss << "    service       : " << rq_.service << endl;
    if(!rq_.host.empty()) ss << "    host          : " << rq_.host << endl;
    if(!rq_.path.empty()) ss << "    path          : " << rq_.path << endl;
    if(!rq_.query_string.empty()) ss << "    query_string  : " << rq_.query_string << endl;
    if(!rq_.query_hash.empty()) ss << "    query_hash    : " << rq_.query_hash << endl;
    ss << "    http_ver      : 1." << rq_.http_version << endl;
    if(!rq_.referer.empty()) ss << "    referer       : " << rq_.referer << endl;
    if(!rq_.accept.empty()) ss << "    accept:       : " << rq_.accept << endl;
    if(!rq_.user_agent.empty()) ss << "    user_agent    : " << rq_.user_agent << endl;
    if(!rq_.content_type.empty()) ss << "    content_type  : " << rq_.content_type << endl;
    if(rq_.content_length>-1) ss << "    content_length: " << rq_.content_length << endl;
    if(!rq_.if_modified_since) ss << "    if_mod_since  : " << rq_.if_modified_since << endl;
    if(!rq_.if_unmodified_since) ss << "    if_unmod_since: " << rq_.if_unmodified_since << endl;
    ss << "    keep_alive    : " << rq_.keep_alive << endl;
    ss << "    no_cache      : " << rq_.no_cache << endl;
    if(!rq_.range.empty()) {
      ss << "    range         : ";
      for(range_t::const_iterator it=rq_.range.begin(); it!=rq_.range.end(); ++it) {
        ss << "[" << it->first << "->" << it->second << "] ";
      }
      ss << endl;
    }
    ss << "    data (ram)    : " << rq_.data.length() << " bytes" << endl
       << "    storage()     : " << storage().size() << " bytes" << endl;
    if(!rq_.headers.empty()) {
      ss << "    headers() {" << endl;
      for(str_map_t::const_iterator it=rq_.headers.begin(); it != rq_.headers.end(); ++it) {
        ss << "      \"" << it->first << "\": \"" << it->second << "\"" << endl;
      }
      ss << "    }" << endl;
    }
    if(!get().empty()) {
      ss << "    get() {" << endl;
      for(str_map_t::const_iterator it = get().begin(); it != get().end(); ++it) {
        ss << "      \"" << it->first << "\": \"" << it->second << "\"" << endl;
      }
      ss << "    }" << endl;
    }
    if(!post().empty()) {
      ss << "    post() {" << endl;
      for(str_map_t::const_iterator it = post().begin(); it != post().end(); ++it) {
        ss << "      \"" << it->first << "\": \"" << it->second << "\"" << endl;
      }
      ss << "    }" << endl;
    }
    if(!rq_.cookie.empty()) {
      ss << "    cookie() {" << endl;
      for(str_map_t::const_iterator it = rq_.cookie.begin(); it != rq_.cookie.end(); ++it) {
        ss << "      \"" << it->first << "\": \"" << it->second << "\"" << endl;
      }
      ss << "    }" << endl;
    }
    if(!rq_.files.empty()) {
      ss << "    files() {" << endl;
      for(typename upload_files_t::const_iterator it= rq_.files.begin(); it!=rq_.files.end(); ++it){
        ss << "      \"" << it->first << "\": {" << endl
           << "        filename    : \"" << it->second.filename << "\"" << endl
           << "        content-type: \"" << it->second.content_type << "\"" << endl
           << "        encoding    : \"" << it->second.content_encoding << "\"" << endl
           << "        start       : " << it->second.start << endl
           << "        size        : " << it->second.size << endl
           << "        data        : " << it->second.data.length() << " bytes" << endl
           << "      }" << endl ;
      }
    }
    ss << "  }" << endl
       << "  response() {" << endl
       << "    status         : " << response().status << endl
       << "    date           : " << response().date << endl
       << "    content_type   :" << response().content_type << endl
       << "    content_length :" << response().content_length << endl
       << "    etag           :" << response().etag << endl
       << "    data           : " << response().data.size() << " bytes" << endl
       << "    last_modified  : " << response().last_modified << endl
       << "    accept_ranges  : " << response().accept_ranges << endl
       << "    cache_ctrl     : ";
       switch(response().cache_ctrl) {
         case cache_ctrl_none: ss << "cache_ctrl_none"; break;
         case cache_ctrl_public: ss << "cache_ctrl_public"; break;
         case cache_ctrl_private: ss << "cache_ctrl_private"; break;
         case cache_ctrl_no_cache: ss << "cache_ctrl_no_cache"; break;
         case cache_ctrl_no_store: ss << "cache_ctrl_no_store"; break;
       }
    ss << endl
       << "    transf_encoding: ";
       switch(response().transf_encoding) {
         case tfe_omit: ss << "tfe_omit"; break;
         case tfe_chunked: ss << "tfe_chunked"; break;
         case tfe_deflate: ss << "tfe_deflate"; break;
         case tfe_gzip: ss << "tfe_gzip"; break;
       }
    ss << endl
       << "    keep_alive     : " <<response().keep_alive << endl
       << "    cookie() {" << endl;
      for(str_map_t::const_iterator it=response().cookie.begin(); it != response().cookie.end(); ++it) {
        ss << "      \"" << it->first << "\": \"" << it->second << "\"" << endl;
      }
    ss << "    }" << endl
       << "  }" << endl
       << "}" << endl;
    return ss.str();
  }
  // </editor-fold>
 
public:
 
  // <editor-fold desc="Class variables" defaultstate="collapsed">
 
  static config_t config;       // On config to rule them all
  static const double nan;      // Convenience NaN
  static const str_t nstr;      // For referencing "not found" string in case of const str_t & returns.
 
  // </editor-fold>
};
 
// <editor-fold desc="Statics & operators" defaultstate="collapsed">
 
////////////////////////////////////////////////////////////////////////////////
// Static variables
 
template <typename S>
typename basic_http<S>::config_t basic_http<S>::config;
 
template <typename S>
const typename basic_http<S>::str_t basic_http<S>::nstr;
 
template <typename S>
const double basic_http<S>::nan = std::numeric_limits<double>::quiet_NaN();
 
// </editor-fold>
}}
 
// <editor-fold desc="Specialisation" defaultstate="collapsed">
 
////////////////////////////////////////////////////////////////////////////////
// Default specialisation
 
namespace sw {
  typedef detail::basic_http<> http;
}
 
// </editor-fold>
 
#endif