diff --git a/SConstruct b/SConstruct index 6371da57e..32e614c5a 100644 --- a/SConstruct +++ b/SConstruct @@ -88,7 +88,7 @@ PROTO_SRCS = env.Protoc([], 'src/ripple.proto', PROTOCOUTDIR='obj', PROTOCPYTHON env.Clean(PROTO_SRCS, 'site_scons/site_tools/protoc.pyc') # Remove unused source files. -UNUSED_SRCS = ['src/HttpReply.cpp'] +UNUSED_SRCS = [] for file in UNUSED_SRCS: NEWCOIN_SRCS.remove(file) diff --git a/js/amount.js b/js/amount.js index 099913479..c3dfa3905 100644 --- a/js/amount.js +++ b/js/amount.js @@ -258,6 +258,15 @@ Amount.prototype.canonicalize = function() { } }; +Amount.prototype.negate = function () { + if (this.is_native) { + this.value.negate(); + } + else { + this.is_negative = !this.is_negative; + } +}; + Amount.prototype.to_json = function() { if (this.is_native) { return this.to_text(); @@ -400,6 +409,12 @@ Amount.prototype.parse_json = function(j) { return this; }; +Amount.prototype.parse_issuer = function (issuer) { + this.issuer.parse_json(issuer); + + return this; +}; + exports.setAccounts = setAccounts; exports.Amount = Amount; exports.Currency = Currency; diff --git a/js/remote.js b/js/remote.js index 083eb01f2..5fed5c549 100644 --- a/js/remote.js +++ b/js/remote.js @@ -16,8 +16,8 @@ var EventEmitter = require('events').EventEmitter; // npm var WebSocket = require('ws'); -var amount = require('./amount.js'); -var Amount = amount.Amount; +var Amount = require('./amount.js').Amount; +var UInt160 = require('./amount.js').UInt160; // Request events emmitted: // 'success' : Request successful. @@ -63,10 +63,23 @@ Request.prototype.request_default = function () { this.remote.request(this); }; +Request.prototype.ledger_choose = function (current) { + if (current) + { + this.message.ledger_index = this.remote.ledger_current_index; + } + else { + this.message.ledger = this.remote.ledger_closed; + } + + return this; +}; + // Set the ledger for a request. // - ledger_entry -Request.prototype.ledger = function (ledger) { - this.message.ledger = ledger; +// - transaction_entry +Request.prototype.ledger_closed = function (ledger) { + this.message.ledger_closed = ledger; return this; }; @@ -104,6 +117,15 @@ Request.prototype.transaction = function (t) { return this; }; +Request.prototype.ripple_state = function (account, issuer, currency) { + this.message.ripple_state = { + 'accounts' : [ account, issuer ], + 'currency' : currency + }; + + return this; +}; + // // Remote - access to a remote Ripple server via websocket. // @@ -463,9 +485,10 @@ Remote.prototype.request_ledger_entry = function (type) { // Transparent caching: request.on('request', function (remote) { // Intercept default request. if (this.ledger_closed) { - // XXX Initial implementation no caching. + // XXX Add caching. } // else if (req.ledger_index) + // else if ('ripple_state' === this.type) // YYY Could be cached per ledger. else if ('account_root' === this.type) { var cache = self.ledgers.current.account_root; @@ -507,17 +530,11 @@ Remote.prototype.request_ledger_entry = function (type) { return request; }; -// --> ledger_closed : optional -Remote.prototype.request_transaction_entry = function (hash, ledger_closed) { +Remote.prototype.request_transaction_entry = function (hash) { assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. - var request = new Request(this, 'transaction_entry'); - - request.message.transaction = hash; - if (ledger_closed) - request.message.ledger_closed = ledger_closed; - - return request; + return (new Request(this, 'transaction_entry')) + .transaction(hash); }; // Submit a transaction. @@ -587,10 +604,8 @@ Remote.prototype.submit = function (transaction) { Remote.prototype._server_subscribe = function () { var self = this; - var request = new Request(this, 'server_subscribe'); - - request. - on('success', function (message) { + (new Request(this, 'server_subscribe')) + .on('success', function (message) { self.stand_alone = !!message.stand_alone; if (message.ledger_closed && message.ledger_current_index) { @@ -614,9 +629,8 @@ Remote.prototype._server_subscribe = function () { Remote.prototype.ledger_accept = function () { if (this.stand_alone || undefined === this.stand_alone) { - var request = new Request(this, 'ledger_accept'); - - request.request(); + (new Request(this, 'ledger_accept')) + .request(); } else { this.emit('error', { @@ -627,6 +641,17 @@ Remote.prototype.ledger_accept = function () { return this; }; +// Return a request to refresh the account balance. +Remote.prototype.request_account_balance = function (account, current) { + return (this.request_ledger_entry('account_root')) + .account_root(account) + .ledger_choose(current) + .on('success', function (message) { + // If the caller also waits for 'success', they might run before this. + request.emit('account_balance', message.node.Balance); + }); +}; + // Return the next account sequence if possible. // <-- undefined or Sequence Remote.prototype.account_seq = function (account, advance) { @@ -648,29 +673,19 @@ Remote.prototype.account_seq_cache = function (account, current) { var self = this; var request = this.request_ledger_entry('account_root'); - request + return request .account_root(account) + .ledger_choose(current) .on('success', function (message) { - var seq = message.node.Sequence; - - if (!self.accounts[account]) - self.accounts[account] = {}; + var seq = message.node.Sequence; + + if (!self.accounts[account]) self.accounts[account] = {}; - self.accounts[account].seq = seq; + self.accounts[account].seq = seq; - // If the caller also waits for 'success', they might run before this. - request.emit('success_account_seq_cache'); - }); - - if (current) - { - request.ledger_index(this.ledger_current_index); - } - else { - request.ledger(this.ledger_closed); - } - - return request; + // If the caller also waits for 'success', they might run before this. + request.emit('success_account_seq_cache'); + }); }; // Mark an account's root node as dirty. @@ -678,6 +693,37 @@ Remote.prototype.dirty_account_root = function (account) { delete this.ledgers.current.account_root[account]; }; +// Return a request to get a ripple balance. +// +// --> account: String +// --> issuer: String +// --> currency: String +// --> current: bool : true = current ledger +Remote.prototype.request_ripple_balance = function (account, issuer, currency, current) { + var src = this.remote.config.accounts[account] ? this.remote.config.accounts[account].account : account; + var dst = this.remote.config.accounts[issuer] ? this.remote.config.accounts[issuer].account : issuer; + + return (this.request_ledger_entry('ripple_state')) // YYY Could be cached per ledger. + .ripple_state(src, dst, currency) + .ledger_choose(current) + .on('success', function (message) { + var node = message.node; + var flip = UInt160.from_json(src) == node.HighLimit.issuer; + var issuerLimit = flip ? node.LowLimit : node.HighLimit; + var accountLimit = flip ? node.HighLimit : node.LowLimit; + var issuerBalance = (flip ? node.Balance.clone().negate() : node.Balance.clone()).parse_issuer(dst); + var accountBalance = issuerBalance.clone().parse_issuer(dst); + + // If the caller also waits for 'success', they might run before this. + request.emit('ripple_state', { + 'issuer_balance' : issuerBalance, // Balance with dst as issuer. + 'account_balance' : accountBalance, // Balance with src as issuer. + 'issuer_limit' : issuerLimit.clone().parse_issuer(src), // Limit set by issuer with src as issuer. + 'account_limit' : accountLimit.clone().parse_issuer(dst) // Limit set by account with dst as issuer. + }); + }); +} + Remote.prototype.transaction = function () { return new Transaction(this); }; @@ -849,7 +895,8 @@ Transaction.prototype.submit = function () { var stop = false; // XXX make sure self.hash is available. - self.remote.request_transaction_entry(self.hash, ledger_closed) + self.remote.request_transaction_entry(self.hash) + .ledger_closed(ledger_closed) .on('success', function (message) { self.set_state(message.metadata.TransactionResult); self.emit('final', message); diff --git a/src/HTTPRequest.cpp b/src/HTTPRequest.cpp new file mode 100644 index 000000000..c3f365e0e --- /dev/null +++ b/src/HTTPRequest.cpp @@ -0,0 +1,102 @@ +#include "HTTPRequest.h" + +#include +#include +#include + +#include "Log.h" +SETUP_LOG(); + +void HTTPRequest::reset() +{ + vHeaders.clear(); + sRequestBody.clear(); + sAuthorization.clear(); + iDataSize = 0; + bShouldClose = true; + eState = await_request; +} + +HTTPRequestAction HTTPRequest::requestDone(bool forceClose) +{ + if (forceClose || bShouldClose) + return haCLOSE_CONN; + reset(); + return haREAD_LINE; +} + +std::string HTTPRequest::getReplyHeaders(bool forceClose) +{ + if (forceClose || bShouldClose) + return "Connection: close\r\n"; + else + return "Connection: Keep-Alive\r\n"; +} + +HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf) +{ + std::string line; + std::istream is(&buf); + std::getline(is, line); + boost::trim(line); + +// cLog(lsTRACE) << "HTTPRequest line: " << line; + + if (eState == await_request) + { // VERB URL PROTO + if (line.empty()) + return haREAD_LINE; + sRequest = line; + bShouldClose = sRequest.find("HTTP/1.1") == std::string::npos; + + eState = await_header; + return haREAD_LINE; + } + + if (eState == await_header) + { // HEADER_NAME: HEADER_BODY + if (line.empty()) // empty line or bare \r + { + if (iDataSize == 0) + { // no body + eState = do_request; + return haDO_REQUEST; + } + eState = getting_body; + return haREAD_RAW; + } + vHeaders.push_back(line); + + size_t colon = line.find(':'); + if (colon != std::string::npos) + { + std::string headerName = line.substr(0, colon); + boost::trim(headerName); + boost::to_lower(headerName); + + std::string headerValue = line.substr(colon+1); + boost::trim(headerValue); + + if (headerName == "connection") + { + boost::to_lower(headerValue); + if ((headerValue == "keep-alive") || (headerValue == "keepalive")) + bShouldClose = false; + if (headerValue == "close") + bShouldClose = true; + } + + if (headerName == "content-length") + iDataSize = boost::lexical_cast(headerValue); + + if (headerName == "authorization") + sAuthorization = headerValue; + + } + + return haREAD_LINE; + } + + assert(false); + return haERROR; +} diff --git a/src/HTTPRequest.h b/src/HTTPRequest.h new file mode 100644 index 000000000..9ecea2443 --- /dev/null +++ b/src/HTTPRequest.h @@ -0,0 +1,61 @@ +#ifndef HTTPREQUEST__HPP +#define HTTPREQUEST__HPP + +#include +#include + +#include + +enum HTTPRequestAction +{ // What the application code needs to do + haERROR = 0, + haREAD_LINE = 1, + haREAD_RAW = 2, + haDO_REQUEST = 3, + haCLOSE_CONN = 4 +}; + +class HTTPRequest +{ // an HTTP request in progress +protected: + + enum state + { + await_request, // We are waiting for the request line + await_header, // We are waiting for request headers + getting_body, // We are waiting for the body + do_request, // We are waiting for the request to complete + }; + + state eState; + std::string sRequest; // VERB URL PROTO + std::string sRequestBody; + std::string sAuthorization; + + std::vector vHeaders; + + int iDataSize; + bool bShouldClose; + +public: + + HTTPRequest() : eState(await_request), iDataSize(0), bShouldClose(true) { ; } + void reset(); + + std::string& peekBody() { return sRequestBody; } + std::string getBody() { return sRequestBody; } + std::string& peekRequest() { return sRequest; } + std::string getRequest() { return sRequest; } + std::string& peekAuth() { return sAuthorization; } + std::string getAuth() { return sAuthorization; } + + std::vector& peekHeaders() { return vHeaders; } + std::string getReplyHeaders(bool forceClose); + + HTTPRequestAction consume(boost::asio::streambuf&); + HTTPRequestAction requestDone(bool forceClose); // call after reply is sent + + int getDataSize() { return iDataSize; } +}; + +#endif diff --git a/src/HttpReply.cpp b/src/HttpReply.cpp deleted file mode 100644 index 4dbb9182d..000000000 --- a/src/HttpReply.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "HttpReply.h" -#include -#include -#include - - -namespace status_strings -{ - const std::string ok = - "HTTP/1.0 200 OK\r\n"; - const std::string created = - "HTTP/1.0 201 Created\r\n"; - const std::string accepted = - "HTTP/1.0 202 Accepted\r\n"; - const std::string no_content = - "HTTP/1.0 204 No Content\r\n"; - const std::string multiple_choices = - "HTTP/1.0 300 Multiple Choices\r\n"; - const std::string moved_permanently = - "HTTP/1.0 301 Moved Permanently\r\n"; - const std::string moved_temporarily = - "HTTP/1.0 302 Moved Temporarily\r\n"; - const std::string not_modified = - "HTTP/1.0 304 Not Modified\r\n"; - const std::string bad_request = - "HTTP/1.0 400 Bad Request\r\n"; - const std::string unauthorized = - "HTTP/1.0 401 Unauthorized\r\n"; - const std::string forbidden = - "HTTP/1.0 403 Forbidden\r\n"; - const std::string not_found = - "HTTP/1.0 404 Not Found\r\n"; - const std::string internal_server_error = - "HTTP/1.0 500 Internal Server Error\r\n"; - const std::string not_implemented = - "HTTP/1.0 501 Not Implemented\r\n"; - const std::string bad_gateway = - "HTTP/1.0 502 Bad Gateway\r\n"; - const std::string service_unavailable = - "HTTP/1.0 503 Service Unavailable\r\n"; - - boost::asio::const_buffer to_buffer(HttpReply::status_type status) - { - switch (status) - { - case HttpReply::ok: - return boost::asio::buffer(ok); - case HttpReply::created: - return boost::asio::buffer(created); - case HttpReply::accepted: - return boost::asio::buffer(accepted); - case HttpReply::no_content: - return boost::asio::buffer(no_content); - case HttpReply::multiple_choices: - return boost::asio::buffer(multiple_choices); - case HttpReply::moved_permanently: - return boost::asio::buffer(moved_permanently); - case HttpReply::moved_temporarily: - return boost::asio::buffer(moved_temporarily); - case HttpReply::not_modified: - return boost::asio::buffer(not_modified); - case HttpReply::bad_request: - return boost::asio::buffer(bad_request); - case HttpReply::unauthorized: - return boost::asio::buffer(unauthorized); - case HttpReply::forbidden: - return boost::asio::buffer(forbidden); - case HttpReply::not_found: - return boost::asio::buffer(not_found); - case HttpReply::internal_server_error: - return boost::asio::buffer(internal_server_error); - case HttpReply::not_implemented: - return boost::asio::buffer(not_implemented); - case HttpReply::bad_gateway: - return boost::asio::buffer(bad_gateway); - case HttpReply::service_unavailable: - return boost::asio::buffer(service_unavailable); - default: - return boost::asio::buffer(internal_server_error); - } - } - -} // namespace status_strings - -namespace misc_strings -{ - const char name_value_separator[] = { ':', ' ' }; - const char crlf[] = { '\r', '\n' }; -} // namespace misc_strings - -std::vector HttpReply::to_buffers() -{ - std::vector buffers; - buffers.push_back(status_strings::to_buffer(status)); - for (std::size_t i = 0; i < headers.size(); ++i) - { - HttpHeader& h = headers[i]; - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - buffers.push_back(boost::asio::buffer(content)); - return buffers; -} - -namespace stock_replies { - -const char ok[] = ""; -const char created[] = - "" - "Created" - "

201 Created

" - ""; -const char accepted[] = - "" - "Accepted" - "

202 Accepted

" - ""; -const char no_content[] = - "" - "No Content" - "

204 Content

" - ""; -const char multiple_choices[] = - "" - "Multiple Choices" - "

300 Multiple Choices

" - ""; -const char moved_permanently[] = - "" - "Moved Permanently" - "

301 Moved Permanently

" - ""; -const char moved_temporarily[] = - "" - "Moved Temporarily" - "

302 Moved Temporarily

" - ""; -const char not_modified[] = - "" - "Not Modified" - "

304 Not Modified

" - ""; -const char bad_request[] = - "" - "Bad Request" - "

400 Bad Request

" - ""; -const char unauthorized[] = - "" - "Unauthorized" - "

401 Unauthorized

" - ""; -const char forbidden[] = - "" - "Forbidden" - "

403 Forbidden

" - ""; -const char not_found[] = - "" - "Not Found" - "

404 Not Found

" - ""; -const char internal_server_error[] = - "" - "Internal Server Error" - "

500 Internal Server Error

" - ""; -const char not_implemented[] = - "" - "Not Implemented" - "

501 Not Implemented

" - ""; -const char bad_gateway[] = - "" - "Bad Gateway" - "

502 Bad Gateway

" - ""; -const char service_unavailable[] = - "" - "Service Unavailable" - "

503 Service Unavailable

" - ""; - -std::string to_string(HttpReply::status_type status) -{ - switch (status) - { - case HttpReply::ok: - return ok; - case HttpReply::created: - return created; - case HttpReply::accepted: - return accepted; - case HttpReply::no_content: - return no_content; - case HttpReply::multiple_choices: - return multiple_choices; - case HttpReply::moved_permanently: - return moved_permanently; - case HttpReply::moved_temporarily: - return moved_temporarily; - case HttpReply::not_modified: - return not_modified; - case HttpReply::bad_request: - return bad_request; - case HttpReply::unauthorized: - return unauthorized; - case HttpReply::forbidden: - return forbidden; - case HttpReply::not_found: - return not_found; - case HttpReply::internal_server_error: - return internal_server_error; - case HttpReply::not_implemented: - return not_implemented; - case HttpReply::bad_gateway: - return bad_gateway; - case HttpReply::service_unavailable: - return service_unavailable; - default: - return internal_server_error; - } -} - -} // namespace stock_replies - -HttpReply HttpReply::stock_reply(HttpReply::status_type status) -{ - HttpReply rep; - rep.status = status; - rep.content = stock_replies::to_string(status); - rep.headers.resize(2); - rep.headers[0].name = "Content-Length"; - rep.headers[0].value = boost::lexical_cast(rep.content.size()); - rep.headers[1].name = "Content-Type"; - rep.headers[1].value = "text/html"; - return rep; -} diff --git a/src/HttpReply.h b/src/HttpReply.h deleted file mode 100644 index d80232739..000000000 --- a/src/HttpReply.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef HTTP_REPLY_HPP -#define HTTP_REPLY_HPP - -#include -#include -#include -#include "HttpRequest.h" - -/// A reply to be sent to a client. -class HttpReply -{ -public: - /// The status of the reply. - enum status_type - { - ok = 200, - created = 201, - accepted = 202, - no_content = 204, - multiple_choices = 300, - moved_permanently = 301, - moved_temporarily = 302, - not_modified = 304, - bad_request = 400, - unauthorized = 401, - forbidden = 403, - not_found = 404, - internal_server_error = 500, - not_implemented = 501, - bad_gateway = 502, - service_unavailable = 503 - } status; - - /// The headers to be included in the reply. - std::vector headers; - - /// The content to be sent in the reply. - std::string content; - - /// Convert the reply into a vector of buffers. The buffers do not own the - /// underlying memory blocks, therefore the reply object must remain valid and - /// not be changed until the write operation has completed. - std::vector to_buffers(); - - /// Get a stock reply. - static HttpReply stock_reply(status_type status); -}; - - -#endif // HTTP_REPLY_HPP \ No newline at end of file diff --git a/src/HttpRequest.h b/src/HttpRequest.h deleted file mode 100644 index 2f6b54522..000000000 --- a/src/HttpRequest.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef HTTP_REQUEST_HPP -#define HTTP_REQUEST_HPP - -#include -#include - -struct HttpHeader -{ - std::string name; - std::string value; -}; - -/// A request received from a client. -struct HttpRequest -{ - std::string method; - std::string uri; - int http_version_major; - int http_version_minor; - std::vector headers; - std::string mBody; -}; - -#endif // HTTP_REQUEST_HPP diff --git a/src/RPCServer.cpp b/src/RPCServer.cpp index 7e5c58783..046a8946d 100644 --- a/src/RPCServer.cpp +++ b/src/RPCServer.cpp @@ -1,7 +1,5 @@ #include "RPCServer.h" -#include "RequestParser.h" -#include "HttpReply.h" #include "HttpsClient.h" #include "Application.h" #include "RPC.h" @@ -22,6 +20,7 @@ #include #include #include +#include #include @@ -30,6 +29,10 @@ SETUP_LOG(); +#ifndef RPC_MAXIMUM_QUERY +#define RPC_MAXIMUM_QUERY (1024*1024) +#endif + RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwork) : mNetOps(nopNetwork), mSocket(io_service) { @@ -107,45 +110,79 @@ Json::Value RPCServer::RPCError(int iError) void RPCServer::connected() { //std::cout << "RPC request" << std::endl; - if (mSocket.remote_endpoint().address().to_string()=="127.0.0.1") mRole=ADMIN; - else mRole=GUEST; + if (mSocket.remote_endpoint().address().to_string()=="127.0.0.1") mRole = ADMIN; + else mRole = GUEST; - mSocket.async_read_some(boost::asio::buffer(mReadBuffer), - boost::bind(&RPCServer::Shandle_read, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); + boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n", + boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error)); } -void RPCServer::handle_read(const boost::system::error_code& e, - std::size_t bytes_transferred) +void RPCServer::handle_read_req(const boost::system::error_code& e) { - if (!e) + std::string req; + + if (mLineBuffer.size()) { - boost::tribool result; - result = mRequestParser.parse( - mIncomingRequest, mReadBuffer.data(), mReadBuffer.data() + bytes_transferred); + req.assign(boost::asio::buffer_cast(mLineBuffer.data()), mLineBuffer.size()); + mLineBuffer.consume(mLineBuffer.size()); + } - if (result) + req += strCopy(mQueryVec); + mReplyStr = handleRequest(req); + boost::asio::async_write(mSocket, boost::asio::buffer(mReplyStr), + boost::bind(&RPCServer::handle_write, shared_from_this(), boost::asio::placeholders::error)); +} + +void RPCServer::handle_read_line(const boost::system::error_code& e) +{ + if (e) + return; + + HTTPRequestAction action = mHTTPRequest.consume(mLineBuffer); + + if (action == haDO_REQUEST) + { // request with no body + cLog(lsWARNING) << "RPC HTTP request with no body"; + boost::system::error_code ignore_ec; + mSocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_ec); + return; + } + else if (action == haREAD_LINE) + { + boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n", + boost::bind(&RPCServer::handle_read_line, shared_from_this(), + boost::asio::placeholders::error)); + } + else if (action == haREAD_RAW) + { + int rLen = mHTTPRequest.getDataSize(); + if ((rLen < 0) || (rLen > RPC_MAXIMUM_QUERY)) { - mReplyStr = handleRequest(mIncomingRequest.mBody); - - sendReply(); + cLog(lsWARNING) << "Illegal RPC request length " << rLen; + boost::system::error_code ignore_ec; + mSocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore_ec); + return; } - else if (!result) - { // bad request - std::cout << "bad request: " << mIncomingRequest.mBody <= 1); - BOOST_FOREACH(HttpHeader& h, mIncomingRequest.headers) - { - if (boost::iequals(h.name, "connection")) - { - if (boost::iequals(h.value, "keep-alive")) - keep_alive = true; - if (boost::iequals(h.value, "close")) - keep_alive = false; - } - } - if (keep_alive) - { - mIncomingRequest.method.clear(); - mIncomingRequest.uri.clear(); - mIncomingRequest.mBody.clear(); - mIncomingRequest.headers.clear(); - mRequestParser.reset(); - mSocket.async_read_some(boost::asio::buffer(mReadBuffer), - boost::bind(&RPCServer::Shandle_read, shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred)); - } - else + HTTPRequestAction action = mHTTPRequest.requestDone(false); + if (action == haCLOSE_CONN) { boost::system::error_code ignored_ec; mSocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); } + else + { + boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n", + boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error)); + } } if (e != boost::asio::error::operation_aborted) diff --git a/src/RPCServer.h b/src/RPCServer.h index 8997707d1..7d7695599 100644 --- a/src/RPCServer.h +++ b/src/RPCServer.h @@ -8,8 +8,7 @@ #include "../json/value.h" -#include "HttpRequest.h" -#include "RequestParser.h" +#include "HTTPRequest.h" #include "NewcoinAddress.h" #include "NetworkOPs.h" #include "SerializedLedger.h" @@ -91,11 +90,12 @@ private: NetworkOPs* mNetOps; boost::asio::ip::tcp::socket mSocket; - boost::array mReadBuffer; + + boost::asio::streambuf mLineBuffer; + std::vector mQueryVec; std::string mReplyStr; - HttpRequest mIncomingRequest; - HttpRequestParser mRequestParser; + HTTPRequest mHTTPRequest; enum { GUEST, USER, ADMIN }; int mRole; @@ -106,15 +106,10 @@ private: RPCServer& operator=(const RPCServer&); // no implementation void handle_write(const boost::system::error_code& ec); - static void Shandle_write(pointer This, const boost::system::error_code& ec) - { This->handle_write(ec); } - - void handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred); - static void Shandle_read(pointer This, const boost::system::error_code& ec, std::size_t bytes_transferred) - { This->handle_read(ec, bytes_transferred); } + void handle_read_line(const boost::system::error_code& ec); + void handle_read_req(const boost::system::error_code& ec); std::string handleRequest(const std::string& requestStr); - void sendReply(); Json::Value doCommand(const std::string& command, Json::Value& params); int getParamCount(const Json::Value& params); diff --git a/src/RequestParser.cpp b/src/RequestParser.cpp deleted file mode 100644 index 8384cd0e1..000000000 --- a/src/RequestParser.cpp +++ /dev/null @@ -1,330 +0,0 @@ -#include "RequestParser.h" -#include "HttpRequest.h" - - - -HttpRequestParser::HttpRequestParser() - : state_(method_start) -{ -} - -void HttpRequestParser::reset() -{ - state_ = method_start; -} - -//template -boost::tribool HttpRequestParser::parse(HttpRequest& req, - char* begin, char* end) -{ - while (begin != end) - { - boost::tribool result = consume(req, *begin++); - if (result || !result) - { - std::string temp(begin,end); - req.mBody=temp; - return result; - } - } - boost::tribool result = boost::indeterminate; - return result; -} - -boost::tribool HttpRequestParser::consume(HttpRequest& req, char input) -{ - switch (state_) - { - case method_start: - if (!is_char(input) || is_ctl(input) || is_tspecial(input)) - { - return false; - } - else - { - state_ = method; - req.method.push_back(input); - return boost::indeterminate; - } - case method: - if (input == ' ') - { - state_ = uri; - return boost::indeterminate; - } - else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) - { - return false; - } - else - { - req.method.push_back(input); - return boost::indeterminate; - } - case uri_start: - if (is_ctl(input)) - { - return false; - } - else - { - state_ = uri; - req.uri.push_back(input); - return boost::indeterminate; - } - case uri: - if (input == ' ') - { - state_ = http_version_h; - return boost::indeterminate; - } - else if (is_ctl(input)) - { - return false; - } - else - { - req.uri.push_back(input); - return boost::indeterminate; - } - case http_version_h: - if (input == 'H') - { - state_ = http_version_t_1; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_t_1: - if (input == 'T') - { - state_ = http_version_t_2; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_t_2: - if (input == 'T') - { - state_ = http_version_p; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_p: - if (input == 'P') - { - state_ = http_version_slash; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_slash: - if (input == '/') - { - req.http_version_major = 0; - req.http_version_minor = 0; - state_ = http_version_major_start; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_major_start: - if (is_digit(input)) - { - req.http_version_major = req.http_version_major * 10 + input - '0'; - state_ = http_version_major; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_major: - if (input == '.') - { - state_ = http_version_minor_start; - return boost::indeterminate; - } - else if (is_digit(input)) - { - req.http_version_major = req.http_version_major * 10 + input - '0'; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_minor_start: - if (is_digit(input)) - { - req.http_version_minor = req.http_version_minor * 10 + input - '0'; - state_ = http_version_minor; - return boost::indeterminate; - } - else - { - return false; - } - case http_version_minor: - if (input == '\r') - { - state_ = expecting_newline_1; - return boost::indeterminate; - } - else if (is_digit(input)) - { - req.http_version_minor = req.http_version_minor * 10 + input - '0'; - return boost::indeterminate; - } - else - { - return false; - } - case expecting_newline_1: - if (input == '\n') - { - state_ = header_line_start; - return boost::indeterminate; - } - else - { - return false; - } - case header_line_start: - if (input == '\r') - { - state_ = expecting_newline_3; - return boost::indeterminate; - } - else if (!req.headers.empty() && (input == ' ' || input == '\t')) - { - state_ = header_lws; - return boost::indeterminate; - } - else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) - { - return false; - } - else - { - req.headers.push_back(HttpHeader()); - req.headers.back().name.push_back(input); - state_ = header_name; - return boost::indeterminate; - } - case header_lws: - if (input == '\r') - { - state_ = expecting_newline_2; - return boost::indeterminate; - } - else if (input == ' ' || input == '\t') - { - return boost::indeterminate; - } - else if (is_ctl(input)) - { - return false; - } - else - { - state_ = header_value; - req.headers.back().value.push_back(input); - return boost::indeterminate; - } - case header_name: - if (input == ':') - { - state_ = space_before_header_value; - return boost::indeterminate; - } - else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) - { - return false; - } - else - { - req.headers.back().name.push_back(input); - return boost::indeterminate; - } - case space_before_header_value: - if (input == ' ') - { - state_ = header_value; - return boost::indeterminate; - } - else - { - return false; - } - case header_value: - if (input == '\r') - { - state_ = expecting_newline_2; - return boost::indeterminate; - } - else if (is_ctl(input)) - { - return false; - } - else - { - req.headers.back().value.push_back(input); - return boost::indeterminate; - } - case expecting_newline_2: - if (input == '\n') - { - state_ = header_line_start; - return boost::indeterminate; - } - else - { - return false; - } - case expecting_newline_3: - return (input == '\n'); - default: - return false; - } -} - -bool HttpRequestParser::is_char(int c) -{ - return c >= 0 && c <= 127; -} - -bool HttpRequestParser::is_ctl(int c) -{ - return (c >= 0 && c <= 31) || (c == 127); -} - -bool HttpRequestParser::is_tspecial(int c) -{ - switch (c) - { - case '(': case ')': case '<': case '>': case '@': - case ',': case ';': case ':': case '\\': case '"': - case '/': case '[': case ']': case '?': case '=': - case '{': case '}': case ' ': case '\t': - return true; - default: - return false; - } -} - -bool HttpRequestParser::is_digit(int c) -{ - return c >= '0' && c <= '9'; -} diff --git a/src/RequestParser.h b/src/RequestParser.h deleted file mode 100644 index f0906daa8..000000000 --- a/src/RequestParser.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef HTTP_REQUEST_PARSER_HPP -#define HTTP_REQUEST_PARSER_HPP - -#include -#include - - - -struct HttpRequest; - -/// Parser for incoming requests. -class HttpRequestParser -{ - /// Handle the next character of input. - boost::tribool consume(HttpRequest& req, char input); - - /// Check if a byte is an HTTP character. - static bool is_char(int c); - - /// Check if a byte is an HTTP control character. - static bool is_ctl(int c); - - /// Check if a byte is defined as an HTTP special character. - static bool is_tspecial(int c); - - /// Check if a byte is a digit. - static bool is_digit(int c); - - /// The current state of the parser. - enum state - { - method_start, - method, - uri_start, - uri, - http_version_h, - http_version_t_1, - http_version_t_2, - http_version_p, - http_version_slash, - http_version_major_start, - http_version_major, - http_version_minor_start, - http_version_minor, - expecting_newline_1, - header_line_start, - header_lws, - header_name, - space_before_header_value, - header_value, - expecting_newline_2, - expecting_newline_3 - } state_; -public: - /// Construct ready to parse the request method. - HttpRequestParser(); - - /// Reset to initial parser state. - void reset(); - - /// Parse some data. The tribool return value is true when a complete request - /// has been parsed, false if the data is invalid, indeterminate when more - /// data is required. The InputIterator return value indicates how much of the - /// input has been consumed. - //template - boost::tribool parse(HttpRequest& req, char*, char*); - - -}; - - -#endif // HTTP_REQUEST_PARSER_HPP diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index acdd76a78..f3726ee53 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -577,7 +577,7 @@ void WSConnection::doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvR void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest) { NetworkOPs& noNetwork = mNetwork; - uint256 uLedger = jvRequest.isMember("ledger") ? uint256(jvRequest["ledger"].asString()) : 0; + uint256 uLedger = jvRequest.isMember("ledger_closed") ? uint256(jvRequest["ledger_closed"].asString()) : 0; uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; Ledger::pointer lpLedger; diff --git a/test/remote-test.js b/test/remote-test.js index 34eceb1aa..96740236a 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -80,7 +80,7 @@ buster.testCase("Remote functions", { alpha .request_ledger_entry('account_root') - .ledger(r.ledger_closed) + .ledger_closed(r.ledger_closed) .account_root("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh") .on('success', function (r) { // console.log("account_root: %s", JSON.stringify(r)); @@ -110,7 +110,7 @@ buster.testCase("Remote functions", { alpha .request_ledger_entry('account_root') - .ledger(r.ledger_closed) + .ledger_closed(r.ledger_closed) .account_root("zHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh") .on('success', function (r) { // console.log("account_root: %s", JSON.stringify(r)); @@ -141,7 +141,7 @@ buster.testCase("Remote functions", { alpha .request_ledger_entry('account_root') - .ledger(r.ledger_closed) + .ledger_closed(r.ledger_closed) .account_root(config.accounts.alice.account) .on('success', function (r) { // console.log("account_root: %s", JSON.stringify(r)); @@ -171,7 +171,7 @@ buster.testCase("Remote functions", { alpha .request_ledger_entry('index') - .ledger(r.ledger_closed) + .ledger_closed(r.ledger_closed) .account_root(config.accounts.alice.account) .index("2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8") .on('success', function (r) {