Browse Source

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
metaverse
Wladimir J. van der Laan 10 years ago
committed by Jack Grigg
parent
commit
afd64f76ea
No known key found for this signature in database GPG Key ID: 6A6914DAFBEA00DA
  1. 4
      src/Makefile.am
  2. 133
      src/bitcoin-cli.cpp
  3. 9
      src/bitcoind.cpp
  4. 201
      src/httprpc.cpp
  5. 37
      src/httprpc.h
  6. 586
      src/httpserver.cpp
  7. 138
      src/httpserver.h
  8. 44
      src/init.cpp
  9. 2
      src/init.h
  10. 243
      src/rest.cpp
  11. 229
      src/rpcprotocol.cpp
  12. 87
      src/rpcprotocol.h
  13. 554
      src/rpcserver.cpp
  14. 73
      src/rpcserver.h

4
src/Makefile.am

@ -119,6 +119,8 @@ BITCOIN_CORE_H = \
eccryptoverify.h \
ecwrapper.h \
hash.h \
httprpc.h \
httpserver.h \
init.h \
key.h \
keystore.h \
@ -203,6 +205,8 @@ libbitcoin_server_a_SOURCES = \
bloom.cpp \
chain.cpp \
checkpoints.cpp \
httprpc.cpp \
httpserver.cpp \
init.cpp \
leveldbwrapper.cpp \
main.cpp \

133
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.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;
}
@ -94,32 +97,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()));
}
@ -127,34 +173,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())

9
src/bitcoind.cpp

@ -10,11 +10,16 @@
#include "noui.h"
#include "scheduler.h"
#include "util.h"
#include "httpserver.h"
#include "httprpc.h"
#include "rpcserver.h"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <stdio.h>
/* Introduction text for doxygen: */
/*! \mainpage Developer documentation
@ -44,7 +49,7 @@ void WaitForShutdown(boost::thread_group* threadGroup)
}
if (threadGroup)
{
threadGroup->interrupt_all();
Interrupt(*threadGroup);
threadGroup->join_all();
}
}
@ -171,7 +176,7 @@ bool AppInit(int argc, char* argv[])
if (!fRet)
{
threadGroup.interrupt_all();
Interrupt(threadGroup);
// threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of
// the startup-failure cases to make sure they don't result in a hang due to some
// thread-blocking-waiting-for-another-thread-during-startup case

201
src/httprpc.cpp

@ -0,0 +1,201 @@
#include "httprpc.h"
#include "base58.h"
#include "chainparams.h"
#include "httpserver.h"
#include "rpcprotocol.h"
#include "rpcserver.h"
#include "random.h"
#include "sync.h"
#include "util.h"
#include "utilstrencodings.h"
#include "ui_interface.h"
#include <boost/algorithm/string.hpp> // boost::trim
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
* re-lock the wellet.
*/
class HTTPRPCTimer : public RPCTimerBase
{
public:
HTTPRPCTimer(struct event_base* eventBase, boost::function<void(void)>& func, int64_t seconds) : ev(eventBase, false, new Handler(func))
{
struct timeval tv = {seconds, 0};
ev.trigger(&tv);
}
private:
HTTPEvent ev;
class Handler : public HTTPClosure
{
public:
Handler(const boost::function<void(void)>& func) : func(func)
{
}
private:
boost::function<void(void)> func;
void operator()() { func(); }
};
};
class HTTPRPCTimerInterface : public RPCTimerInterface
{
public:
HTTPRPCTimerInterface(struct event_base* base) : base(base)
{
}
const char* Name()
{
return "HTTP";
}
RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t seconds)
{
return new HTTPRPCTimer(base, func, seconds);
}
private:
struct event_base* base;
};
/* Pre-base64-encoded authentication token */
static std::string strRPCUserColonPass;
/* Stored RPC timer interface (for unregistration) */
static HTTPRPCTimerInterface* httpRPCTimerInterface = 0;
static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id)
{
// Send error reply from json-rpc error object
int nStatus = HTTP_INTERNAL_SERVER_ERROR;
int code = find_value(objError, "code").get_int();
if (code == RPC_INVALID_REQUEST)
nStatus = HTTP_BAD_REQUEST;
else if (code == RPC_METHOD_NOT_FOUND)
nStatus = HTTP_NOT_FOUND;
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(nStatus, strReply);
}
static bool RPCAuthorized(const std::string& strAuth)
{
if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
return false;
if (strAuth.substr(0, 6) != "Basic ")
return false;
std::string strUserPass64 = strAuth.substr(6);
boost::trim(strUserPass64);
std::string strUserPass = DecodeBase64(strUserPass64);
return TimingResistantEqual(strUserPass, strRPCUserColonPass);
}
static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
{
// JSONRPC handles only POST
if (req->GetRequestMethod() != HTTPRequest::POST) {
req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests");
return false;
}
// Check authorization
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
if (!authHeader.first) {
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
if (!RPCAuthorized(authHeader.second)) {
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString());
/* Deter brute-forcing
If this results in a DoS the user really
shouldn't have their RPC port exposed. */
MilliSleep(250);
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
JSONRequest jreq;
try {
// Parse request
UniValue valRequest;
if (!valRequest.read(req->ReadBody()))
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
std::string strReply;
// singleton request
if (valRequest.isObject()) {
jreq.parse(valRequest);
UniValue result = tableRPC.execute(jreq.strMethod, jreq.params);
// Send reply
strReply = JSONRPCReply(result, NullUniValue, jreq.id);
// array of requests
} else if (valRequest.isArray())
strReply = JSONRPCExecBatch(valRequest.get_array());
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strReply);
} catch (const UniValue& objError) {
JSONErrorReply(req, objError, jreq.id);
return false;
} catch (const std::exception& e) {
JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
return false;
}
return true;
}
static bool InitRPCAuthentication()
{
if (mapArgs["-rpcpassword"] == "")
{
LogPrintf("No rpcpassword set - using random cookie authentication\n");
if (!GenerateAuthCookie(&strRPCUserColonPass)) {
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode
"", CClientUIInterface::MSG_ERROR);
return false;
}
} else {
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
return true;
}
bool StartHTTPRPC()
{
LogPrint("rpc", "Starting HTTP RPC server\n");
if (!InitRPCAuthentication())
return false;
RegisterHTTPHandler("/", true, HTTPReq_JSONRPC);
assert(EventBase());
httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase());
RPCRegisterTimerInterface(httpRPCTimerInterface);
return true;
}
void InterruptHTTPRPC()
{
LogPrint("rpc", "Interrupting HTTP RPC server\n");
}
void StopHTTPRPC()
{
LogPrint("rpc", "Stopping HTTP RPC server\n");
UnregisterHTTPHandler("/", true);
if (httpRPCTimerInterface) {
RPCUnregisterTimerInterface(httpRPCTimerInterface);
delete httpRPCTimerInterface;
httpRPCTimerInterface = 0;
}
}

37
src/httprpc.h

@ -0,0 +1,37 @@
// 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_HTTPRPC_H
#define BITCOIN_HTTPRPC_H
#include <string>
#include <map>
class HTTPRequest;
/** Start HTTP RPC subsystem.
* Precondition; HTTP and RPC has been started.
*/
bool StartHTTPRPC();
/** Interrupt HTTP RPC subsystem.
*/
void InterruptHTTPRPC();
/** Stop HTTP RPC subsystem.
* Precondition; HTTP and RPC has been stopped.
*/
void StopHTTPRPC();
/** Start HTTP REST subsystem.
* Precondition; HTTP and RPC has been started.
*/
bool StartREST();
/** Interrupt RPC REST subsystem.
*/
void InterruptREST();
/** Stop HTTP REST subsystem.
* Precondition; HTTP and RPC has been stopped.
*/
void StopREST();
#endif

586
src/httpserver.cpp

