Browse Source
Split bitcoinrpc up into - rpcserver: bitcoind RPC server - rpcclient: bitcoin-cli RPC client - rpcprotocol: shared common HTTP/JSON-RPC protocol code One step towards making bitcoin-cli independent from the rest of the code, and thus a smaller executable that doesn't have to be linked against leveldb. This commit only does code movement, there are no functional changes.0.10
![laanwj@gmail.com](/assets/img/avatar_default.png)
18 changed files with 692 additions and 629 deletions
@ -0,0 +1,246 @@ |
|||||||
|
// Copyright (c) 2010 Satoshi Nakamoto
|
||||||
|
// Copyright (c) 2009-2013 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "rpcclient.h" |
||||||
|
|
||||||
|
#include "rpcprotocol.h" |
||||||
|
#include "util.h" |
||||||
|
#include "ui_interface.h" |
||||||
|
#include "chainparams.h" // for Params().RPCPort() |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#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/lexical_cast.hpp> |
||||||
|
#include <boost/shared_ptr.hpp> |
||||||
|
#include "json/json_spirit_writer_template.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace boost; |
||||||
|
using namespace boost::asio; |
||||||
|
using namespace json_spirit; |
||||||
|
|
||||||
|
Object CallRPC(const string& strMethod, const Array& params) |
||||||
|
{ |
||||||
|
if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") |
||||||
|
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."), |
||||||
|
GetConfigFile().string().c_str())); |
||||||
|
|
||||||
|
// Connect to localhost
|
||||||
|
bool fUseSSL = GetBoolArg("-rpcssl", false); |
||||||
|
asio::io_service io_service; |
||||||
|
ssl::context context(io_service, ssl::context::sslv23); |
||||||
|
context.set_options(ssl::context::no_sslv2); |
||||||
|
asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context); |
||||||
|
SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL); |
||||||
|
iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d); |
||||||
|
|
||||||
|
bool fWait = GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started
|
||||||
|
do { |
||||||
|
bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(Params().RPCPort()))); |
||||||
|
if (fConnected) break; |
||||||
|
if (fWait) |
||||||
|
MilliSleep(1000); |
||||||
|
else |
||||||
|
throw runtime_error("couldn't connect to server"); |
||||||
|
} while (fWait); |
||||||
|
|
||||||
|
// HTTP basic authentication
|
||||||
|
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); |
||||||
|
map<string, string> mapRequestHeaders; |
||||||
|
mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; |
||||||
|
|
||||||
|
// 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); |
||||||
|
|
||||||
|
// Receive HTTP reply message headers and body
|
||||||
|
map<string, string> mapHeaders; |
||||||
|
string strReply; |
||||||
|
ReadHTTPMessage(stream, mapHeaders, strReply, nProto); |
||||||
|
|
||||||
|
if (nStatus == 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()) |
||||||
|
throw runtime_error("no response from server"); |
||||||
|
|
||||||
|
// Parse reply
|
||||||
|
Value valReply; |
||||||
|
if (!read_string(strReply, valReply)) |
||||||
|
throw runtime_error("couldn't parse reply from server"); |
||||||
|
const Object& reply = valReply.get_obj(); |
||||||
|
if (reply.empty()) |
||||||
|
throw runtime_error("expected reply to have result, error and id properties"); |
||||||
|
|
||||||
|
return reply; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
void ConvertTo(Value& value, bool fAllowNull=false) |
||||||
|
{ |
||||||
|
if (fAllowNull && value.type() == null_type) |
||||||
|
return; |
||||||
|
if (value.type() == str_type) |
||||||
|
{ |
||||||
|
// reinterpret string as unquoted json value
|
||||||
|
Value value2; |
||||||
|
string strJSON = value.get_str(); |
||||||
|
if (!read_string(strJSON, value2)) |
||||||
|
throw runtime_error(string("Error parsing JSON:")+strJSON); |
||||||
|
ConvertTo<T>(value2, fAllowNull); |
||||||
|
value = value2; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
value = value.get_value<T>(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Convert strings to command-specific RPC representation
|
||||||
|
Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) |
||||||
|
{ |
||||||
|
Array params; |
||||||
|
BOOST_FOREACH(const std::string ¶m, strParams) |
||||||
|
params.push_back(param); |
||||||
|
|
||||||
|
int n = params.size(); |
||||||
|
|
||||||
|
//
|
||||||
|
// Special case non-string parameter types
|
||||||
|
//
|
||||||
|
if (strMethod == "stop" && n > 0) ConvertTo<bool>(params[0]); |
||||||
|
if (strMethod == "getaddednodeinfo" && n > 0) ConvertTo<bool>(params[0]); |
||||||
|
if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); |
||||||
|
if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "getnetworkhashps" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "getnetworkhashps" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); |
||||||
|
if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); |
||||||
|
if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); |
||||||
|
if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); |
||||||
|
if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); |
||||||
|
if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); |
||||||
|
if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); |
||||||
|
if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); |
||||||
|
if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); |
||||||
|
if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]); |
||||||
|
if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]); |
||||||
|
if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); |
||||||
|
if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]); |
||||||
|
if (strMethod == "createmultisig" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "createmultisig" && n > 1) ConvertTo<Array>(params[1]); |
||||||
|
if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "listunspent" && n > 2) ConvertTo<Array>(params[2]); |
||||||
|
if (strMethod == "getblock" && n > 1) ConvertTo<bool>(params[1]); |
||||||
|
if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]); |
||||||
|
if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]); |
||||||
|
if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true); |
||||||
|
if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true); |
||||||
|
if (strMethod == "sendrawtransaction" && n > 1) ConvertTo<bool>(params[1], true); |
||||||
|
if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]); |
||||||
|
if (strMethod == "lockunspent" && n > 0) ConvertTo<bool>(params[0]); |
||||||
|
if (strMethod == "lockunspent" && n > 1) ConvertTo<Array>(params[1]); |
||||||
|
if (strMethod == "importprivkey" && n > 2) ConvertTo<bool>(params[2]); |
||||||
|
if (strMethod == "verifychain" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
if (strMethod == "verifychain" && n > 1) ConvertTo<boost::int64_t>(params[1]); |
||||||
|
if (strMethod == "keypoolrefill" && n > 0) ConvertTo<boost::int64_t>(params[0]); |
||||||
|
|
||||||
|
return params; |
||||||
|
} |
||||||
|
|
||||||
|
int CommandLineRPC(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
string strPrint; |
||||||
|
int nRet = 0; |
||||||
|
try |
||||||
|
{ |
||||||
|
// Skip switches
|
||||||
|
while (argc > 1 && IsSwitchChar(argv[1][0])) |
||||||
|
{ |
||||||
|
argc--; |
||||||
|
argv++; |
||||||
|
} |
||||||
|
|
||||||
|
// Method
|
||||||
|
if (argc < 2) |
||||||
|
throw runtime_error("too few parameters"); |
||||||
|
string strMethod = argv[1]; |
||||||
|
|
||||||
|
// Parameters default to strings
|
||||||
|
std::vector<std::string> strParams(&argv[2], &argv[argc]); |
||||||
|
Array params = RPCConvertValues(strMethod, strParams); |
||||||
|
|
||||||
|
// Execute
|
||||||
|
Object reply = CallRPC(strMethod, params); |
||||||
|
|
||||||
|
// Parse reply
|
||||||
|
const Value& result = find_value(reply, "result"); |
||||||
|
const Value& error = find_value(reply, "error"); |
||||||
|
|
||||||
|
if (error.type() != null_type) |
||||||
|
{ |
||||||
|
// Error
|
||||||
|
strPrint = "error: " + write_string(error, false); |
||||||
|
int code = find_value(error.get_obj(), "code").get_int(); |
||||||
|
nRet = abs(code); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// Result
|
||||||
|
if (result.type() == null_type) |
||||||
|
strPrint = ""; |
||||||
|
else if (result.type() == str_type) |
||||||
|
strPrint = result.get_str(); |
||||||
|
else |
||||||
|
strPrint = write_string(result, true); |
||||||
|
} |
||||||
|
} |
||||||
|
catch (boost::thread_interrupted) { |
||||||
|
throw; |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
strPrint = string("error: ") + e.what(); |
||||||
|
nRet = 87; |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
PrintException(NULL, "CommandLineRPC()"); |
||||||
|
} |
||||||
|
|
||||||
|
if (strPrint != "") |
||||||
|
{ |
||||||
|
fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
||||||
|
} |
||||||
|
return nRet; |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
// Copyright (c) 2010 Satoshi Nakamoto
|
||||||
|
// Copyright (c) 2009-2013 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef _BITCOINRPC_CLIENT_H_ |
||||||
|
#define _BITCOINRPC_CLIENT_H_ 1 |
||||||
|
|
||||||
|
#include "json/json_spirit_reader_template.h" |
||||||
|
#include "json/json_spirit_utils.h" |
||||||
|
#include "json/json_spirit_writer_template.h" |
||||||
|
|
||||||
|
int CommandLineRPC(int argc, char *argv[]); |
||||||
|
|
||||||
|
json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams); |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,262 @@ |
|||||||
|
// Copyright (c) 2010 Satoshi Nakamoto
|
||||||
|
// Copyright (c) 2009-2013 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "rpcprotocol.h" |
||||||
|
|
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#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/lexical_cast.hpp> |
||||||
|
#include <boost/shared_ptr.hpp> |
||||||
|
#include "json/json_spirit_writer_template.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace boost; |
||||||
|
using namespace boost::asio; |
||||||
|
using namespace json_spirit; |
||||||
|
|
||||||
|
//
|
||||||
|
// 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: bitcoin-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() |
||||||
|
{ |
||||||
|
char buffer[64]; |
||||||
|
time_t now; |
||||||
|
time(&now); |
||||||
|
struct tm* now_gmt = gmtime(&now); |
||||||
|
string locale(setlocale(LC_TIME, NULL)); |
||||||
|
setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings
|
||||||
|
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); |
||||||
|
setlocale(LC_TIME, locale.c_str()); |
||||||
|
return string(buffer); |
||||||
|
} |
||||||
|
|
||||||
|
string HTTPReply(int nStatus, const string& strMsg, bool keepalive) |
||||||
|
{ |
||||||
|
if (nStatus == HTTP_UNAUTHORIZED) |
||||||
|
return strprintf("HTTP/1.0 401 Authorization Required\r\n" |
||||||
|
"Date: %s\r\n" |
||||||
|
"Server: bitcoin-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().c_str(), FormatFullVersion().c_str()); |
||||||
|
const char *cStatus; |
||||||
|
if (nStatus == HTTP_OK) cStatus = "OK"; |
||||||
|
else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; |
||||||
|
else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; |
||||||
|
else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; |
||||||
|
else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; |
||||||
|
else cStatus = ""; |
||||||
|
return strprintf( |
||||||
|
"HTTP/1.1 %d %s\r\n" |
||||||
|
"Date: %s\r\n" |
||||||
|
"Connection: %s\r\n" |
||||||
|
"Content-Length: %"PRIszu"\r\n" |
||||||
|
"Content-Type: application/json\r\n" |
||||||
|
"Server: bitcoin-json-rpc/%s\r\n" |
||||||
|
"\r\n" |
||||||
|
"%s", |
||||||
|
nStatus, |
||||||
|
cStatus, |
||||||
|
rfc1123Time().c_str(), |
||||||
|
keepalive ? "keep-alive" : "close", |
||||||
|
strMsg.size(), |
||||||
|
FormatFullVersion().c_str(), |
||||||
|
strMsg.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
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) |
||||||
|
{ |
||||||
|
mapHeadersRet.clear(); |
||||||
|
strMessageRet = ""; |
||||||
|
|
||||||
|
// Read header
|
||||||
|
int nLen = ReadHTTPHeaders(stream, mapHeadersRet); |
||||||
|
if (nLen < 0 || nLen > (int)MAX_SIZE) |
||||||
|
return HTTP_INTERNAL_SERVER_ERROR; |
||||||
|
|
||||||
|
// Read message
|
||||||
|
if (nLen > 0) |
||||||
|
{ |
||||||
|
vector<char> vch(nLen); |
||||||
|
stream.read(&vch[0], nLen); |
||||||
|
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
|
||||||
|
// unspecified (HTTP errors and contents of 'error').
|
||||||
|
//
|
||||||
|
// 1.0 spec: http://json-rpc.org/wiki/specification
|
||||||
|
// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
|
||||||
|
// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx
|
||||||
|
//
|
||||||
|
|
||||||
|
string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id) |
||||||
|
{ |
||||||
|
Object request; |
||||||
|
request.push_back(Pair("method", strMethod)); |
||||||
|
request.push_back(Pair("params", params)); |
||||||
|
request.push_back(Pair("id", id)); |
||||||
|
return write_string(Value(request), false) + "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) |
||||||
|
{ |
||||||
|
Object reply; |
||||||
|
if (error.type() != null_type) |
||||||
|
reply.push_back(Pair("result", Value::null)); |
||||||
|
else |
||||||
|
reply.push_back(Pair("result", result)); |
||||||
|
reply.push_back(Pair("error", error)); |
||||||
|
reply.push_back(Pair("id", id)); |
||||||
|
return reply; |
||||||
|
} |
||||||
|
|
||||||
|
string JSONRPCReply(const Value& result, const Value& error, const Value& id) |
||||||
|
{ |
||||||
|
Object reply = JSONRPCReplyObj(result, error, id); |
||||||
|
return write_string(Value(reply), false) + "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
Object JSONRPCError(int code, const string& message) |
||||||
|
{ |
||||||
|
Object error; |
||||||
|
error.push_back(Pair("code", code)); |
||||||
|
error.push_back(Pair("message", message)); |
||||||
|
return error; |
||||||
|
} |
@ -0,0 +1,138 @@ |
|||||||
|
// Copyright (c) 2010 Satoshi Nakamoto
|
||||||
|
// Copyright (c) 2009-2013 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef _BITCOINRPC_PROTOCOL_H_ |
||||||
|
#define _BITCOINRPC_PROTOCOL_H_ 1 |
||||||
|
|
||||||
|
#include <list> |
||||||
|
#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 "json/json_spirit_reader_template.h" |
||||||
|
#include "json/json_spirit_utils.h" |
||||||
|
#include "json/json_spirit_writer_template.h" |
||||||
|
|
||||||
|
// HTTP status codes
|
||||||
|
enum HTTPStatusCode |
||||||
|
{ |
||||||
|
HTTP_OK = 200, |
||||||
|
HTTP_BAD_REQUEST = 400, |
||||||
|
HTTP_UNAUTHORIZED = 401, |
||||||
|
HTTP_FORBIDDEN = 403, |
||||||
|
HTTP_NOT_FOUND = 404, |
||||||
|
HTTP_INTERNAL_SERVER_ERROR = 500, |
||||||
|
}; |
||||||
|
|
||||||
|
// Bitcoin RPC error codes
|
||||||
|
enum RPCErrorCode |
||||||
|
{ |
||||||
|
// Standard JSON-RPC 2.0 errors
|
||||||
|
RPC_INVALID_REQUEST = -32600, |
||||||
|
RPC_METHOD_NOT_FOUND = -32601, |
||||||
|
RPC_INVALID_PARAMS = -32602, |
||||||
|
RPC_INTERNAL_ERROR = -32603, |
||||||
|
RPC_PARSE_ERROR = -32700, |
||||||
|
|
||||||
|
// General application defined errors
|
||||||
|
RPC_MISC_ERROR = -1, // std::exception thrown in command handling
|
||||||
|
RPC_FORBIDDEN_BY_SAFE_MODE = -2, // Server is in safe mode, and command is not allowed in safe mode
|
||||||
|
RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter
|
||||||
|
RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key
|
||||||
|
RPC_OUT_OF_MEMORY = -7, // Ran out of memory during operation
|
||||||
|
RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter
|
||||||
|
RPC_DATABASE_ERROR = -20, // Database error
|
||||||
|
RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format
|
||||||
|
RPC_SERVER_NOT_STARTED = -18, // RPC server was not started (StartRPCThreads() not called)
|
||||||
|
|
||||||
|
// P2P client errors
|
||||||
|
RPC_CLIENT_NOT_CONNECTED = -9, // Bitcoin is not connected
|
||||||
|
RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, // Still downloading initial blocks
|
||||||
|
RPC_CLIENT_NODE_ALREADY_ADDED = -23, // Node is already added
|
||||||
|
RPC_CLIENT_NODE_NOT_ADDED = -24, // Node has not been added before
|
||||||
|
|
||||||
|
// Wallet errors
|
||||||
|
RPC_WALLET_ERROR = -4, // Unspecified problem with wallet (key not found etc.)
|
||||||
|
RPC_WALLET_INSUFFICIENT_FUNDS = -6, // Not enough funds in wallet or account
|
||||||
|
RPC_WALLET_INVALID_ACCOUNT_NAME = -11, // Invalid account name
|
||||||
|
RPC_WALLET_KEYPOOL_RAN_OUT = -12, // Keypool ran out, call keypoolrefill first
|
||||||
|
RPC_WALLET_UNLOCK_NEEDED = -13, // Enter the wallet passphrase with walletpassphrase first
|
||||||
|
RPC_WALLET_PASSPHRASE_INCORRECT = -14, // The wallet passphrase entered was incorrect
|
||||||
|
RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
|
||||||
|
RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet
|
||||||
|
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) |
||||||
|
{ |
||||||
|
boost::asio::ip::tcp::resolver resolver(stream.get_io_service()); |
||||||
|
boost::asio::ip::tcp::resolver::query query(server.c_str(), port.c_str()); |
||||||
|
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); |
||||||
|
boost::asio::ip::tcp::resolver::iterator end; |
||||||
|
boost::system::error_code error = boost::asio::error::host_not_found; |
||||||
|
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 HTTPReply(int nStatus, const std::string& strMsg, bool keepalive); |
||||||
|
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); |
||||||
|
std::string JSONRPCRequest(const std::string& strMethod, const json_spirit::Array& params, const json_spirit::Value& id); |
||||||
|
json_spirit::Object JSONRPCReplyObj(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id); |
||||||
|
std::string JSONRPCReply(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id); |
||||||
|
json_spirit::Object JSONRPCError(int code, const std::string& message); |
||||||
|
|
||||||
|
#endif |
Loading…
Reference in new issue