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
Wladimir J. van der Laan
11 years ago
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