@ -0,0 +1,586 @@
// 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.
#include "httpserver.h"
#include "chainparamsbase.h"
#include "compat.h"
#include "util.h"
#include "netbase.h"
#include "rpcprotocol.h" // For HTTP status codes
#include "sync.h"
#include "ui_interface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/thread.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
#ifdef EVENT__HAVE_NETINET_IN_H
#include <netinet/in.h>
#ifdef _XOPEN_SOURCE_EXTENDED
#include <arpa/inet.h>
#endif
#endif
#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
/** HTTP request work item */
class HTTPWorkItem : public HTTPClosure
{
public:
HTTPWorkItem(HTTPRequest* req, const std::string &path, const HTTPRequestHandler& func):
req(req), path(path), func(func)
{
}
void operator()()
{
func(req.get(), path);
}
boost::scoped_ptr<HTTPRequest> req;
private:
std::string path;
HTTPRequestHandler func;
};
/** Simple work queue for distributing work over multiple threads.
* Work items are simply callable objects.
*/
template <typename WorkItem>
class WorkQueue
{
private:
/** Mutex protects entire object */
CWaitableCriticalSection cs;
CConditionVariable cond;
/* XXX in C++11 we can use std::unique_ptr here and avoid manual cleanup */
std::deque<WorkItem*> queue;
bool running;
size_t maxDepth;
public:
WorkQueue(size_t maxDepth) : running(true),
maxDepth(maxDepth)
{
}
/* Precondition: worker threads have all stopped */
~WorkQueue()
{
while (!queue.empty()) {
delete queue.front();
queue.pop_front();
}
}
/** Enqueue a work item */
bool Enqueue(WorkItem* item)
{
boost::unique_lock<boost::mutex> lock(cs);
if (queue.size() >= maxDepth) {
return false;
}
queue.push_back(item);
cond.notify_one();
return true;
}
/** Thread function */
void Run()
{
while (running) {
WorkItem* i = 0;
{
boost::unique_lock<boost::mutex> lock(cs);
while (running && queue.empty())
cond.wait(lock);
if (!running)
break;
i = queue.front();
queue.pop_front();
}
(*i)();
delete i;
}
}
/** Interrupt and exit loops */
void Interrupt()
{
boost::unique_lock<boost::mutex> lock(cs);
running = false;
cond.notify_all();
}
/** Return current depth of queue */
size_t Depth()
{
boost::unique_lock<boost::mutex> lock(cs);
return queue.size();
}
};
struct HTTPPathHandler
{
HTTPPathHandler() {}
HTTPPathHandler(std::string prefix, bool exactMatch, HTTPRequestHandler handler):
prefix(prefix), exactMatch(exactMatch), handler(handler)
{
}
std::string prefix;
bool exactMatch;
HTTPRequestHandler handler;
};
/** HTTP module state */
//! libevent event loop
static struct event_base* eventBase = 0;
//! HTTP server
struct evhttp* eventHTTP = 0;
//! List of subnets to allow RPC connections from
static std::vector<CSubNet> rpc_allow_subnets;
//! Work queue for handling longer requests off the event loop thread
static WorkQueue<HTTPClosure>* workQueue = 0;
//! Handlers for (sub)paths
std::vector<HTTPPathHandler> pathHandlers;
/** Check if a network address is allowed to access the HTTP server */
static bool ClientAllowed(const CNetAddr& netaddr)
{
if (!netaddr.IsValid())
return false;
BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets)
if (subnet.Match(netaddr))
return true;
return false;
}
/** Initialize ACL list for HTTP server */
static bool InitHTTPAllowList()
{
rpc_allow_subnets.clear();
rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet
rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost
if (mapMultiArgs.count("-rpcallowip")) {
const std::vector<std::string>& vAllow = mapMultiArgs["-rpcallowip"];
BOOST_FOREACH (std::string strAllow, vAllow) {
CSubNet subnet(strAllow);
if (!subnet.IsValid()) {
uiInterface.ThreadSafeMessageBox(
strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
"", CClientUIInterface::MSG_ERROR);
return false;
}
rpc_allow_subnets.push_back(subnet);
}
}
std::string strAllowed;
BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets)
strAllowed += subnet.ToString() + " ";
LogPrint("http", "Allowing HTTP connections from: %s\n", strAllowed);
return true;
}
/** HTTP request method as string - use for logging only */
static std::string RequestMethodString(HTTPRequest::RequestMethod m)
{
switch (m) {
case HTTPRequest::GET:
return "GET";
break;
case HTTPRequest::POST:
return "POST";
break;
case HTTPRequest::HEAD:
return "HEAD";
break;
case HTTPRequest::PUT:
return "PUT";
break;
default:
return "unknown";
}
}
/** HTTP request callback */
static void http_request_cb(struct evhttp_request* req, void* arg)
{
std::auto_ptr<HTTPRequest> hreq(new HTTPRequest(req));
LogPrint("http", "Received a %s request for %s from %s\n",
RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString());
// Early address-based allow check
if (!ClientAllowed(hreq->GetPeer())) {
hreq->WriteReply(HTTP_FORBIDDEN);
return;
}
// Early reject unknown HTTP methods
if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) {
hreq->WriteReply(HTTP_BADMETHOD);
return;
}
// Find registered handler for prefix
std::string strURI = hreq->GetURI();
std::string path;
std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin();
std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end();
for (; i != iend; ++i) {
bool match = false;
if (i->exactMatch)
match = (strURI == i->prefix);
else
match = (strURI.substr(0, i->prefix.size()) == i->prefix);
if (match) {
path = strURI.substr(i->prefix.size());
break;
}
}
// Dispatch to worker thread
if (i != iend) {
std::auto_ptr<HTTPWorkItem> item(new HTTPWorkItem(hreq.release(), path, i->handler));
assert(workQueue);
if (workQueue->Enqueue(item.get()))
item.release(); /* if true, queue took ownership */
else
item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded");
} else {
hreq->WriteReply(HTTP_NOTFOUND);
}
}
/** Event dispatcher thread */
static void ThreadHTTP(struct event_base* base, struct evhttp* http)
{
RenameThread("bitcoin-http");
LogPrint("http", "Entering http event loop\n");
event_base_dispatch(base);
// Event loop will be interrupted by InterruptHTTPServer()
LogPrint("http", "Exited http event loop\n");
}
/** Bind HTTP server to specified addresses */
static bool HTTPBindAddresses(struct evhttp* http)
{
int defaultPort = GetArg("-rpcport", BaseParams().RPCPort());
int nBound = 0;
std::vector<std::pair<std::string, uint16_t> > endpoints;
// Determine what addresses to bind to
if (!mapArgs.count("-rpcallowip")) { // Default to loopback if not allowing external IPs
endpoints.push_back(std::make_pair("::1", defaultPort));
endpoints.push_back(std::make_pair("127.0.0.1", defaultPort));
if (mapArgs.count("-rpcbind")) {
LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
}
} else if (mapArgs.count("-rpcbind")) { // Specific bind address
const std::vector<std::string>& vbind = mapMultiArgs["-rpcbind"];
for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) {
int port = defaultPort;
std::string host;
SplitHostPort(*i, port, host);
endpoints.push_back(std::make_pair(host, port));
}
} else { // No specific bind address specified, bind to any
endpoints.push_back(std::make_pair("::", defaultPort));
endpoints.push_back(std::make_pair("0.0.0.0", defaultPort));
}
// Bind addresses
for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) {
LogPrint("http", "Binding RPC on address %s port %i\n", i->first, i->second);
if (evhttp_bind_socket(http, i->first.empty() ? NULL : i->first.c_str(), i->second) == 0) {
nBound += 1;
} else {
LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second);
}
}
return nBound > 0;
}
/** Simple wrapper to set thread name and run work queue */
static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue)
{
RenameThread("bitcoin-httpworker");
queue->Run();
}
bool StartHTTPServer(boost::thread_group& threadGroup)
{
struct evhttp* http = 0;
struct event_base* base = 0;
if (!InitHTTPAllowList())
return false;
if (GetBoolArg("-rpcssl", false)) {
uiInterface.ThreadSafeMessageBox(
"SSL mode for RPC (-rpcssl) is no longer supported.",
"", CClientUIInterface::MSG_ERROR);
return false;
}
#ifdef WIN32
evthread_use_windows_threads();
#else
evthread_use_pthreads();
#endif
base = event_base_new(); // XXX RAII
if (!base) {
LogPrintf("Couldn't create an event_base: exiting\n");
return false;
}
/* Create a new evhttp object to handle requests. */
http = evhttp_new(base); // XXX RAII
if (!http) {
LogPrintf("couldn't create evhttp. Exiting.\n");
event_base_free(base);
return false;
}
evhttp_set_timeout(http, GetArg("-rpctimeout", 30));
evhttp_set_max_body_size(http, MAX_SIZE);
evhttp_set_gencb(http, http_request_cb, NULL);
if (!HTTPBindAddresses(http)) {
LogPrintf("Unable to bind any endpoint for RPC server\n");
evhttp_free(http);
event_base_free(base);
return false;
}
LogPrint("http", "Starting HTTP server\n");
int workQueueDepth = std::max((long)GetArg("-rpcworkqueue", 16), 1L);
int rpcThreads = std::max((long)GetArg("-rpcthreads", 4), 1L);
LogPrintf("HTTP: creating work queue of depth %d and %d worker threads\n", workQueueDepth, rpcThreads);
workQueue = new WorkQueue<HTTPClosure>(workQueueDepth);
threadGroup.create_thread(boost::bind(&ThreadHTTP, base, http));
for (int i = 0; i < rpcThreads; i++)
threadGroup.create_thread(boost::bind(&HTTPWorkQueueRun, workQueue));
eventBase = base;
eventHTTP = http;
return true;
}
void InterruptHTTPServer()
{
LogPrint("http", "Interrupting HTTP server\n");
if (eventBase)
event_base_loopbreak(eventBase);
if (workQueue)
workQueue->Interrupt();
}
void StopHTTPServer()
{
LogPrint("http", "Stopping HTTP server\n");
delete workQueue;
if (eventHTTP) {
evhttp_free(eventHTTP);
eventHTTP = 0;
}
if (eventBase) {
event_base_free(eventBase);
eventBase = 0;
}
}
struct event_base* EventBase()
{
return eventBase;
}
static void httpevent_callback_fn(evutil_socket_t, short, void* data)
{
// Static handler simply passes through execution flow to _handle method
((HTTPEvent*)data)->_handle();
}
void HTTPEvent::_handle()
{
(*handler)();
if (deleteWhenTriggered)
delete this;
}
HTTPEvent::HTTPEvent(struct event_base* base, bool deleteWhenTriggered, HTTPClosure* handler) : deleteWhenTriggered(deleteWhenTriggered), handler(handler)
{
ev = event_new(base, -1, 0, httpevent_callback_fn, this);
assert(ev);
}
HTTPEvent::~HTTPEvent()
{
event_free(ev);
}
void HTTPEvent::trigger(struct timeval* tv)
{
if (tv == NULL)
event_active(ev, 0, 0); // immediately trigger event in main thread
else
evtimer_add(ev, tv); // trigger after timeval passed
}
HTTPRequest::HTTPRequest(struct evhttp_request* req) : req(req),
replySent(false)
{
}
HTTPRequest::~HTTPRequest()
{
if (!replySent) {
// Keep track of whether reply was sent to avoid request leaks
LogPrintf("%s: Unhandled request\n", __func__);
WriteReply(HTTP_INTERNAL, "Unhandled request");
}
// evhttpd cleans up the request, as long as a reply was sent.
}
std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string& hdr)
{
const struct evkeyvalq* headers = evhttp_request_get_input_headers(req);
assert(headers);
const char* val = evhttp_find_header(headers, hdr.c_str());
if (val)
return std::make_pair(true, val);
else
return std::make_pair(false, "");
}
std::string HTTPRequest::ReadBody()
{
struct evbuffer* buf = evhttp_request_get_input_buffer(req);
if (!buf)
return "";
size_t size = evbuffer_get_length(buf);
/** Trivial implementation: if this is ever a performance bottleneck,
* internal copying can be avoided in multi-segment buffers by using
* evbuffer_peek and an awkward loop. Though in that case, it'd be even
* better to not copy into an intermediate string but use a stream
* abstraction to consume the evbuffer on the fly in the parsing algorithm.
*/
const char* data = (const char*)evbuffer_pullup(buf, size);
if (!data) // returns NULL in case of empty buffer
return "";
std::string rv(data, size);
evbuffer_drain(buf, size);
return rv;
}
void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value)
{
struct evkeyvalq* headers = evhttp_request_get_output_headers(req);
assert(headers);
evhttp_add_header(headers, hdr.c_str(), value.c_str());
}
/** Closure sent to main thread to request a reply to be sent to
* a HTTP request.
* Replies must be sent in the main loop in the main http thread,
* this cannot be done from worker threads.
*/
struct HTTPSendReplyHandler : HTTPClosure {
public:
HTTPSendReplyHandler(struct evhttp_request* req, int nStatus) : req(req), nStatus(nStatus)
{
}
void operator()()
{
evhttp_send_reply(req, nStatus, NULL, NULL);
}
private:
struct evhttp_request* req;
int nStatus;
};
void HTTPRequest::WriteReply(int nStatus, const std::string& strReply)
{
assert(!replySent && req);
// Send event to main http thread to send reply message
struct evbuffer* evb = evhttp_request_get_output_buffer(req);
assert(evb);
evbuffer_add(evb, strReply.data(), strReply.size());
HTTPEvent* ev = new HTTPEvent(eventBase, true,
new HTTPSendReplyHandler(req, nStatus));
ev->trigger(0);
replySent = true;
req = 0; // transferred back to main thread
}
CService HTTPRequest::GetPeer()
{
evhttp_connection* con = evhttp_request_get_connection(req);
CService peer;
if (con) {
// evhttp retains ownership over returned address string
const char* address = "";
uint16_t port = 0;
evhttp_connection_get_peer(con, (char**)&address, &port);
peer = CService(address, port);
}
return peer;
}
std::string HTTPRequest::GetURI()
{
return evhttp_request_get_uri(req);
}
HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod()
{
switch (evhttp_request_get_command(req)) {
case EVHTTP_REQ_GET:
return GET;
break;
case EVHTTP_REQ_POST:
return POST;
break;
case EVHTTP_REQ_HEAD:
return HEAD;
break;
case EVHTTP_REQ_PUT:
return PUT;
break;
default:
return UNKNOWN;
break;
}
}
void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
{
LogPrint("http", "Registering HTTP handler for %s (exactmath %d)\n", prefix, exactMatch);
pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
}
void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
{
std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin();
std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end();
for (; i != iend; ++i)
if (i->prefix == prefix && i->exactMatch == exactMatch)
break;
if (i != iend)
{
LogPrint("http", "Unregistering HTTP handler for %s (exactmath %d)\n", prefix, exactMatch);
pathHandlers.erase(i);
}
}

