Skip to content

Commit 40b556d

Browse files
committed
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 bitcoin#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 bitcoin#5674, bitcoin#5655, bitcoin#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
1 parent ee2a42b commit 40b556d

15 files changed

Lines changed: 1299 additions & 1049 deletions

src/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ BITCOIN_CORE_H = \
9898
eccryptoverify.h \
9999
ecwrapper.h \
100100
hash.h \
101+
httprpc.h \
102+
httpserver.h \
101103
init.h \
102104
key.h \
103105
keystore.h \
@@ -170,6 +172,8 @@ libbitcoin_server_a_SOURCES = \
170172
bloom.cpp \
171173
chain.cpp \
172174
checkpoints.cpp \
175+
httprpc.cpp \
176+
httpserver.cpp \
173177
init.cpp \
174178
leveldbwrapper.cpp \
175179
main.cpp \

src/bitcoin-cli.cpp

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
#include "utilstrencodings.h"
1212

1313
#include <boost/filesystem/operations.hpp>
14+
#include <stdio.h>
15+
16+
#include <event2/event.h>
17+
#include <event2/http.h>
18+
#include <event2/buffer.h>
19+
#include <event2/keyvalq_struct.h>
1420

1521
#include "univalue/univalue.h"
1622

@@ -32,9 +38,6 @@ std::string HelpMessageCli()
3238
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
3339
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
3440

35-
strUsage += HelpMessageGroup(_("SSL options: (see the Bitcoin Wiki for SSL setup instructions)"));
36-
strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections"));
37-
3841
return strUsage;
3942
}
4043

@@ -92,67 +95,117 @@ static bool AppInitRPC(int argc, char* argv[])
9295
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
9396
return false;
9497
}
98+
if (GetBoolArg("-rpcssl", false))
99+
{
100+
fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
101+
return false;
102+
}
95103
return true;
96104
}
97105

98-
UniValue CallRPC(const string& strMethod, const UniValue& params)
106+
107+
/** Reply structure for request_done to fill in */
108+
struct HTTPReply
99109
{
100-
// Connect to localhost
101-
bool fUseSSL = GetBoolArg("-rpcssl", false);
102-
boost::asio::io_service io_service;
103-
boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23);
104-
context.set_options(boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
105-
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslStream(io_service, context);
106-
SSLIOStreamDevice<boost::asio::ip::tcp> d(sslStream, fUseSSL);
107-
boost::iostreams::stream< SSLIOStreamDevice<boost::asio::ip::tcp> > stream(d);
108-
109-
const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort())));
110-
if (!fConnected)
111-
throw CConnectionFailed("couldn't connect to server");
110+
int status;
111+
std::string body;
112+
};
113+
114+
static void http_request_done(struct evhttp_request *req, void *ctx)
115+
{
116+
HTTPReply *reply = static_cast<HTTPReply*>(ctx);
117+
118+
if (req == NULL) {
119+
/* If req is NULL, it means an error occurred while connecting, but
120+
* I'm not sure how to find out which one. We also don't really care.
121+
*/
122+
reply->status = 0;
123+
return;
124+
}
112125

113-
// Find credentials to use
126+
reply->status = evhttp_request_get_response_code(req);
127+
128+
struct evbuffer *buf = evhttp_request_get_input_buffer(req);
129+
if (buf)
130+
{
131+
size_t size = evbuffer_get_length(buf);
132+
const char *data = (const char*)evbuffer_pullup(buf, size);
133+
if (data)
134+
reply->body = std::string(data, size);
135+
evbuffer_drain(buf, size);
136+
}
137+
}
138+
139+
UniValue CallRPC(const string& strMethod, const UniValue& params)
140+
{
141+
std::string host = GetArg("-rpcconnect", "127.0.0.1");
142+
int port = GetArg("-rpcport", BaseParams().RPCPort());
143+
144+
// Create event base
145+
struct event_base *base = event_base_new(); // TODO RAII
146+
if (!base)
147+
throw runtime_error("cannot create event_base");
148+
149+
// Synchronously look up hostname
150+
struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII
151+
if (evcon == NULL)
152+
throw runtime_error("create connection failed");
153+
evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30));
154+
155+
HTTPReply response;
156+
struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII
157+
if (req == NULL)
158+
throw runtime_error("create http request failed");
159+
160+
// Get credentials
114161
std::string strRPCUserColonPass;
115162
if (mapArgs["-rpcpassword"] == "") {
116163
// Try fall back to cookie-based authentication if no password is provided
117164
if (!GetAuthCookie(&strRPCUserColonPass)) {
118165
throw runtime_error(strprintf(
119-
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
120-
"If the file does not exist, create it with owner-readable-only file permissions."),
166+
_("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
121167
GetConfigFile().string().c_str()));
122168

123169
}
124170
} else {
125171
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
126172
}
127173

