aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Garzik <jgarzik@bitpay.com>2014-11-11 04:52:43 -0500
committerJeff Garzik <jgarzik@bitpay.com>2014-11-11 04:52:43 -0500
commite2655e0ab1fc36cd4a58a5145c53500cb7b2d4d0 (patch)
treea4ebff17c00212fd883cd2089f2aef1a3cf8db0e
parent83f5daf2fe6e2f42c81576097f4084d2788b454b (diff)
downloadbitcoin-e2655e0ab1fc36cd4a58a5145c53500cb7b2d4d0.tar.xz
Add unauthenticated HTTP REST interface to public blockchain data.
-rw-r--r--src/Makefile.am1
-rw-r--r--src/rest.cpp201
-rw-r--r--src/rpcserver.cpp9
-rw-r--r--src/rpcserver.h6
4 files changed, 216 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8b5d009842..2c2a96b198 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -165,6 +165,7 @@ libbitcoin_server_a_SOURCES = \
net.cpp \
noui.cpp \
pow.cpp \
+ rest.cpp \
rpcblockchain.cpp \
rpcmining.cpp \
rpcmisc.cpp \
diff --git a/src/rest.cpp b/src/rest.cpp
new file mode 100644
index 0000000000..48c1672ae8
--- /dev/null
+++ b/src/rest.cpp
@@ -0,0 +1,201 @@
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2012 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 <boost/algorithm/string.hpp>
+#include "rpcserver.h"
+#include "streams.h"
+#include "utilstrencodings.h"
+#include "core/block.h"
+#include "core/transaction.h"
+#include "version.h"
+#include "main.h"
+
+using namespace std;
+using namespace json_spirit;
+
+enum RetFormat {
+ RF_BINARY,
+ RF_HEX,
+ RF_JSON,
+};
+
+static const struct {
+ enum RetFormat rf;
+ const char *name;
+} rf_names[] = {
+ { RF_BINARY, "binary" }, // default, if match not found
+ { RF_HEX, "hex" },
+ { RF_JSON, "json" },
+};
+
+class RestErr {
+public:
+ enum HTTPStatusCode status;
+ string message;
+};
+
+extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry);
+extern Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex);
+
+static RestErr RESTERR(enum HTTPStatusCode status, string message)
+{
+ RestErr re;
+ re.status = status;
+ re.message = message;
+ return re;
+}
+
+static enum RetFormat ParseDataFormat(const string& format)
+{
+ for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
+ if (format == rf_names[i].name)
+ return rf_names[i].rf;
+
+ return rf_names[0].rf;
+}
+
+static bool ParseHashStr(string& strReq, uint256& v)
+{
+ if (!IsHex(strReq) || (strReq.size() != 64))
+ return false;
+
+ v.SetHex(strReq);
+ return true;
+}
+
+static bool rest_block(AcceptedConnection *conn,
+ string& strReq,
+ map<string, string>& mapHeaders,
+ bool fRun)
+{
+ vector<string> params;
+ boost::split(params, strReq, boost::is_any_of("/"));
+
+ enum RetFormat rf = ParseDataFormat(params.size() > 1 ? params[1] : string(""));
+
+ string hashStr = params[0];
+ uint256 hash;
+ if (!ParseHashStr(hashStr, hash))
+ throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
+
+ if (mapBlockIndex.count(hash) == 0)
+ throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+
+ CBlock block;
+ CBlockIndex* pblockindex = mapBlockIndex[hash];
+ if (!ReadBlockFromDisk(block, pblockindex))
+ throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+
+ CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
+ ssBlock << block;
+
+ switch (rf) {
+ case RF_BINARY: {
+ string binaryBlock = ssBlock.str();
+ conn->stream() << HTTPReply(HTTP_OK, binaryBlock, fRun, true, "application/octet-stream") << binaryBlock << std::flush;
+ 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;
+ return true;
+ }
+
+ case RF_JSON: {
+ Object objBlock = blockToJSON(block, pblockindex);
+ string strJSON = write_string(Value(objBlock), false) + "\n";
+ conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ return true;
+ }
+ }
+
+ // not reached
+ return true; // continue to process further HTTP reqs on this cxn
+}
+
+static bool rest_tx(AcceptedConnection *conn,
+ string& strReq,
+ map<string, string>& mapHeaders,
+ bool fRun)
+{
+ vector<string> params;
+ boost::split(params, strReq, boost::is_any_of("/"));
+
+ enum RetFormat rf = ParseDataFormat(params.size() > 1 ? params[1] : string(""));
+
+ string hashStr = params[0];
+ uint256 hash;
+ if (!ParseHashStr(hashStr, hash))
+ throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
+
+ CTransaction tx;
+ uint256 hashBlock = 0;
+ if (!GetTransaction(hash, tx, hashBlock, true))
+ throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found");
+
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << tx;
+
+ switch (rf) {
+ case RF_BINARY: {
+ string binaryTx = ssTx.str();
+ conn->stream() << HTTPReply(HTTP_OK, binaryTx, fRun, true, "application/octet-stream") << binaryTx << std::flush;
+ 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;
+ return true;
+ }
+
+ case RF_JSON: {
+ Object objTx;
+ TxToJSON(tx, hashBlock, objTx);
+ string strJSON = write_string(Value(objTx), false) + "\n";
+ conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
+ return true;
+ }
+ }
+
+ // not reached
+ return true; // continue to process further HTTP reqs on this cxn
+}
+
+static const struct {
+ const char *prefix;
+ bool (*handler)(AcceptedConnection *conn,
+ string& strURI,
+ map<string, string>& mapHeaders,
+ bool fRun);
+} uri_prefixes[] = {
+ { "/rest/tx/", rest_tx },
+ { "/rest/block/", rest_block },
+};
+
+bool HTTPReq_REST(AcceptedConnection *conn,
+ string& strURI,
+ map<string, string>& mapHeaders,
+ bool fRun)
+{
+ try {
+ 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 strReq = strURI.substr(plen);
+ return uri_prefixes[i].handler(conn, strReq, mapHeaders, fRun);
+ }
+ }
+ }
+ catch (RestErr& re) {
+ conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush;
+ return false;
+ }
+
+ conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush;
+ return false;
+}
+
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp
index cc80887ba4..d072f18e36 100644
--- a/src/rpcserver.cpp
+++ b/src/rpcserver.cpp
@@ -946,11 +946,18 @@ void ServiceConnection(AcceptedConnection *conn)
if (mapHeaders["connection"] == "close")
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/") {
+ if (!HTTPReq_REST(conn, strURI, mapHeaders, fRun))
+ break;
+
} else {
- conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush;
+ conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush;
break;
}
}
diff --git a/src/rpcserver.h b/src/rpcserver.h
index 2a258dd89a..60793f79ae 100644
--- a/src/rpcserver.h
+++ b/src/rpcserver.h
@@ -218,4 +218,10 @@ extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp)
extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
+// in rest.cpp
+extern bool HTTPReq_REST(AcceptedConnection *conn,
+ std::string& strURI,
+ std::map<std::string, std::string>& mapHeaders,
+ bool fRun);
+
#endif // BITCOIN_RPCSERVER_H