138
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 <string>
#include <stdint.h>
#include <boost/thread.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/function.hpp>
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<void(HTTPRequest* req, const std::string &)> 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<bool, std::string> 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<HTTPClosure> handler;
};
#endif // BITCOIN_HTTPSERVER_H

44
src/init.cpp

@ -17,6 +17,8 @@
#include "checkpoints.h"
#include "compat/sanity.h"
#include "consensus/validation.h"
#include "httpserver.h"
#include "httprpc.h"
#include "key.h"
#include "main.h"
#include "metrics.h"
@ -151,6 +153,15 @@ public:
static CCoinsViewDB *pcoinsdbview = NULL;
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
void Interrupt(boost::thread_group& threadGroup)
{
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
threadGroup.interrupt_all();
}
void Shutdown()
{
LogPrintf("%s: In progress...\n", __func__);
@ -165,7 +176,11 @@ void Shutdown()
/// module was initialized.
RenameThread("zcash-shutoff");
mempool.AddTransactionsUpdated(1);
StopRPCThreads();
StopHTTPRPC();
StopREST();
StopRPC();
StopHTTPServer();
#ifdef ENABLE_WALLET
if (pwalletMain)
pwalletMain->Flush(false);
@ -467,17 +482,10 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)"), 8232, 18232));
strUsage += HelpMessageOpt("-rpcallowip=<ip>", _("Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times"));
strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), 4));
strUsage += HelpMessageOpt("-rpckeepalive", strprintf(_("RPC support for HTTP persistent connections (default: %d)"), 1));
// Disabled until we can lock notes and also tune performance of libsnark which by default uses multiple threads
//strUsage += HelpMessageOpt("-rpcasyncthreads=<n>", strprintf(_("Set the number of threads to service Async RPC calls (default: %d)"), 1));
strUsage += HelpMessageGroup(_("RPC SSL options: (see the Bitcoin Wiki for SSL setup instructions)"));
strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcsslcertificatechainfile=<file.cert>", strprintf(_("Server certificate file (default: %s)"), "server.cert"));
strUsage += HelpMessageOpt("-rpcsslprivatekeyfile=<file.pem>", strprintf(_("Server private key (default: %s)"), "server.pem"));
strUsage += HelpMessageOpt("-rpcsslciphers=<ciphers>", strprintf(_("Acceptable ciphers (default: %s)"), "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"));
if (mode == HMM_BITCOIND) {
strUsage += HelpMessageGroup(_("Metrics Options (only if -daemon and -printtoconsole are not set):"));
strUsage += HelpMessageOpt("-showmetrics", _("Show metrics on stdout (default: 1 if running in a console, 0 otherwise)"));
@ -661,6 +669,21 @@ static void ZC_LoadParams()
pzcashParams->setProvingKeyPath(pk_path.string());
}
bool AppInitServers(boost::thread_group& threadGroup)
{
RPCServer::OnStopped(&OnRPCStopped);
RPCServer::OnPreCommand(&OnRPCPreCommand);
if (!StartHTTPServer(threadGroup))
return false;
if (!StartRPC())
return false;
if (!StartHTTPRPC())
return false;
if (GetBoolArg("-rest", false) && !StartREST())
return false;
return true;
}
/** Initialize bitcoin.
* @pre Parameters should be parsed and config file should be read.
*/
@ -1070,9 +1093,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (fServer)
{
uiInterface.InitMessage.connect(SetRPCWarmupStatus);
RPCServer::OnStopped(&OnRPCStopped);
RPCServer::OnPreCommand(&OnRPCPreCommand);
StartRPCThreads();
if (!AppInitServers(threadGroup))
return InitError(_("Unable to start HTTP server. See debug log for details."));
}
int64_t nStart;

