All Classes Namespaces Functions Variables Groups Pages
HttpServer.qm.dox.h
// -*- mode: c++; indent-tabs-mode: nil -*-
// @file HttpServer.qm HTTP multi-threaded server module definition
/* HttpServer.qm Copyright 2012 David Nichols
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
// need mime definitions
/* Version History
* 2012-12-18 v0.3.6: David Nichols <david@qore.org>
+ fixed a race condition in tracking active requests in dynamic HTTP handlers and removing dynamic HTTP handlers
* 2012-09-20 v0.3.5: David Nichols <david@qore.org>
+ fixed bugs in regexes in the HttpServer::addListeners() and HttpServer::addListenersWithHandler() methods
+ updated module example code in summary
+ added "listener-id" to request context hash
* 2012-06-01 v0.3.4: David Nichols <david@qore.org>
+ added public parse_uri_query() function
* 2012-05-24 v0.3.3: David Nichols <david@qore.org>
+ updated to a user module
+ added support for custom redirects from user handlers
+ use the Mime module for mime definitions
* v0.3.2: David Nichols <david@qore.org>
+ added support for listeners with specific handler lists
+ added the ability to manage dynamic content handlers
+ implemented content-encoding handling flags in AbstractHttpRequestHandler
+ implemented support for "identity" encoding (if anyone ever sends it)
* v0.3.1: David Nichols <david@qore.org>
+ added ipv6 support in qore 0.8.2
* v0.3.0: David Nichols <david@qore.org>
+ added the ability to start and stop listeners on demand
* v0.2.9: David Nichols <david@qore.org>
+ updates for new SSL and timeout behavior with with Socket class with qore 0.8.1+
+ set socket encoding to UTF-8 by default
+ add "charset=utf-8" to Content-Type header if not already present
+ add "text/html" to Content-Type header if no content-type is given by the handler
+ fixed setting X.509 certificate and private key for HTTPS listeners
+ require qore >= 0.8.1 for new Socket features
* v0.2.8: David Nichols <david@qore.org>
+ converted to hard typing for use with %require-types
+ require qore >= 0.8.0 for new Socket features
* v0.2.7: David Nichols <david@qore.org>
+ set TCP_NODELAY on all sockets to ensure that clients get all data before closing the socket, especially in case of errors
+ require qore >= 0.7.4 for new Socket features
* v0.2.6: David Nichols <david@qore.org>
+ minor fixes for SOAP support
+ improved handler matching
* v0.2.5: David Nichols <david@qore.org>
+ minor fixes for SOAP support
* v0.2.4: David Nichols <david@qore.org>
+ improved Content-Type handling
+ improved URL/path support
* v0.2.3: David Nichols <david@qore.org>
+ bzip2 content-encoding support
* v0.2.2: David Nichols <david@qore.org>
+ basic authentication
* v0.2.1: David Nichols <david@qore.org>
+ implementing logic to handle "deflate" and "gzip" content-encoding
+ chunked content-encoding supported for POSTs
+ Date: header always sent as per HTTP 1.1 spec
* v0.2.0: David Nichols <david@qore.org>
+ modular/multiple listener support added
+ https support added
note that this server suffers from the following limitations, among many others:
+ not totally HTTP 1.1 compliant although claims to be
*/
namespace HttpServer {
string get_exception_string(hash ex);
hash parse_uri_query(string path);
};
// class containing handler info
class HttpServer::HandlerInfo {
public:
public :
string name;
AbstractHttpRequestHandler obj;
string url;
*string content;
*list shdr;
public:
constructor(string name, AbstractHttpRequestHandler obj, string url, *string content, *softlist shdr);
};
// class to implement handler-handling (private)
class HttpServer::HttpHandlerList {
public:
public :
hash handlers;
public:
private:
static checkSpecialHeaders(reference sh);
public:
setHandler(string name, string url, *string content, AbstractHttpRequestHandler obj, *softlist special_headers);
// matches a handler to the request
*HandlerInfo findHandler(*hash url, hash hdr, bool final = False);
bool empty();
int size();
};
// class containing dynamic handler info
class HttpServer::DynamicHandlerInfo : public HttpServer::HandlerInfo {
public:
public :
Counter counter();
public:
constructor(string name, AbstractHttpRequestHandler obj, string url, *string content, *softlist shdr);
};
// maintains the request count for dynamic handlers (private)
class HttpServer::DynamicHandlerHelper {
public:
private:
public:
private :
Counter c;
public:
constructor(Counter c);
destructor();
};
// for dynamic handler-handling (private)
class HttpServer::DynamicHttpHandlerList : public HttpServer::HttpHandlerList {
public:
private :
RWLock dhl();
public:
setHandler(string name, string url, *string content_type, AbstractHttpRequestHandler obj, *softlist special_headers);
removeHandler(string name);
*DynamicHandlerInfo findHandler(*hash url, hash hdr, reference dhh);
};
class HttpServer::AbstractAuthenticator {
public:
bool requiresAuthentication();
string getRealm();
list authenticate(string user, string pass = "");
list authenticateByIP(string ip, reference user);
};
class HttpServer::AbstractHttpRequestHandler {
public:
public :
*AbstractAuthenticator auth;
bool decompress = True;
bool decompress_to_string = True;
public:
constructor(*AbstractAuthenticator auth);
hash handleRequest(hash cx, hash hdr, *data body);
static *string getLogMessage(hash cx, hash api, reference params, *reference args);
*hash saveThreadLocalData();
restoreThreadLocalData(*hash data);
};
// Listener will be assigned to private members of the HttpServer; is not directly accessible from code outside the HttpServer (private)
class HttpServer::HttpListener : public Qore::Socket {
public:
private :
HttpServer serv;
Sequence ss;
*SSLCertificate cert;
*SSLPrivateKey key;
bool ssl = False;
any socket;
hash socket_info;
// connection counter
Counter cThreads();
bool exit = False;
bool stopped = False;
int id;
// stop mutex
Mutex sm();
// code references to external logging functions
*code logger;
*code errorlogger;
// stop notification closure
*code stopc;
string name;
const PollInterval = 300ms;
public:
public :
// TID of the background listener thread
int tid;
// listener-specific handlers
HttpHandlerList handlers();
public:
// params: server, id, session ID sequence object, socket, rbac obj, [cert, key]
constructor(HttpServer server, int id, Sequence ss, any sock, *SSLCertificate cert, *SSLPrivateKey key, *hash hi, *code logger, *code errorlogger, *code stopc, string name);
copy();
destructor();
string getName();
any getAddress();
int getID();
bool isSecure();
hash getInfo();
stopNoWait();
stop();
log();
logError();
private:
mainThread();
public:
// thread for handling communication per connection
private:
connectionThread(Socket s, hash info);
public:
};
class HttpServer::HttpServer {
public:
public :
const Version = "0.3.6";
const ReadTimeout = 30000; // recvs timeout after 30 seconds
const PollTimeout = 5000; // check for exit every 5 seconds while waiting
// logging options
const LP_LOGPARAMS = 1 << 16;
const LP_LEVELMASK = LP_LOGPARAMS - 1;
const AIFlags = AI_PASSIVE | AI_ADDRCONFIG;
const HttpCodes =
(
// 100s: Informational
"100": "Continue",
"101": "Switching Protocols",
// RFC 2518: WebDAV
"102": "Processing",
// 200s: Success
"200": "OK",
"201": "Created",
"202": "Accepted",
"203": "Non-Authoritative Information",
"204": "No Content",
"205": "Reset Content",
"206": "Partial Content",
// RFC 4918: WebDAV: The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made
"207": "Multi-Status",
// RFC 5842: WebDAV: The members of a DAV binding have already been enumerated in a previous reply to this request, and are not being included again
"208": "Already Reported",
// RFC 3229
"226": "IM Used",
// 300s: Redirection
"300": "Multiple Choices",
"301": "Moved Permanently",
"302": "Found",
"303": "See Other",
"304": "Not Modified",
"305": "Use Proxy",
//"306": "(Reserved)",
"307": "Temporary Redirect",
// 400s: Client Errors
"400": "Bad Request",
"401": "Unauthorized",
"402": "Payment Required",
"403": "Forbidden",
"404": "Not Found",
"405": "Method Not Allowed",
"406": "Not Acceptable",
"407": "Proxy Authentication Required",
"408": "Request Timeout",
"409": "Conflict",
"410": "Gone",
"411": "Length Required",
"412": "Precondition Failed",
"413": "Request Entity Too Large",
"414": "Request-URI Too Long",
"415": "Unsupported Media Type",
"416": "Requested Range Not Satisfiable",
"417": "Expectation Failed",
// RFC 2324: http://tools.ietf.org/html/rfc2324
"418": "I'm a teapot",
// Returned by the Twitter Search and Trends API when the client is being rate limited
"420": "Enhance Yextern Calm",
// RFC 4918: WebDAV: The request was well-formed but was unable to be followed due to semantic errors
"422": "Unprocessable Entity",
// RFC 4918: WebDAV: The resource that is being accessed is locked
"423": "Locked",
// RFC 4918: WebDAV: The request failed due to failure of a previous request (e.g. a PROPPATCH)
"424": "Failed Dependency",
// Internet draft: Defined in drafts of "WebDAV Advanced Collections Protocol", but not present in "Web Distributed Authoring and Versioning (WebDAV) Ordered Collections Protocol"
"425": "Unordered Collection",
// RFC 2817: The client should switch to a different protocol such as TLS/1.0
"426": "Upgrade Required",
// RFC 6585: The origin server requires the request to be conditional. Intended to prevent "the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict."
"428": "Precondition Required",
// RFC 6585: The user has sent too many requests in a given amount of time. Intended for use with rate limiting schemes
"429": "Too Many Requests",
// RFC 6585
"431": "Request Header Fields Too Large",
// 500s: Server Errors
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"505": "HTTP Version Not Supported",
"509": "Bandwidth Limit Exceeded",
// RFC 2774: Further extensions to the request are required for the server to fulfill it
"510": "Not Extended",
// RFC 6585: The client needs to authenticate to gain network access. Intended for use by intercepting proxies used to control access to the network (e.g. "captive portals" used to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot)
"511": "Network Authentication Required",
);
public:
private :
*code logfunc;
*code errlogfunc;
// quit server flag
bool exit = False;
// if True then verbose exception info will be logged
bool debug;
Sequence seqSessions();
Sequence seqListeners();
string httpserverstring;
bool stopped = False;
// permanent handlers; these handlers are never removed
HttpHandlerList handlers();
// default handler
hash defaultHandler;
// hash of listeners keyed by listener ID
hash listeners;
// map of bind addresses to listener IDs
hash smap;
// map of listener names to listener IDs
hash nmap;
// listener Gate
Gate lm();
// running listener counter
Counter c();
// dynamic handlers
DynamicHttpHandlerList dhandlers();
public:
constructor(*code logfunc, *code errlogfunc, bool dbg = False, string name = sprintf("Qore-HTTP-Server/%s", HttpServer::Version));
destructor();
final list addListenersWithHandler(string hname, AbstractHttpRequestHandler handler, hash lp, *code logger, *code errorlogger, *code stopc, *string name);
hash addListener(softstring sock, *string cert_path, *string key_path, *string name);
softlist addListeners(softstring sock, *string cert_path, *string key_path, *string name);
list addINETListeners(*string node, softstring service, *string cert_path, *string key_path, *string name);
copy();
hash getListeners();
hash getListenerInfo(softint id);
hash getListenerInfoName(string name);
int getListenerCount();
stopNoWait();
waitStop();
// only called from the listeners - do not call externally
listenerStopped(HttpListener l);
stop();
stopListener(softstring bind);
stopListenerID(softint id);
int getListenerTID(softint id);
setDefaultHandler(string name, AbstractHttpRequestHandler obj);
setHandler(string name, string url, *string content_type, AbstractHttpRequestHandler obj, *softlist special_headers);
setDynamicHandler(string name, string url, *string content_type, AbstractHttpRequestHandler obj, *softlist special_headers);
removeDynamicHandler(string name);
log();
logError();
logArgs(list args = ());
logErrorArgs(list args = ());
sendHttpError(Socket s, int code, string msg, bool close = False, *hash extra_hdrs, *string encoding);
static string getURLFromBind(softstring bind, *string host);
setDebug(bool dbg = True);
bool getDebug();
// don't reimplement this method; fix/enhance it in the module
private:
final HttpListener addListenerIntern(string sock, *SSLCertificate cert, *SSLPrivateKey key, *hash hi, *code logger, *code errorlogger, *code stopc, *string name);
public:
// don't reimplement this method; fix/enhance it in the module
private:
static final hash getSSLObjects(string cert_path, *string key_path);
public:
// don't reimplement this method; fix/enhance it in the module
private:
final list addINETListenersIntern(*string node, softstring service, hash sd, *hash lp, *code logger, *code errorlogger, *code stopc, *string name);
public:
// don't reimplement this method; fix/enhance it in the module
private:
final hash noHandlerError(hash cx, hash hdr, any body);
public:
// handles an incoming request - do not call externally; this method is called by the listeners when a request is received
// don't reimplement this method; fix/enhance it in the module
final handleRequest(HttpListener listener, Socket s, reference cx, hash hdr, *data body, reference close, bool head = False);
};