aboutsummaryrefslogtreecommitdiff
path: root/src/bitcoin-cli.cpp
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@gmail.com>2015-01-23 07:53:17 +0100
committerWladimir J. van der Laan <laanwj@gmail.com>2015-09-03 10:59:18 +0200
commit40b556d3742a1f65d67e2d4c760d0b13fe8be5b7 (patch)
tree2a8117475070db6a8498201f7bee6cc85f2606ad /src/bitcoin-cli.cpp
parentee2a42b447eebacc05ec4238d562b04a9a0d8462 (diff)
downloadbitcoin-40b556d3742a1f65d67e2d4c760d0b13fe8be5b7.tar.xz
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
Diffstat (limited to 'src/bitcoin-cli.cpp')
-rw-r--r--src/bitcoin-cli.cpp133
1 files changed, 93 insertions, 40 deletions
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 1c5a312874..2e094fde22 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -11,6 +11,12 @@
#include "utilstrencodings.h"
#include <boost/filesystem/operations.hpp>
+#include <stdio.h>
+
+#include <event2/event.h>
+#include <event2/http.h>
+#include <event2/buffer.h>
+#include <event2/keyvalq_struct.h>
#include "univalue/univalue.h"
@@ -32,9 +38,6 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
- strUsage += HelpMessageGroup(_("SSL options: (see the Bitcoin Wiki for SSL setup instructions)"));
- strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections"));
-
return strUsage;
}
@@ -92,32 +95,75 @@ static bool AppInitRPC(int argc, char* argv[])
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
return false;
}
+ if (GetBoolArg("-rpcssl", false))
+ {
+ fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n");
+ return false;
+ }
return true;
}
-UniValue CallRPC(const string& strMethod, const UniValue& params)
+
+/** Reply structure for request_done to fill in */
+struct HTTPReply
{
- // Connect to localhost
- bool fUseSSL = GetBoolArg("-rpcssl", false);
- boost::asio::io_service io_service;
- boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23);
- context.set_options(boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
- boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslStream(io_service, context);
- SSLIOStreamDevice<boost::asio::ip::tcp> d(sslStream, fUseSSL);
- boost::iostreams::stream< SSLIOStreamDevice<boost::asio::ip::tcp> > stream(d);
-
- const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort())));
- if (!fConnected)
- throw CConnectionFailed("couldn't connect to server");
+ int status;
+ std::string body;
+};
+
+static void http_request_done(struct evhttp_request *req, void *ctx)
+{
+ HTTPReply *reply = static_cast<HTTPReply*>(ctx);
+
+ if (req == NULL) {
+ /* If req is NULL, it means an error occurred while connecting, but
+ * I'm not sure how to find out which one. We also don't really care.
+ */
+ reply->status = 0;
+ return;
+ }
- // Find credentials to use
+ reply->status = evhttp_request_get_response_code(req);
+
+ struct evbuffer *buf = evhttp_request_get_input_buffer(req);
+ if (buf)
+ {
+ size_t size = evbuffer_get_length(buf);
+ const char *data = (const char*)evbuffer_pullup(buf, size);
+ if (data)
+ reply->body = std::string(data, size);
+ evbuffer_drain(buf, size);
+ }
+}
+
+UniValue CallRPC(const string& strMethod, const UniValue& params)
+{
+ std::string host = GetArg("-rpcconnect", "127.0.0.1");
+ int port = GetArg("-rpcport", BaseParams().RPCPort());
+
+ // Create event base
+ struct event_base *base = event_base_new(); // TODO RAII
+ if (!base)
+ throw runtime_error("cannot create event_base");
+
+ // Synchronously look up hostname
+ struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII
+ if (evcon == NULL)
+ throw runtime_error("create connection failed");
+ evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30));
+
+ HTTPReply response;
+ struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII
+ if (req == NULL)
+ throw runtime_error("create http request failed");
+
+ // Get credentials
std::string strRPCUserColonPass;
if (mapArgs["-rpcpassword"] == "") {
// Try fall back to cookie-based authentication if no password is provided
if (!GetAuthCookie(&strRPCUserColonPass)) {
throw runtime_error(strprintf(
- _("You must set rpcpassword=<password> in the configuration file:\n%s\n"
- "If the file does not exist, create it with owner-readable-only file permissions."),
+ _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"),
GetConfigFile().string().c_str()));
}
@@ -125,34 +171,41 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
- // HTTP basic authentication
- map<string, string> mapRequestHeaders;
- mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
-
- // Send request
- string strRequest = JSONRPCRequest(strMethod, params, 1);
- string strPost = HTTPPost(strRequest, mapRequestHeaders);
- stream << strPost << std::flush;
-
- // Receive HTTP reply status
- int nProto = 0;
- int nStatus = ReadHTTPStatus(stream, nProto);
+ struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req);
+ assert(output_headers);
+ evhttp_add_header(output_headers, "Host", host.c_str());
+ evhttp_add_header(output_headers, "Connection", "close");
+ evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
+
+ // Attach request data
+ std::string strRequest = JSONRPCRequest(strMethod, params, 1);
+ struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req);
+ assert(output_buffer);
+ evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
+
+ int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/");
+ if (r != 0) {
+ evhttp_connection_free(evcon);
+ event_base_free(base);
+ throw CConnectionFailed("send http request failed");
+ }
- // Receive HTTP reply message headers and body
- map<string, string> mapHeaders;
- string strReply;
- ReadHTTPMessage(stream, mapHeaders, strReply, nProto, std::numeric_limits<size_t>::max());
+ event_base_dispatch(base);
+ evhttp_connection_free(evcon);
+ event_base_free(base);
- if (nStatus == HTTP_UNAUTHORIZED)
+ if (response.status == 0)
+ throw CConnectionFailed("couldn't connect to server");
+ else if (response.status == HTTP_UNAUTHORIZED)
throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
- else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR)
- throw runtime_error(strprintf("server returned HTTP error %d", nStatus));
- else if (strReply.empty())
+ else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR)
+ throw runtime_error(strprintf("server returned HTTP error %d", response.status));
+ else if (response.body.empty())
throw runtime_error("no response from server");
// Parse reply
UniValue valReply(UniValue::VSTR);
- if (!valReply.read(strReply))
+ if (!valReply.read(response.body))
throw runtime_error("couldn't parse reply from server");
const UniValue& reply = valReply.get_obj();
if (reply.empty())