2
src/init.h

@ -23,6 +23,8 @@ extern ZCJoinSplit* pzcashParams;
void StartShutdown();
bool ShutdownRequested();
/** Interrupt threads */
void Interrupt(boost::thread_group& threadGroup);
void Shutdown();
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler);

243
src/rest.cpp

@ -6,6 +6,7 @@
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "main.h"
#include "httpserver.h"
#include "rpcserver.h"
#include "streams.h"
#include "sync.h"
@ -55,13 +56,6 @@ struct CCoin {
}
};
class RestErr
{
public:
enum HTTPStatusCode status;
string message;
};
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false);
extern UniValue mempoolInfoToJSON();
@ -69,15 +63,14 @@ extern UniValue mempoolToJSON(bool fVerbose = false);
extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
extern UniValue blockheaderToJSON(const CBlockIndex* blockindex);
static RestErr RESTERR(enum HTTPStatusCode status, string message)
static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, string message)
{
RestErr re;
re.status = status;
re.message = message;
return re;
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(status, message + "\r\n");
return false;
}
static enum RetFormat ParseDataFormat(vector<string>& params, const string strReq)
static enum RetFormat ParseDataFormat(vector<string>& params, const string& strReq)
{
boost::split(params, strReq, boost::is_any_of("."));
if (params.size() > 1) {
@ -114,28 +107,35 @@ static bool ParseHashStr(const string& strReq, uint256& v)
return true;
}
static bool rest_headers(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool CheckWarmup(HTTPRequest* req)
{
std::string statusmessage;
if (RPCIsInWarmup(&statusmessage))
return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
return true;
}
static bool rest_headers(HTTPRequest* req,
const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
vector<string> path;
boost::split(path, params[0], boost::is_any_of("/"));
if (path.size() != 2)
throw RESTERR(HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
long count = strtol(path[0].c_str(), NULL, 10);
if (count < 1 || count > 2000)
throw RESTERR(HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
string hashStr = path[1];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
@ -159,28 +159,29 @@ static bool rest_headers(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryHeader = ssHeader.str();
conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryHeader.size(), "application/octet-stream") << binaryHeader << std::flush;
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n";
conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RF_JSON: {
UniValue jsonHeaders(UniValue::VARR);
BOOST_FOREACH(const CBlockIndex *pindex, headers) {
jsonHeaders.push_back(blockheaderToJSON(pindex));
}
string strJSON = jsonHeaders.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)");
}
}
@ -188,34 +189,33 @@ static bool rest_headers(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_block(AcceptedConnection* conn,
static bool rest_block(HTTPRequest* req,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun,
bool showTxDetails)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
string hashStr = params[0];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CBlock block;
CBlockIndex* pblockindex = NULL;
{
LOCK(cs_main);
if (mapBlockIndex.count(hash) == 0)
throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
pblockindex = mapBlockIndex[hash];
if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
throw RESTERR(HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
if (!ReadBlockFromDisk(block, pblockindex))
throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
@ -224,25 +224,28 @@ static bool rest_block(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryBlock = ssBlock.str();
conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryBlock.size(), "application/octet-stream") << binaryBlock << std::flush;
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryBlock);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RF_JSON: {
UniValue objBlock = blockToJSON(block, pblockindex, showTxDetails);
string strJSON = objBlock.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@ -250,30 +253,20 @@ static bool rest_block(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_block_extended(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_block_extended(HTTPRequest* req, const std::string& strURIPart)
{
return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, true);
return rest_block(req, strURIPart, true);
}
static bool rest_block_notxdetails(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_block_notxdetails(HTTPRequest* req, const std::string& strURIPart)
{
return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, false);
return rest_block(req, strURIPart, false);
}
static bool rest_chaininfo(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_chaininfo(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
@ -282,11 +275,12 @@ static bool rest_chaininfo(AcceptedConnection* conn,
UniValue rpcParams(UniValue::VARR);
UniValue chainInfoObject = getblockchaininfo(rpcParams, false);
string strJSON = chainInfoObject.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
}
}
@ -294,12 +288,10 @@ static bool rest_chaininfo(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_mempool_info(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_mempool_info(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
@ -308,11 +300,12 @@ static bool rest_mempool_info(AcceptedConnection* conn,
UniValue mempoolInfoObject = mempoolInfoToJSON();
string strJSON = mempoolInfoObject.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
}
}
@ -320,12 +313,10 @@ static bool rest_mempool_info(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_mempool_contents(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_mempool_contents(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
@ -334,11 +325,12 @@ static bool rest_mempool_contents(AcceptedConnection* conn,
UniValue mempoolObject = mempoolToJSON(true);
string strJSON = mempoolObject.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
}
}
@ -346,24 +338,22 @@ static bool rest_mempool_contents(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_tx(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_tx(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
const RetFormat rf = ParseDataFormat(params, strURIPart);
string hashStr = params[0];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CTransaction tx;
uint256 hashBlock = uint256();
if (!GetTransaction(hash, tx, hashBlock, true))
throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << tx;
@ -371,13 +361,15 @@ static bool rest_tx(AcceptedConnection* conn,
switch (rf) {
case RF_BINARY: {
string binaryTx = ssTx.str();
conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryTx.size(), "application/octet-stream") << binaryTx << std::flush;
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryTx);
return true;
}
case RF_HEX: {
string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";
conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
@ -385,12 +377,13 @@ static bool rest_tx(AcceptedConnection* conn,
UniValue objTx(UniValue::VOBJ);
TxToJSON(tx, hashBlock, objTx);
string strJSON = objTx.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@ -398,12 +391,10 @@ static bool rest_tx(AcceptedConnection* conn,
return true; // continue to process further HTTP reqs on this cxn
}
static bool rest_getutxos(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
vector<string> params;
enum RetFormat rf = ParseDataFormat(params, strURIPart);
@ -415,8 +406,9 @@ static bool rest_getutxos(AcceptedConnection* conn,
}
// throw exception in case of a empty request
if (strRequest.length() == 0 && uriParts.size() == 0)
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
std::string strRequestMutable = req->ReadBody();
if (strRequestMutable.length() == 0 && uriParts.size() == 0)
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
bool fInputParsed = false;
bool fCheckMemPool = false;
@ -440,7 +432,7 @@ static bool rest_getutxos(AcceptedConnection* conn,
std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1);
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error");
txid.SetHex(strTxid);
vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
@ -449,15 +441,13 @@ static bool rest_getutxos(AcceptedConnection* conn,
if (vOutPoints.size() > 0)
fInputParsed = true;
else
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
}
string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting
switch (rf) {
case RF_HEX: {
// convert hex to bin, continue then with bin part
std::vector<unsigned char> strRequestV = ParseHex(strRequest);
std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
}
@ -467,7 +457,7 @@ static bool rest_getutxos(AcceptedConnection* conn,
if (strRequestMutable.size() > 0)
{
if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
oss << strRequestMutable;
@ -476,24 +466,24 @@ static bool rest_getutxos(AcceptedConnection* conn,
}
} catch (const std::ios_base::failure& e) {
// abort in case of unreadable binary data
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error");
}
break;
}
case RF_JSON: {
if (!fInputParsed)
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
break;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
// limit max outpoints
if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
// check spentness and form a bitmap (as well as a JSON capable human-readble string representation)
vector<unsigned char> bitmap;
@ -543,7 +533,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
string ssGetUTXOResponseString = ssGetUTXOResponse.str();
conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, ssGetUTXOResponseString.size(), "application/octet-stream") << ssGetUTXOResponseString << std::flush;
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
return true;
}
@ -552,7 +543,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n";
conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush;
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
@ -582,11 +574,12 @@ static bool rest_getutxos(AcceptedConnection* conn,
// return json string
string strJSON = objGetUTXOResponse.write() + "\n";
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
@ -596,11 +589,7 @@ static bool rest_getutxos(AcceptedConnection* conn,
static const struct {
const char* prefix;
bool (*handler)(AcceptedConnection* conn,
const std::string& strURIPart,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun);
bool (*handler)(HTTPRequest* req, const std::string& strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
@ -612,29 +601,19 @@ static const struct {
{"/rest/getutxos", rest_getutxos},
};
bool HTTPReq_REST(AcceptedConnection* conn,
const std::string& strURI,
const string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun)
bool StartREST()
{
try {
std::string statusmessage;
if (RPCIsInWarmup(&statusmessage))
throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) {
unsigned int plen = strlen(uri_prefixes[i].prefix);
if (strURI.substr(0, plen) == uri_prefixes[i].prefix) {
string strURIPart = strURI.substr(plen);
return uri_prefixes[i].handler(conn, strURIPart, strRequest, mapHeaders, fRun);
}
}
} catch (const RestErr& re) {
conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush;
return false;
}
for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler);
return true;
}
conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush;
return false;
void InterruptREST()
{
}
void StopREST()
{
for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
}

229
src/rpcprotocol.cpp

@ -5,7 +5,6 @@
#include "rpcprotocol.h"
#include "clientversion.h"
#include "random.h"
#include "tinyformat.h"
#include "util.h"
@ -16,236 +15,8 @@
#include <stdint.h>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/shared_ptr.hpp>
#include <univalue.h>
using namespace std;
//! Number of bytes to allocate and read at most at once in post data
const size_t POST_READ_SIZE = 256 * 1024;
/**
* HTTP protocol
*
* This ain't Apache. We're just using HTTP header for the length field
* and to be compatible with other JSON-RPC implementations.
*/
string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders)
{
ostringstream s;
s << "POST / HTTP/1.1\r\n"
<< "User-Agent: zcash-json-rpc/" << FormatFullVersion() << "\r\n"
<< "Host: 127.0.0.1\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Connection: close\r\n"
<< "Accept: application/json\r\n";
BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders)
s << item.first << ": " << item.second << "\r\n";
s << "\r\n" << strMsg;
return s.str();
}
static string rfc1123Time()
{
return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime());
}
static const char *httpStatusDescription(int nStatus)
{
switch (nStatus) {
case HTTP_OK: return "OK";
case HTTP_BAD_REQUEST: return "Bad Request";
case HTTP_FORBIDDEN: return "Forbidden";
case HTTP_NOT_FOUND: return "Not Found";
case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error";
default: return "";
}
}
string HTTPError(int nStatus, bool keepalive, bool headersOnly)
{
if (nStatus == HTTP_UNAUTHORIZED)
return strprintf("HTTP/1.0 401 Authorization Required\r\n"
"Date: %s\r\n"
"Server: zcash-json-rpc/%s\r\n"
"WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 296\r\n"
"\r\n"
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
"\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
"<HTML>\r\n"
"<HEAD>\r\n"
"<TITLE>Error</TITLE>\r\n"
"<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n"
"</HEAD>\r\n"
"<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
"</HTML>\r\n", rfc1123Time(), FormatFullVersion());
return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive,
headersOnly, "text/plain");
}
string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType)
{
return strprintf(
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Connection: %s\r\n"
"Content-Length: %u\r\n"
"Content-Type: %s\r\n"
"Server: zcash-json-rpc/%s\r\n"
"\r\n",
nStatus,
httpStatusDescription(nStatus),
rfc1123Time(),
keepalive ? "keep-alive" : "close",
contentLength,
contentType,
FormatFullVersion());
}
string HTTPReply(int nStatus, const string& strMsg, bool keepalive,
bool headersOnly, const char *contentType)
{
if (headersOnly)
{
return HTTPReplyHeader(nStatus, keepalive, 0, contentType);
} else {
return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg;
}
}
bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
string& http_method, string& http_uri)
{
string str;
getline(stream, str);
// HTTP request line is space-delimited
vector<string> vWords;
boost::split(vWords, str, boost::is_any_of(" "));
if (vWords.size() < 2)
return false;
// HTTP methods permitted: GET, POST
http_method = vWords[0];
if (http_method != "GET" && http_method != "POST")
return false;
// HTTP URI must be an absolute path, relative to current host
http_uri = vWords[1];
if (http_uri.size() == 0 || http_uri[0] != '/')
return false;
// parse proto, if present
string strProto = "";
if (vWords.size() > 2)
strProto = vWords[2];
proto = 0;
const char *ver = strstr(strProto.c_str(), "HTTP/1.");
if (ver != NULL)
proto = atoi(ver+7);
return true;
}
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto)
{
string str;
getline(stream, str);
vector<string> vWords;
boost::split(vWords, str, boost::is_any_of(" "));
if (vWords.size() < 2)
return HTTP_INTERNAL_SERVER_ERROR;
proto = 0;
const char *ver = strstr(str.c_str(), "HTTP/1.");
if (ver != NULL)
proto = atoi(ver+7);
return atoi(vWords[1].c_str());
}
int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
{
int nLen = 0;
while (true)
{
string str;
std::getline(stream, str);
if (str.empty() || str == "\r")
break;
string::size_type nColon = str.find(":");
if (nColon != string::npos)
{
string strHeader = str.substr(0, nColon);
boost::trim(strHeader);
boost::to_lower(strHeader);
string strValue = str.substr(nColon+1);
boost::trim(strValue);
mapHeadersRet[strHeader] = strValue;
if (strHeader == "content-length")
nLen = atoi(strValue.c_str());
}
}
return nLen;
}
int ReadHTTPMessage(std::basic_istream<char>& stream, map<string,
string>& mapHeadersRet, string& strMessageRet,
int nProto, size_t max_size)
{
mapHeadersRet.clear();
strMessageRet = "";
// Read header
int nLen = ReadHTTPHeaders(stream, mapHeadersRet);
if (nLen < 0 || (size_t)nLen > max_size)
return HTTP_INTERNAL_SERVER_ERROR;
// Read message
if (nLen > 0)
{
vector<char> vch;
size_t ptr = 0;
while (ptr < (size_t)nLen)
{
size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE);
vch.resize(ptr + bytes_to_read);
stream.read(&vch[ptr], bytes_to_read);
if (!stream) // Connection lost while reading
return HTTP_INTERNAL_SERVER_ERROR;
ptr += bytes_to_read;
}
strMessageRet = string(vch.begin(), vch.end());
}
string sConHdr = mapHeadersRet["connection"];
if ((sConHdr != "close") && (sConHdr != "keep-alive"))
{
if (nProto >= 1)
mapHeadersRet["connection"] = "keep-alive";
else
mapHeadersRet["connection"] = "close";
}
return HTTP_OK;
}
/**
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were

87
src/rpcprotocol.h

@ -10,10 +10,6 @@
#include <map>
#include <stdint.h>
#include <string>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/filesystem.hpp>
#include <univalue.h>
@ -26,6 +22,7 @@ enum HTTPStatusCode
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_BAD_METHOD = 405,
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_SERVICE_UNAVAILABLE = 503,
};
@ -79,88 +76,6 @@ enum RPCErrorCode
RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked
};
/**
* IOStream device that speaks SSL but can also speak non-SSL
*/
template <typename Protocol>
class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidirectional> {
public:
SSLIOStreamDevice(boost::asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn)
{
fUseSSL = fUseSSLIn;
fNeedHandshake = fUseSSLIn;
}
void handshake(boost::asio::ssl::stream_base::handshake_type role)
{
if (!fNeedHandshake) return;
fNeedHandshake = false;
stream.handshake(role);
}
std::streamsize read(char* s, std::streamsize n)
{
handshake(boost::asio::ssl::stream_base::server); // HTTPS servers read first
if (fUseSSL) return stream.read_some(boost::asio::buffer(s, n));
return stream.next_layer().read_some(boost::asio::buffer(s, n));
}
std::streamsize write(const char* s, std::streamsize n)
{
handshake(boost::asio::ssl::stream_base::client); // HTTPS clients write first
if (fUseSSL) return boost::asio::write(stream, boost::asio::buffer(s, n));
return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n));
}
bool connect(const std::string& server, const std::string& port)
{
using namespace boost::asio::ip;
tcp::resolver resolver(stream.get_io_service());
tcp::resolver::iterator endpoint_iterator;
#if BOOST_VERSION >= 104300
try {
#endif
// The default query (flags address_configured) tries IPv6 if
// non-localhost IPv6 configured, and IPv4 if non-localhost IPv4
// configured.
tcp::resolver::query query(server.c_str(), port.c_str());
endpoint_iterator = resolver.resolve(query);
#if BOOST_VERSION >= 104300
} catch (const boost::system::system_error&) {
// If we at first don't succeed, try blanket lookup (IPv4+IPv6 independent of configured interfaces)
tcp::resolver::query query(server.c_str(), port.c_str(), resolver_query_base::flags());
endpoint_iterator = resolver.resolve(query);
}
#endif
boost::system::error_code error = boost::asio::error::host_not_found;
tcp::resolver::iterator end;
while (error && endpoint_iterator != end)
{
stream.lowest_layer().close();
stream.lowest_layer().connect(*endpoint_iterator++, error);
}
if (error)
return false;
return true;
}
private:
bool fNeedHandshake;
bool fUseSSL;
boost::asio::ssl::stream<typename Protocol::socket>& stream;
};
std::string HTTPPost(const std::string& strMsg, const std::map<std::string,std::string>& mapRequestHeaders);
std::string HTTPError(int nStatus, bool keepalive,
bool headerOnly = false);
std::string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength,
const char *contentType = "application/json");
std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive,
bool headerOnly = false,
const char *contentType = "application/json");
bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
std::string& http_method, std::string& http_uri);
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto);
int ReadHTTPHeaders(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet);
int ReadHTTPMessage(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet, int nProto, size_t max_size);
std::string JSONRPCRequest(const std::string& strMethod, const UniValue& params, const UniValue& id);
UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id);
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);

554
src/rpcserver.cpp

@ -12,16 +12,12 @@
#include "ui_interface.h"
#include "util.h"
#include "utilstrencodings.h"
#ifdef ENABLE_WALLET
#include "wallet/wallet.h"
#endif
#include "asyncrpcqueue.h"
#include <memory>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <univalue.h>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
@ -30,28 +26,20 @@
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>
#include <boost/algorithm/string/case_conv.hpp> // for to_upper()
#include <univalue.h>
using namespace boost::asio;
using namespace RPCServer;
using namespace std;
static std::string strRPCUserColonPass;
static bool fRPCRunning = false;
static bool fRPCInWarmup = true;
static std::string rpcWarmupStatus("RPC server started");
static CCriticalSection cs_rpcWarmup;
//! These are created by StartRPCThreads, destroyed in StopRPCThreads
static boost::asio::io_service* rpc_io_service = NULL;
static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
static ssl::context* rpc_ssl_context = NULL;
static boost::thread_group* rpc_worker_group = NULL;
static boost::asio::io_service::work *rpc_dummy_work = NULL;
static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from
static std::vector< boost::shared_ptr<ip::tcp::acceptor> > rpc_acceptors;
/* Timer-creating functions */
static std::vector<RPCTimerInterface*> timerInterfaces;
/* Map of name to timer.
* @note Can be changed to std::unique_ptr when C++11 */
static std::map<std::string, boost::shared_ptr<RPCTimerBase> > deadlineTimers;
static struct CRPCSignals
{
@ -172,7 +160,6 @@ vector<unsigned char> ParseHexO(const UniValue& o, string strKey)
return ParseHexV(find_value(o, strKey), strKey);
}
/**
* Note: This interface may still be subject to change.
*/
@ -264,8 +251,6 @@ UniValue stop(const UniValue& params, bool fHelp)
return "Zcash server stopping";
}
/**
* Call Table
*/
@ -427,7 +412,7 @@ CRPCTable::CRPCTable()
}
}
const CRPCCommand *CRPCTable::operator[](const std::string& name) const
const CRPCCommand *CRPCTable::operator[](const std::string &name) const
{
map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name);
if (it == mapCommands.end())
@ -435,320 +420,9 @@ const CRPCCommand *CRPCTable::operator[](const std::string& name) const
return (*it).second;
}
bool HTTPAuthorized(map<string, string>& mapHeaders)
{
string strAuth = mapHeaders["authorization"];
if (strAuth.substr(0,6) != "Basic ")
return false;
string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64);
string strUserPass = DecodeBase64(strUserPass64);
return TimingResistantEqual(strUserPass, strRPCUserColonPass);
}
void ErrorReply(std::ostream& stream, const UniValue& objError, const UniValue& id)
{
// Send error reply from json-rpc error object
int nStatus = HTTP_INTERNAL_SERVER_ERROR;
int code = find_value(objError, "code").get_int();
if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST;
else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND;
string strReply = JSONRPCReply(NullUniValue, objError, id);
stream << HTTPReply(nStatus, strReply, false) << std::flush;
}
CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address)
bool StartRPC()
{
CNetAddr netaddr;
// Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses
if (address.is_v6()
&& (address.to_v6().is_v4_compatible()
|| address.to_v6().is_v4_mapped()))
address = address.to_v6().to_v4();
if(address.is_v4())
{
boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes();
netaddr.SetRaw(NET_IPV4, &bytes[0]);
}
else
{
boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes();
netaddr.SetRaw(NET_IPV6, &bytes[0]);
}
return netaddr;
}
bool ClientAllowed(const boost::asio::ip::address& address)
{
CNetAddr netaddr = BoostAsioToCNetAddr(address);
BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
if (subnet.Match(netaddr))
return true;
return false;
}
template <typename Protocol>
class AcceptedConnectionImpl : public AcceptedConnection
{
public:
AcceptedConnectionImpl(
boost::asio::io_service& io_service,
ssl::context &context,
bool fUseSSL) :
sslStream(io_service, context),
_d(sslStream, fUseSSL),
_stream(_d)
{
}
virtual std::iostream& stream()
{
return _stream;
}
virtual std::string peer_address_to_string() const
{
return peer.address().to_string();
}
virtual void close()
{
_stream.close();
}
typename Protocol::endpoint peer;
boost::asio::ssl::stream<typename Protocol::socket> sslStream;
private:
SSLIOStreamDevice<Protocol> _d;
boost::iostreams::stream< SSLIOStreamDevice<Protocol> > _stream;
};
void ServiceConnection(AcceptedConnection *conn);
//! Forward declaration required for RPCListen
template <typename Protocol, typename SocketAcceptorService>
static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
ssl::context& context,
bool fUseSSL,
boost::shared_ptr< AcceptedConnection > conn,
const boost::system::error_code& error);
/**
* Sets up I/O resources to accept and handle a new connection.
*/
template <typename Protocol, typename SocketAcceptorService>
static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
ssl::context& context,
const bool fUseSSL)
{
// Accept connection
boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL));
acceptor->async_accept(
conn->sslStream.lowest_layer(),
conn->peer,
boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>,
acceptor,
boost::ref(context),
fUseSSL,
conn,
_1));
}
/**
* Accept and handle incoming connection.
*/
template <typename Protocol, typename SocketAcceptorService>
static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
ssl::context& context,
const bool fUseSSL,
boost::shared_ptr< AcceptedConnection > conn,
const boost::system::error_code& error)
{
// Immediately start accepting new connections, except when we're cancelled or our socket is closed.
if (error != boost::asio::error::operation_aborted && acceptor->is_open())
RPCListen(acceptor, context, fUseSSL);
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get());
if (error)
{
// TODO: Actually handle errors
LogPrintf("%s: Error: %s\n", __func__, error.message());
}
// Restrict callers by IP. It is important to
// do this before starting client thread, to filter out
// certain DoS and misbehaving clients.
else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address()))
{
// Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
if (!fUseSSL)
conn->stream() << HTTPError(HTTP_FORBIDDEN, false) << std::flush;
conn->close();
}
else {
ServiceConnection(conn.get());
conn->close();
}
}
static ip::tcp::endpoint ParseEndpoint(const std::string &strEndpoint, int defaultPort)
{
std::string addr;
int port = defaultPort;
SplitHostPort(strEndpoint, port, addr);
return ip::tcp::endpoint(boost::asio::ip::address::from_string(addr), port);
}
void StartRPCThreads()
{
rpc_allow_subnets.clear();
rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet
rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost
if (mapMultiArgs.count("-rpcallowip"))
{
const vector<string>& vAllow = mapMultiArgs["-rpcallowip"];
BOOST_FOREACH(string strAllow, vAllow)
{
CSubNet subnet(strAllow);
if(!subnet.IsValid())
{
uiInterface.ThreadSafeMessageBox(
strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow),
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
rpc_allow_subnets.push_back(subnet);
}
}
std::string strAllowed;
BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
strAllowed += subnet.ToString() + " ";
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
if (mapArgs["-rpcpassword"] == "")
{
LogPrintf("No rpcpassword set - using random cookie authentication\n");
if (!GenerateAuthCookie(&strRPCUserColonPass)) {
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
} else {
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
assert(rpc_io_service == NULL);
rpc_io_service = new boost::asio::io_service();
rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23);
const bool fUseSSL = GetBoolArg("-rpcssl", false);
if (fUseSSL)
{
rpc_ssl_context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3);
boost::filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert"));
if (!pathCertFile.is_complete()) pathCertFile = boost::filesystem::path(GetDataDir()) / pathCertFile;
if (boost::filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string());
else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string());
boost::filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem"));
if (!pathPKFile.is_complete()) pathPKFile = boost::filesystem::path(GetDataDir()) / pathPKFile;
if (boost::filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem);
else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string());
string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH");
SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str());
}
std::vector<ip::tcp::endpoint> vEndpoints;
bool bBindAny = false;
int defaultPort = GetArg("-rpcport", BaseParams().RPCPort());
if (!mapArgs.count("-rpcallowip")) // Default to loopback if not allowing external IPs
{
vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::loopback(), defaultPort));
vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), defaultPort));
if (mapArgs.count("-rpcbind"))
{
LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
}
} else if (mapArgs.count("-rpcbind")) // Specific bind address
{
BOOST_FOREACH(const std::string &addr, mapMultiArgs["-rpcbind"])
{
try {
vEndpoints.push_back(ParseEndpoint(addr, defaultPort));
}
catch (const boost::system::system_error&)
{
uiInterface.ThreadSafeMessageBox(
strprintf(_("Could not parse -rpcbind value %s as network address"), addr),
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
}
} else { // No specific bind address specified, bind to any
vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::any(), defaultPort));
vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::any(), defaultPort));
// Prefer making the socket dual IPv6/IPv4 instead of binding
// to both addresses separately.
bBindAny = true;
}
bool fListening = false;
std::string strerr;
std::string straddress;
BOOST_FOREACH(const ip::tcp::endpoint &endpoint, vEndpoints)
{
try {
boost::asio::ip::address bindAddress = endpoint.address();
straddress = bindAddress.to_string();
LogPrintf("Binding RPC on address %s port %i (IPv4+IPv6 bind any: %i)\n", straddress, endpoint.port(), bBindAny);
boost::system::error_code v6_only_error;
boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service));
acceptor->open(endpoint.protocol());
acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
// Try making the socket dual IPv6/IPv4 when listening on the IPv6 "any" address
acceptor->set_option(boost::asio::ip::v6_only(
!bBindAny || bindAddress != boost::asio::ip::address_v6::any()), v6_only_error);
acceptor->bind(endpoint);
acceptor->listen(socket_base::max_connections);
RPCListen(acceptor, *rpc_ssl_context, fUseSSL);
fListening = true;
rpc_acceptors.push_back(acceptor);
// If dual IPv6/IPv4 bind successful, skip binding to IPv4 separately
if(bBindAny && bindAddress == boost::asio::ip::address_v6::any() && !v6_only_error)
break;
}
catch (const boost::system::system_error& e)
{
LogPrintf("ERROR: Binding RPC on address %s port %i failed: %s\n", straddress, endpoint.port(), e.what());
strerr = strprintf(_("An error occurred while setting up the RPC address %s port %u for listening: %s"), straddress, endpoint.port(), e.what());
}
}
if (!fListening) {
uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
rpc_worker_group = new boost::thread_group();
for (int i = 0; i < GetArg("-rpcthreads", 4); i++)
rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service));
LogPrint("rpc", "Starting RPC\n");
fRPCRunning = true;
g_rpcSignals.Started();
@ -766,57 +440,21 @@ void StartRPCThreads()
for (int i = 0; i < n; i++)
getAsyncRPCQueue()->addWorker();
*/
return true;
}
void StartDummyRPCThread()
void InterruptRPC()
{
if(rpc_io_service == NULL)
{
rpc_io_service = new boost::asio::io_service();
/* Create dummy "work" to keep the thread from exiting when no timeouts active,
* see http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work */
rpc_dummy_work = new boost::asio::io_service::work(*rpc_io_service);
rpc_worker_group = new boost::thread_group();
rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service));
fRPCRunning = true;
}
LogPrint("rpc", "Interrupting RPC\n");
// Interrupt e.g. running longpolls
fRPCRunning = false;
}
void StopRPCThreads()
void StopRPC()
{
if (rpc_io_service == NULL) return;
// Set this to false first, so that longpolling loops will exit when woken up
fRPCRunning = false;
// First, cancel all timers and acceptors
// This is not done automatically by ->stop(), and in some cases the destructor of
// boost::asio::io_service can hang if this is skipped.
boost::system::error_code ec;
BOOST_FOREACH(const boost::shared_ptr<ip::tcp::acceptor> &acceptor, rpc_acceptors)
{
acceptor->cancel(ec);
if (ec)
LogPrintf("%s: Warning: %s when cancelling acceptor\n", __func__, ec.message());
}
rpc_acceptors.clear();
BOOST_FOREACH(const PAIRTYPE(std::string, boost::shared_ptr<deadline_timer>) &timer, deadlineTimers)
{
timer.second->cancel(ec);
if (ec)
LogPrintf("%s: Warning: %s when cancelling timer\n", __func__, ec.message());
}
LogPrint("rpc", "Stopping RPC\n");
deadlineTimers.clear();
DeleteAuthCookie();
rpc_io_service->stop();
g_rpcSignals.Stopped();
if (rpc_worker_group != NULL)
rpc_worker_group->join_all();
delete rpc_dummy_work; rpc_dummy_work = NULL;
delete rpc_worker_group; rpc_worker_group = NULL;
delete rpc_ssl_context; rpc_ssl_context = NULL;
delete rpc_io_service; rpc_io_service = NULL;
// Tells async queue to cancel all operations and shutdown.
LogPrintf("%s: waiting for async rpc workers to stop\n", __func__);
@ -849,36 +487,6 @@ bool RPCIsInWarmup(std::string *outStatus)
return fRPCInWarmup;
}
void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func)
{
if (!err)
func();
}
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds)
{
assert(rpc_io_service != NULL);
if (deadlineTimers.count(name) == 0)
{
deadlineTimers.insert(make_pair(name,
boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service))));
}
deadlineTimers[name]->expires_from_now(boost::posix_time::seconds(nSeconds));
deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func));
}
class JSONRequest
{
public:
UniValue id;
string strMethod;
UniValue params;
JSONRequest() { id = NullUniValue; }
void parse(const UniValue& valRequest);
};
void JSONRequest::parse(const UniValue& valRequest)
{
// Parse request
@ -909,7 +517,6 @@ void JSONRequest::parse(const UniValue& valRequest)
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
}
static UniValue JSONRPCExecOne(const UniValue& req)
{
UniValue rpc_result(UniValue::VOBJ);
@ -934,7 +541,7 @@ static UniValue JSONRPCExecOne(const UniValue& req)
return rpc_result;
}
static string JSONRPCExecBatch(const UniValue& vReq)
std::string JSONRPCExecBatch(const UniValue& vReq)
{
UniValue ret(UniValue::VARR);
for (size_t reqIdx = 0; reqIdx < vReq.size(); reqIdx++)
@ -943,107 +550,6 @@ static string JSONRPCExecBatch(const UniValue& vReq)
return ret.write() + "\n";
}
static bool HTTPReq_JSONRPC(AcceptedConnection *conn,
string& strRequest,
map<string, string>& mapHeaders,
bool fRun)
{
// Check authorization
if (mapHeaders.count("authorization") == 0)
{
conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush;
return false;
}
if (!HTTPAuthorized(mapHeaders))
{
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string());
/* Deter brute-forcing
We don't support exposing the RPC port, so this shouldn't result
in a DoS. */
MilliSleep(250);
conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush;
return false;
}
JSONRequest jreq;
try
{
// Parse request
UniValue valRequest;
if (!valRequest.read(strRequest))
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
string strReply;
// singleton request
if (valRequest.isObject()) {
jreq.parse(valRequest);
UniValue result = tableRPC.execute(jreq.strMethod, jreq.params);
// Send reply
strReply = JSONRPCReply(result, NullUniValue, jreq.id);
// array of requests
} else if (valRequest.isArray())
strReply = JSONRPCExecBatch(valRequest.get_array());
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, strReply.size()) << strReply << std::flush;
}
catch (const UniValue& objError)
{
ErrorReply(conn->stream(), objError, jreq.id);
return false;
}
catch (const std::exception& e)
{
ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
return false;
}
return true;
}
void ServiceConnection(AcceptedConnection *conn)
{
bool fRun = true;
while (fRun && !ShutdownRequested())
{
int nProto = 0;
map<string, string> mapHeaders;
string strRequest, strMethod, strURI;
// Read HTTP request line
if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI))
break;
// Read HTTP message headers and body
ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto, MAX_SIZE);
// HTTP Keep-Alive is false; close connection immediately
if ((mapHeaders["connection"] == "close") || (!GetBoolArg("-rpckeepalive", true)))
fRun = false;
// Process via JSON-RPC API
if (strURI == "/") {
if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun))
break;
// Process via HTTP REST API
} else if (strURI.substr(0, 6) == "/rest/" && GetBoolArg("-rest", false)) {
if (!HTTPReq_REST(conn, strURI, strRequest, mapHeaders, fRun))
break;
} else {
conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush;
break;
}
}
}
UniValue CRPCTable::execute(const std::string &strMethod, const UniValue &params) const
{
// Return immediately if in warmup
@ -1084,6 +590,28 @@ std::string HelpExampleRpc(const std::string& methodname, const std::string& arg
"\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8232/\n";
}
void RPCRegisterTimerInterface(RPCTimerInterface *iface)
{
timerInterfaces.push_back(iface);
}
void RPCUnregisterTimerInterface(RPCTimerInterface *iface)
{
std::vector<RPCTimerInterface*>::iterator i = std::find(timerInterfaces.begin(), timerInterfaces.end(), iface);
assert(i != timerInterfaces.end());
timerInterfaces.erase(i);
}
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds)
{
if (timerInterfaces.empty())
throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
deadlineTimers.erase(name);
RPCTimerInterface* timerInterface = timerInterfaces[0];
LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
deadlineTimers.insert(std::make_pair(name, timerInterface->NewTimer(func, nSeconds)));
}
const CRPCTable tableRPC;
// Return async rpc queue