128-
// HTTP basic authentication
129-
map<string, string> mapRequestHeaders;
130-
mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
131-
132-
// Send request
133-
string strRequest = JSONRPCRequest(strMethod, params, 1);
134-
string strPost = HTTPPost(strRequest, mapRequestHeaders);
135-
stream << strPost << std::flush;
136-
137-
// Receive HTTP reply status
138-
int nProto = 0;
139-
int nStatus = ReadHTTPStatus(stream, nProto);
174+
struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);
175+
assert(output_headers);
176+
evhttp_add_header(output_headers, "Host", host.c_str());
177+
evhttp_add_header(output_headers, "Connection", "close");
178+
evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
179+
180+
// Attach request data
181+
std::string strRequest = JSONRPCRequest(strMethod, params, 1);
182+
struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req);
183+
assert(output_buffer);
184+
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
185+
186+
int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/");
187+
if (r != 0) {
188+
evhttp_connection_free(evcon);
189+
event_base_free(base);
190+
throw CConnectionFailed("send http request failed");
191+
}
140192

141-
// Receive HTTP reply message headers and body
142-
map<string, string> mapHeaders;
143-
string strReply;
144-
ReadHTTPMessage(stream, mapHeaders, strReply, nProto, std::numeric_limits<size_t>::max());
193+
event_base_dispatch(base);
194+
evhttp_connection_free(evcon);
195+
event_base_free(base);
145196

146-
if (nStatus == HTTP_UNAUTHORIZED)
197+
if (response.status == 0)
198+
throw CConnectionFailed("couldn't connect to server");
199+
else if (response.status == HTTP_UNAUTHORIZED)
147200
throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
148-
else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR)
149-
throw runtime_error(strprintf("server returned HTTP error %d", nStatus));
150-
else if (strReply.empty())
201+
else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
202+
throw runtime_error(strprintf("server returned HTTP error %d", response.status));
203+
else if (response.body.empty())
151204
throw runtime_error("no response from server");
152205

153206
// Parse reply
154207
UniValue valReply(UniValue::VSTR);
155-
if (!valReply.read(strReply))
208+
if (!valReply.read(response.body))
156209
throw runtime_error("couldn't parse reply from server");
157210
const UniValue& reply = valReply.get_obj();
158211
if (reply.empty())

src/bitcoind.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
#include "noui.h"
1111
#include "scheduler.h"
1212
#include "util.h"
13+
#include "httpserver.h"
14+
#include "httprpc.h"
15+
#include "rpcserver.h"
1316

1417
#include <boost/algorithm/string/predicate.hpp>
1518
#include <boost/filesystem.hpp>
1619
#include <boost/thread.hpp>
1720

21+
#include <stdio.h>
22+
1823
/* Introduction text for doxygen: */
1924

2025
/*! \mainpage Developer documentation
@@ -44,7 +49,7 @@ void WaitForShutdown(boost::thread_group* threadGroup)
4449
}
4550
if (threadGroup)
4651
{
47-
threadGroup->interrupt_all();
52+
Interrupt(*threadGroup);
4853
threadGroup->join_all();
4954
}
5055
}
@@ -154,7 +159,7 @@ bool AppInit(int argc, char* argv[])
154159

155160
if (!fRet)
156161
{
157-
threadGroup.interrupt_all();
162+
Interrupt(threadGroup);
158163
// threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of
159164
// the startup-failure cases to make sure they don't result in a hang due to some
160165
// thread-blocking-waiting-for-another-thread-during-startup case

0 commit comments

Comments
 (0)