From 40b556d3742a1f65d67e2d4c760d0b13fe8be5b7 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 23 Jan 2015 07:53:17 +0100 Subject: evhttpd implementation - *Replace usage of boost::asio with [libevent2](http://libevent.org/)*. boost::asio is not part of C++11, so unlike other boost there is no forwards-compatibility reason to stick with it. Together with #4738 (convert json_spirit to UniValue), this rids Bitcoin Core of the worst offenders with regard to compile-time slowness. - *Replace spit-and-duct-tape http server with evhttp*. Front-end http handling is handled by libevent, a work queue (with configurable depth and parallelism) is used to handle application requests. - *Wrap HTTP request in C++ class*; this makes the application code mostly HTTP-server-neutral - *Refactor RPC to move all http-specific code to a separate file*. Theoreticaly this can allow building without HTTP server but with another RPC backend, e.g. Qt's debug console (currently not implemented) or future RPC mechanisms people may want to use. - *HTTP dispatch mechanism*; services (e.g., RPC, REST) register which URL paths they want to handle. By using a proven, high-performance asynchronous networking library (also used by Tor) and HTTP server, problems such as #5674, #5655, #344 should be avoided. What works? bitcoind, bitcoin-cli, bitcoin-qt. Unit tests and RPC/REST tests pass. The aim for now is everything but SSL support. Configuration options: - `-rpcthreads`: repurposed as "number of work handler threads". Still defaults to 4. - `-rpcworkqueue`: maximum depth of work queue. When this is reached, new requests will return a 500 Internal Error. - `-rpctimeout`: inactivity time, in seconds, after which to disconnect a client. - `-debug=http`: low-level http activity logging --- src/httpserver.h | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/httpserver.h (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h new file mode 100644 index 0000000000..c6a7804195 --- /dev/null +++ b/src/httpserver.h @@ -0,0 +1,138 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HTTPSERVER_H +#define BITCOIN_HTTPSERVER_H + +#include +#include +#include +#include +#include + +struct evhttp_request; +struct event_base; +class CService; +class HTTPRequest; + +/** Start HTTP server */ +bool StartHTTPServer(boost::thread_group& threadGroup); +/** Interrupt HTTP server threads */ +void InterruptHTTPServer(); +/** Stop HTTP server */ +void StopHTTPServer(); + +/** Handler for requests to a certain HTTP path */ +typedef boost::function HTTPRequestHandler; +/** Register handler for prefix. + * If multiple handlers match a prefix, the first-registered one will + * be invoked. + */ +void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler); +/** Unregister handler for prefix */ +void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch); + +/** Return evhttp event base. This can be used by submodules to + * queue timers or custom events. + */ +struct event_base* EventBase(); + +/** In-flight HTTP request. + * Thin C++ wrapper around evhttp_request. + */ +class HTTPRequest +{ +private: + struct evhttp_request* req; + bool replySent; + +public: + HTTPRequest(struct evhttp_request* req); + ~HTTPRequest(); + + enum RequestMethod { + UNKNOWN, + GET, + POST, + HEAD, + PUT + }; + + /** Get requested URI. + */ + std::string GetURI(); + + /** Get CService (address:ip) for the origin of the http request. + */ + CService GetPeer(); + + /** Get request method. + */ + RequestMethod GetRequestMethod(); + + /** + * Get the request header specified by hdr, or an empty string. + * Return an pair (isPresent,string). + */ + std::pair GetHeader(const std::string& hdr); + + /** + * Read request body. + * + * @note As this consumes the underlying buffer, call this only once. + * Repeated calls will return an empty string. + */ + std::string ReadBody(); + + /** + * Write output header. + * + * @note call this before calling WriteErrorReply or Reply. + */ + void WriteHeader(const std::string& hdr, const std::string& value); + + /** + * Write HTTP reply. + * nStatus is the HTTP status code to send. + * strReply is the body of the reply. Keep it empty to send a standard message. + * + * @note Can be called only once. As this will give the request back to the + * main thread, do not call any other HTTPRequest methods after calling this. + */ + void WriteReply(int nStatus, const std::string& strReply = ""); +}; + +/** Event handler closure. + */ +class HTTPClosure +{ +public: + virtual void operator()() = 0; + virtual ~HTTPClosure() {} +}; + +/** Event class. This can be used either as an cross-thread trigger or as a timer. + */ +class HTTPEvent +{ +public: + /** Create a new event */ + HTTPEvent(struct event_base* base, bool deleteWhenTriggered, HTTPClosure* handler); + ~HTTPEvent(); + + /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after + * the given time has elapsed. + */ + void trigger(struct timeval* tv); + + /** Internal function for handling, do not call directly */ + void _handle(); + +private: + bool deleteWhenTriggered; + struct event* ev; + boost::scoped_ptr handler; +}; + +#endif // BITCOIN_HTTPSERVER_H -- cgit v1.2.3 From be33f3f50b7358bbad9e16bf730fac2ab3c4886b Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 28 Aug 2015 16:46:20 +0200 Subject: Implement RPCTimerHandler for Qt RPC console Implement RPCTimerHandler for Qt RPC console, so that `walletpassphrase` works with GUI and `-server=0`. Also simplify HTTPEvent-related code by using boost::function directly. --- src/httpserver.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h index c6a7804195..648e8b6f86 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -117,8 +117,11 @@ public: class HTTPEvent { public: - /** Create a new event */ - HTTPEvent(struct event_base* base, bool deleteWhenTriggered, HTTPClosure* handler); + /** Create a new event. + * deleteWhenTriggered deletes this event object after the event is triggered (and the handler called) + * handler is the handler to call when the event is triggered. + */ + HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function& handler); ~HTTPEvent(); /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after @@ -126,13 +129,10 @@ public: */ void trigger(struct timeval* tv); - /** Internal function for handling, do not call directly */ - void _handle(); - -private: bool deleteWhenTriggered; + boost::function handler; +private: struct event* ev; - boost::scoped_ptr handler; }; #endif // BITCOIN_HTTPSERVER_H -- cgit v1.2.3 From 6d2bc221463ffe3ed3a99e8c682b090983b2e7b5 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 28 Aug 2015 17:14:51 +0200 Subject: Document options for new HTTP/RPC server in --help --- src/httpserver.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h index 648e8b6f86..1b0d77ad4d 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -11,6 +11,10 @@ #include #include +static const int DEFAULT_HTTP_THREADS=4; +static const int DEFAULT_HTTP_WORKQUEUE=16; +static const int DEFAULT_HTTP_TIMEOUT=30; + struct evhttp_request; struct event_base; class CService; -- cgit v1.2.3 From 3a174cd400c6c239539d4c0c10b557c3e0615212 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 28 Aug 2015 16:55:16 +0200 Subject: Fix race condition between starting HTTP server thread and setting EventBase() Split StartHTTPServer into InitHTTPServer and StartHTTPServer to give clients a window to register their handlers without race conditions. Thanks @ajweiss for figuring this out. --- src/httpserver.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h index 1b0d77ad4d..459c60c047 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -20,7 +20,14 @@ struct event_base; class CService; class HTTPRequest; -/** Start HTTP server */ +/** Initialize HTTP server. + * Call this before RegisterHTTPHandler or EventBase(). + */ +bool InitHTTPServer(); +/** Start HTTP server. + * This is separate from InitHTTPServer to give users race-condition-free time + * to register their handlers between InitHTTPServer and StartHTTPServer. + */ bool StartHTTPServer(boost::thread_group& threadGroup); /** Interrupt HTTP server threads */ void InterruptHTTPServer(); -- cgit v1.2.3 From 2190ea6c4e7f56f29fc18539284801a5f504ee48 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Fri, 18 Sep 2015 15:45:38 +0200 Subject: rpc: Split option -rpctimeout into -rpcservertimeout and -rpcclienttimeout The two timeouts for the server and client, are essentially different: - In the case of the server it should be a lower value to avoid clients clogging up connection slots - In the case of the client it should be a high value to accomedate slow responses from the server, for example for slow queries or when the lock is contended Split the options into `-rpcservertimeout` and `-rpcclienttimeout` with respective defaults of 30 and 900. --- src/httpserver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h index 459c60c047..b377dc19fc 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -13,7 +13,7 @@ static const int DEFAULT_HTTP_THREADS=4; static const int DEFAULT_HTTP_WORKQUEUE=16; -static const int DEFAULT_HTTP_TIMEOUT=30; +static const int DEFAULT_HTTP_SERVER_TIMEOUT=30; struct evhttp_request; struct event_base; -- cgit v1.2.3 From a264c32e3321ae909ca59cb8ce8bf5d812dbc4e1 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 11 Nov 2015 17:34:10 +0100 Subject: http: speed up shutdown This continues/fixes #6719. `event_base_loopbreak` was not doing what I expected it to, at least in libevent 2.0.21. What I expected was that it sets a timeout, given that no other pending events it would exit in N seconds. However, what it does was delay the event loop exit with 10 seconds, even if nothing is pending. Solve it in a different way: give the event loop thread time to exit out of itself, and if it doesn't, send loopbreak. This speeds up the RPC tests a lot, each exit incurred a 10 second overhead, with this change there should be no shutdown overhead in the common case and up to two seconds if the event loop is blocking. As a bonus this breaks dependency on boost::thread_group, as the HTTP server minds its own offspring. --- src/httpserver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/httpserver.h') diff --git a/src/httpserver.h b/src/httpserver.h index b377dc19fc..20a119cc5c 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -28,7 +28,7 @@ bool InitHTTPServer(); * This is separate from InitHTTPServer to give users race-condition-free time * to register their handlers between InitHTTPServer and StartHTTPServer. */ -bool StartHTTPServer(boost::thread_group& threadGroup); +bool StartHTTPServer(); /** Interrupt HTTP server threads */ void InterruptHTTPServer(); /** Stop HTTP server */ -- cgit v1.2.3