You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rest.cpp 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. // Copyright (c) 2009-2010 Satoshi Nakamoto
  2. // Copyright (c) 2009-2016 The Starwels developers
  3. // Distributed under the MIT software license, see the accompanying
  4. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5. #include "chain.h"
  6. #include "chainparams.h"
  7. #include "core_io.h"
  8. #include "primitives/block.h"
  9. #include "primitives/transaction.h"
  10. #include "validation.h"
  11. #include "httpserver.h"
  12. #include "rpc/blockchain.h"
  13. #include "rpc/server.h"
  14. #include "streams.h"
  15. #include "sync.h"
  16. #include "txmempool.h"
  17. #include "utilstrencodings.h"
  18. #include "version.h"
  19. #include <boost/algorithm/string.hpp>
  20. #include <univalue.h>
  21. static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
  22. enum RetFormat {
  23. RF_UNDEF,
  24. RF_BINARY,
  25. RF_HEX,
  26. RF_JSON,
  27. };
  28. static const struct {
  29. enum RetFormat rf;
  30. const char* name;
  31. } rf_names[] = {
  32. {RF_UNDEF, ""},
  33. {RF_BINARY, "bin"},
  34. {RF_HEX, "hex"},
  35. {RF_JSON, "json"},
  36. };
  37. struct CCoin {
  38. uint32_t nHeight;
  39. CTxOut out;
  40. ADD_SERIALIZE_METHODS;
  41. CCoin() : nHeight(0) {}
  42. CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
  43. template <typename Stream, typename Operation>
  44. inline void SerializationOp(Stream& s, Operation ser_action)
  45. {
  46. uint32_t nTxVerDummy = 0;
  47. READWRITE(nTxVerDummy);
  48. READWRITE(nHeight);
  49. READWRITE(out);
  50. }
  51. };
  52. static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
  53. {
  54. req->WriteHeader("Content-Type", "text/plain");
  55. req->WriteReply(status, message + "\r\n");
  56. return false;
  57. }
  58. static enum RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
  59. {
  60. const std::string::size_type pos = strReq.rfind('.');
  61. if (pos == std::string::npos)
  62. {
  63. param = strReq;
  64. return rf_names[0].rf;
  65. }
  66. param = strReq.substr(0, pos);
  67. const std::string suff(strReq, pos + 1);
  68. for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
  69. if (suff == rf_names[i].name)
  70. return rf_names[i].rf;
  71. /* If no suffix is found, return original string. */
  72. param = strReq;
  73. return rf_names[0].rf;
  74. }
  75. static std::string AvailableDataFormatsString()
  76. {
  77. std::string formats = "";
  78. for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++)
  79. if (strlen(rf_names[i].name) > 0) {
  80. formats.append(".");
  81. formats.append(rf_names[i].name);
  82. formats.append(", ");
  83. }
  84. if (formats.length() > 0)
  85. return formats.substr(0, formats.length() - 2);
  86. return formats;
  87. }
  88. static bool ParseHashStr(const std::string& strReq, uint256& v)
  89. {
  90. if (!IsHex(strReq) || (strReq.size() != 64))
  91. return false;
  92. v.SetHex(strReq);
  93. return true;
  94. }
  95. static bool CheckWarmup(HTTPRequest* req)
  96. {
  97. std::string statusmessage;
  98. if (RPCIsInWarmup(&statusmessage))
  99. return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
  100. return true;
  101. }
  102. static bool rest_headers(HTTPRequest* req,
  103. const std::string& strURIPart)
  104. {
  105. if (!CheckWarmup(req))
  106. return false;
  107. std::string param;
  108. const RetFormat rf = ParseDataFormat(param, strURIPart);
  109. std::vector<std::string> path;
  110. boost::split(path, param, boost::is_any_of("/"));
  111. if (path.size() != 2)
  112. return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
  113. long count = strtol(path[0].c_str(), nullptr, 10);
  114. if (count < 1 || count > 2000)
  115. return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
  116. std::string hashStr = path[1];
  117. uint256 hash;
  118. if (!ParseHashStr(hashStr, hash))
  119. return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
  120. std::vector<const CBlockIndex *> headers;
  121. headers.reserve(count);
  122. {
  123. LOCK(cs_main);
  124. BlockMap::const_iterator it = mapBlockIndex.find(hash);
  125. const CBlockIndex *pindex = (it != mapBlockIndex.end()) ? it->second : nullptr;
  126. while (pindex != nullptr && chainActive.Contains(pindex)) {
  127. headers.push_back(pindex);
  128. if (headers.size() == (unsigned long)count)
  129. break;
  130. pindex = chainActive.Next(pindex);
  131. }
  132. }
  133. CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
  134. for (const CBlockIndex *pindex : headers) {
  135. ssHeader << pindex->GetBlockHeader();
  136. }
  137. switch (rf) {
  138. case RF_BINARY: {
  139. std::string binaryHeader = ssHeader.str();
  140. req->WriteHeader("Content-Type", "application/octet-stream");
  141. req->WriteReply(HTTP_OK, binaryHeader);
  142. return true;
  143. }
  144. case RF_HEX: {
  145. std::string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n";
  146. req->WriteHeader("Content-Type", "text/plain");
  147. req->WriteReply(HTTP_OK, strHex);
  148. return true;
  149. }
  150. case RF_JSON: {
  151. UniValue jsonHeaders(UniValue::VARR);
  152. for (const CBlockIndex *pindex : headers) {
  153. jsonHeaders.push_back(blockheaderToJSON(pindex));
  154. }
  155. std::string strJSON = jsonHeaders.write() + "\n";
  156. req->WriteHeader("Content-Type", "application/json");
  157. req->WriteReply(HTTP_OK, strJSON);
  158. return true;
  159. }
  160. default: {
  161. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)");
  162. }
  163. }
  164. // not reached
  165. return true; // continue to process further HTTP reqs on this cxn
  166. }
  167. static bool rest_block(HTTPRequest* req,
  168. const std::string& strURIPart,
  169. bool showTxDetails)
  170. {
  171. if (!CheckWarmup(req))
  172. return false;
  173. std::string hashStr;
  174. const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
  175. uint256 hash;
  176. if (!ParseHashStr(hashStr, hash))
  177. return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
  178. CBlock block;
  179. CBlockIndex* pblockindex = nullptr;
  180. {
  181. LOCK(cs_main);
  182. if (mapBlockIndex.count(hash) == 0)
  183. return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
  184. pblockindex = mapBlockIndex[hash];
  185. if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0)
  186. return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
  187. if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
  188. return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
  189. }
  190. CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
  191. ssBlock << block;
  192. switch (rf) {
  193. case RF_BINARY: {
  194. std::string binaryBlock = ssBlock.str();
  195. req->WriteHeader("Content-Type", "application/octet-stream");
  196. req->WriteReply(HTTP_OK, binaryBlock);
  197. return true;
  198. }
  199. case RF_HEX: {
  200. std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
  201. req->WriteHeader("Content-Type", "text/plain");
  202. req->WriteReply(HTTP_OK, strHex);
  203. return true;
  204. }
  205. case RF_JSON: {
  206. UniValue objBlock = blockToJSON(block, pblockindex, showTxDetails);
  207. std::string strJSON = objBlock.write() + "\n";
  208. req->WriteHeader("Content-Type", "application/json");
  209. req->WriteReply(HTTP_OK, strJSON);
  210. return true;
  211. }
  212. default: {
  213. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
  214. }
  215. }
  216. // not reached
  217. return true; // continue to process further HTTP reqs on this cxn
  218. }
  219. static bool rest_block_extended(HTTPRequest* req, const std::string& strURIPart)
  220. {
  221. return rest_block(req, strURIPart, true);
  222. }
  223. static bool rest_block_notxdetails(HTTPRequest* req, const std::string& strURIPart)
  224. {
  225. return rest_block(req, strURIPart, false);
  226. }
  227. // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
  228. UniValue getblockchaininfo(const JSONRPCRequest& request);
  229. static bool rest_chaininfo(HTTPRequest* req, const std::string& strURIPart)
  230. {
  231. if (!CheckWarmup(req))
  232. return false;
  233. std::string param;
  234. const RetFormat rf = ParseDataFormat(param, strURIPart);
  235. switch (rf) {
  236. case RF_JSON: {
  237. JSONRPCRequest jsonRequest;
  238. jsonRequest.params = UniValue(UniValue::VARR);
  239. UniValue chainInfoObject = getblockchaininfo(jsonRequest);
  240. std::string strJSON = chainInfoObject.write() + "\n";
  241. req->WriteHeader("Content-Type", "application/json");
  242. req->WriteReply(HTTP_OK, strJSON);
  243. return true;
  244. }
  245. default: {
  246. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
  247. }
  248. }
  249. // not reached
  250. return true; // continue to process further HTTP reqs on this cxn
  251. }
  252. static bool rest_mempool_info(HTTPRequest* req, const std::string& strURIPart)
  253. {
  254. if (!CheckWarmup(req))
  255. return false;
  256. std::string param;
  257. const RetFormat rf = ParseDataFormat(param, strURIPart);
  258. switch (rf) {
  259. case RF_JSON: {
  260. UniValue mempoolInfoObject = mempoolInfoToJSON();
  261. std::string strJSON = mempoolInfoObject.write() + "\n";
  262. req->WriteHeader("Content-Type", "application/json");
  263. req->WriteReply(HTTP_OK, strJSON);
  264. return true;
  265. }
  266. default: {
  267. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
  268. }
  269. }
  270. // not reached
  271. return true; // continue to process further HTTP reqs on this cxn
  272. }
  273. static bool rest_mempool_contents(HTTPRequest* req, const std::string& strURIPart)
  274. {
  275. if (!CheckWarmup(req))
  276. return false;
  277. std::string param;
  278. const RetFormat rf = ParseDataFormat(param, strURIPart);
  279. switch (rf) {
  280. case RF_JSON: {
  281. UniValue mempoolObject = mempoolToJSON(true);
  282. std::string strJSON = mempoolObject.write() + "\n";
  283. req->WriteHeader("Content-Type", "application/json");
  284. req->WriteReply(HTTP_OK, strJSON);
  285. return true;
  286. }
  287. default: {
  288. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
  289. }
  290. }
  291. // not reached
  292. return true; // continue to process further HTTP reqs on this cxn
  293. }
  294. static bool rest_tx(HTTPRequest* req, const std::string& strURIPart)
  295. {
  296. if (!CheckWarmup(req))
  297. return false;
  298. std::string hashStr;
  299. const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
  300. uint256 hash;
  301. if (!ParseHashStr(hashStr, hash))
  302. return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
  303. CTransactionRef tx;
  304. uint256 hashBlock = uint256();
  305. if (!GetTransaction(hash, tx, Params().GetConsensus(), hashBlock, true))
  306. return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
  307. CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
  308. ssTx << tx;
  309. switch (rf) {
  310. case RF_BINARY: {
  311. std::string binaryTx = ssTx.str();
  312. req->WriteHeader("Content-Type", "application/octet-stream");
  313. req->WriteReply(HTTP_OK, binaryTx);
  314. return true;
  315. }
  316. case RF_HEX: {
  317. std::string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";
  318. req->WriteHeader("Content-Type", "text/plain");
  319. req->WriteReply(HTTP_OK, strHex);
  320. return true;
  321. }
  322. case RF_JSON: {
  323. UniValue objTx(UniValue::VOBJ);
  324. TxToUniv(*tx, hashBlock, objTx);
  325. std::string strJSON = objTx.write() + "\n";
  326. req->WriteHeader("Content-Type", "application/json");
  327. req->WriteReply(HTTP_OK, strJSON);
  328. return true;
  329. }
  330. default: {
  331. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
  332. }
  333. }
  334. // not reached
  335. return true; // continue to process further HTTP reqs on this cxn
  336. }
  337. static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
  338. {
  339. if (!CheckWarmup(req))
  340. return false;
  341. std::string param;
  342. const RetFormat rf = ParseDataFormat(param, strURIPart);
  343. std::vector<std::string> uriParts;
  344. if (param.length() > 1)
  345. {
  346. std::string strUriParams = param.substr(1);
  347. boost::split(uriParts, strUriParams, boost::is_any_of("/"));
  348. }
  349. // throw exception in case of an empty request
  350. std::string strRequestMutable = req->ReadBody();
  351. if (strRequestMutable.length() == 0 && uriParts.size() == 0)
  352. return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
  353. bool fInputParsed = false;
  354. bool fCheckMemPool = false;
  355. std::vector<COutPoint> vOutPoints;
  356. // parse/deserialize input
  357. // input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
  358. if (uriParts.size() > 0)
  359. {
  360. //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
  361. if (uriParts.size() > 0 && uriParts[0] == "checkmempool")
  362. fCheckMemPool = true;
  363. for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
  364. {
  365. uint256 txid;
  366. int32_t nOutput;
  367. std::string strTxid = uriParts[i].substr(0, uriParts[i].find("-"));
  368. std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1);
  369. if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
  370. return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
  371. txid.SetHex(strTxid);
  372. vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
  373. }
  374. if (vOutPoints.size() > 0)
  375. fInputParsed = true;
  376. else
  377. return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
  378. }
  379. switch (rf) {
  380. case RF_HEX: {
  381. // convert hex to bin, continue then with bin part
  382. std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
  383. strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
  384. }
  385. case RF_BINARY: {
  386. try {
  387. //deserialize only if user sent a request
  388. if (strRequestMutable.size() > 0)
  389. {
  390. if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
  391. return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");
  392. CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
  393. oss << strRequestMutable;
  394. oss >> fCheckMemPool;
  395. oss >> vOutPoints;
  396. }
  397. } catch (const std::ios_base::failure& e) {
  398. // abort in case of unreadable binary data
  399. return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
  400. }
  401. break;
  402. }
  403. case RF_JSON: {
  404. if (!fInputParsed)
  405. return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
  406. break;
  407. }
  408. default: {
  409. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
  410. }
  411. }
  412. // limit max outpoints
  413. if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
  414. return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
  415. // check spentness and form a bitmap (as well as a JSON capable human-readable string representation)
  416. std::vector<unsigned char> bitmap;
  417. std::vector<CCoin> outs;
  418. std::string bitmapStringRepresentation;
  419. std::vector<bool> hits;
  420. bitmap.resize((vOutPoints.size() + 7) / 8);
  421. {
  422. LOCK2(cs_main, mempool.cs);
  423. CCoinsView viewDummy;
  424. CCoinsViewCache view(&viewDummy);
  425. CCoinsViewCache& viewChain = *pcoinsTip;
  426. CCoinsViewMemPool viewMempool(&viewChain, mempool);
  427. if (fCheckMemPool)
  428. view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool
  429. for (size_t i = 0; i < vOutPoints.size(); i++) {
  430. bool hit = false;
  431. Coin coin;
  432. if (view.GetCoin(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
  433. hit = true;
  434. outs.emplace_back(std::move(coin));
  435. }
  436. hits.push_back(hit);
  437. bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
  438. bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
  439. }
  440. }
  441. switch (rf) {
  442. case RF_BINARY: {
  443. // serialize data
  444. // use exact same output as mentioned in Bip64
  445. CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
  446. ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
  447. std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
  448. req->WriteHeader("Content-Type", "application/octet-stream");
  449. req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
  450. return true;
  451. }
  452. case RF_HEX: {
  453. CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
  454. ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs;
  455. std::string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n";
  456. req->WriteHeader("Content-Type", "text/plain");
  457. req->WriteReply(HTTP_OK, strHex);
  458. return true;
  459. }
  460. case RF_JSON: {
  461. UniValue objGetUTXOResponse(UniValue::VOBJ);
  462. // pack in some essentials
  463. // use more or less the same output as mentioned in Bip64
  464. objGetUTXOResponse.push_back(Pair("chainHeight", chainActive.Height()));
  465. objGetUTXOResponse.push_back(Pair("chaintipHash", chainActive.Tip()->GetBlockHash().GetHex()));
  466. objGetUTXOResponse.push_back(Pair("bitmap", bitmapStringRepresentation));
  467. UniValue utxos(UniValue::VARR);
  468. for (const CCoin& coin : outs) {
  469. UniValue utxo(UniValue::VOBJ);
  470. utxo.push_back(Pair("height", (int32_t)coin.nHeight));
  471. utxo.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));
  472. // include the script in a json output
  473. UniValue o(UniValue::VOBJ);
  474. ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
  475. utxo.push_back(Pair("scriptPubKey", o));
  476. utxos.push_back(utxo);
  477. }
  478. objGetUTXOResponse.push_back(Pair("utxos", utxos));
  479. // return json string
  480. std::string strJSON = objGetUTXOResponse.write() + "\n";
  481. req->WriteHeader("Content-Type", "application/json");
  482. req->WriteReply(HTTP_OK, strJSON);
  483. return true;
  484. }
  485. default: {
  486. return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
  487. }
  488. }
  489. // not reached
  490. return true; // continue to process further HTTP reqs on this cxn
  491. }
  492. static const struct {
  493. const char* prefix;
  494. bool (*handler)(HTTPRequest* req, const std::string& strReq);
  495. } uri_prefixes[] = {
  496. {"/rest/tx/", rest_tx},
  497. {"/rest/block/notxdetails/", rest_block_notxdetails},
  498. {"/rest/block/", rest_block_extended},
  499. {"/rest/chaininfo", rest_chaininfo},
  500. {"/rest/mempool/info", rest_mempool_info},
  501. {"/rest/mempool/contents", rest_mempool_contents},
  502. {"/rest/headers/", rest_headers},
  503. {"/rest/getutxos", rest_getutxos},
  504. };
  505. bool StartREST()
  506. {
  507. for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
  508. RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler);
  509. return true;
  510. }
  511. void InterruptREST()
  512. {
  513. }
  514. void StopREST()
  515. {
  516. for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++)
  517. UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
  518. }