73
src/rpcserver.h

@ -34,26 +34,17 @@ namespace RPCServer
class CBlockIndex;
class CNetAddr;
class AcceptedConnection
class JSONRequest
{
public:
virtual ~AcceptedConnection() {}
UniValue id;
std::string strMethod;
UniValue params;
virtual std::iostream& stream() = 0;
virtual std::string peer_address_to_string() const = 0;
virtual void close() = 0;
JSONRequest() { id = NullUniValue; }
void parse(const UniValue& valRequest);
};
/** Start RPC threads */
void StartRPCThreads();
/**
* Alternative to StartRPCThreads for the GUI, when no server is
* used. The RPC thread in this case is only used to handle timeouts.
* If real RPC threads have already been started this is a no-op.
*/
void StartDummyRPCThread();
/** Stop RPC threads */
void StopRPCThreads();
/** Query whether RPC is running */
bool IsRPCRunning();
@ -87,15 +78,45 @@ void RPCTypeCheck(const UniValue& params,
void RPCTypeCheckObj(const UniValue& o,
const std::map<std::string, UniValue::VType>& typesExpected, bool fAllowNull=false);
/** Opaque base class for timers returned by NewTimerFunc.
* This provides no methods at the moment, but makes sure that delete
* cleans up the whole state.
*/
class RPCTimerBase
{
public:
virtual ~RPCTimerBase() {}
};
/**
* RPC timer "driver".
*/
class RPCTimerInterface
{
public:
virtual ~RPCTimerInterface() {}
/** Implementation name */
virtual const char *Name() = 0;
/** Factory function for timers.
* RPC will call the function to create a timer that will call func in *seconds* seconds.
* @note As the RPC mechanism is backend-neutral, it can use different implementations of timers.
* This is needed to cope with the case in which there is no HTTP server, but
* only GUI RPC console, and to break the dependency of pcserver on httprpc.
*/
virtual RPCTimerBase* NewTimer(boost::function<void(void)>&, int64_t) = 0;
};
/** Register factory function for timers */
void RPCRegisterTimerInterface(RPCTimerInterface *iface);
/** Unregister factory function for timers */
void RPCUnregisterTimerInterface(RPCTimerInterface *iface);
/**
* Run func nSeconds from now. Uses boost deadline timers.
* Run func nSeconds from now.
* Overrides previous timer <name> (if any).
*/
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds);
//! Convert boost::asio address to CNetAddr
extern CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address);
typedef UniValue(*rpcfn_type)(const UniValue& params, bool fHelp);
class CRPCCommand
@ -140,9 +161,6 @@ extern uint256 ParseHashO(const UniValue& o, std::string strKey);
extern std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName);
extern std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
extern void InitRPCMining();
extern void ShutdownRPCMining();
extern int64_t nWalletUnlockTime;
extern CAmount AmountFromValue(const UniValue& value);
extern UniValue ValueFromAmount(const CAmount& amount);
@ -274,12 +292,9 @@ extern UniValue z_getoperationresult(const UniValue& params, bool fHelp); // in
extern UniValue z_listoperationids(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_validateaddress(const UniValue& params, bool fHelp); // in rpcmisc.cpp
// in rest.cpp
extern bool HTTPReq_REST(AcceptedConnection *conn,
const std::string& strURI,
const std::string& strRequest,
const std::map<std::string, std::string>& mapHeaders,
bool fRun);
bool StartRPC();
void InterruptRPC();
void StopRPC();
std::string JSONRPCExecBatch(const UniValue& vReq);
#endif // BITCOIN_RPCSERVER_H

Loading…
Cancel
Save