Send output incrementally in ServerHandlerImp.

This commit is contained in:
Tom Ritchford
2014-12-02 15:26:42 -05:00
committed by Vinnie Falco
parent 167f4666e2
commit fc9a23d6d4
6 changed files with 144 additions and 185 deletions

View File

@@ -1762,6 +1762,8 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\ledger\LedgerTiming.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\ledger\LedgerToJson.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\OrderBookDB.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>

View File

@@ -2628,6 +2628,9 @@
<ClInclude Include="..\..\src\ripple\app\ledger\LedgerTiming.h">
<Filter>ripple\app\ledger</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\ledger\LedgerToJson.h">
<Filter>ripple\app\ledger</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\ledger\OrderBookDB.cpp">
<Filter>ripple\app\ledger</Filter>
</ClCompile>

View File

@@ -29,16 +29,6 @@ namespace ripple {
unsigned int const gMaxHTTPHeaderSize = 0x02000000;
Json::Value JSONRPCError (int code, std::string const& message)
{
Json::Value error (Json::objectValue);
error[jss::code] = Json::Value (code);
error[jss::message] = Json::Value (message);
return error;
}
std::string getHTTPHeaderTimestamp ()
{
// CHECKME This is probably called often enough that optimizing it makes
@@ -54,160 +44,74 @@ std::string getHTTPHeaderTimestamp ()
return std::string (buffer);
}
std::string HTTPReply (int nStatus, std::string const& strMsg)
void HTTPReply (int nStatus, std::string const& content, RPC::Output output)
{
if (ShouldLog (lsTRACE, RPC))
{
WriteLog (lsTRACE, RPC) << "HTTP Reply " << nStatus << " " << strMsg;
WriteLog (lsTRACE, RPC) << "HTTP Reply " << nStatus << " " << content;
}
std::string ret;
if (nStatus == 401)
{
ret.reserve (512);
ret.append ("HTTP/1.0 401 Authorization Required\r\n");
ret.append (getHTTPHeaderTimestamp ());
output ("HTTP/1.0 401 Authorization Required\r\n");
output (getHTTPHeaderTimestamp ());
// CHECKME this returns a different version than the replies below. Is
// this by design or an accident or should it be using
// BuildInfo::getFullVersionString () as well?
ret.append ("Server: " + systemName () + "-json-rpc/v1");
ret.append ("\r\n");
output ("Server: " + systemName () + "-json-rpc/v1");
output ("\r\n");
// Be careful in modifying this! If you change the contents you MUST
// update the Content-Length header as well to indicate the correct
// size of the data.
ret.append ("WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
output ("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"
"<!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"
"<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");
return ret;
return;
}
ret.reserve(256 + strMsg.length());
switch (nStatus)
{
case 200: ret.append ("HTTP/1.1 200 OK\r\n"); break;
case 400: ret.append ("HTTP/1.1 400 Bad Request\r\n"); break;
case 403: ret.append ("HTTP/1.1 403 Forbidden\r\n"); break;
case 404: ret.append ("HTTP/1.1 404 Not Found\r\n"); break;
case 500: ret.append ("HTTP/1.1 500 Internal Server Error\r\n"); break;
case 200: output ("HTTP/1.1 200 OK\r\n"); break;
case 400: output ("HTTP/1.1 400 Bad Request\r\n"); break;
case 403: output ("HTTP/1.1 403 Forbidden\r\n"); break;
case 404: output ("HTTP/1.1 404 Not Found\r\n"); break;
case 500: output ("HTTP/1.1 500 Internal Server Error\r\n"); break;
}
ret.append (getHTTPHeaderTimestamp ());
output (getHTTPHeaderTimestamp ());
ret.append ("Connection: Keep-Alive\r\n");
output ("Connection: Keep-Alive\r\n"
"Content-Length: ");
// VFALCO TODO Determine if/when this header should be added
//if (getConfig ().RPC_ALLOW_REMOTE)
// ret.append ("Access-Control-Allow-Origin: *\r\n");
// output ("Access-Control-Allow-Origin: *\r\n");
ret.append ("Content-Length: ");
ret.append (std::to_string(strMsg.size () + 2));
ret.append ("\r\n");
output (std::to_string(content.size () + 2));
output ("\r\n"
"Content-Type: application/json; charset=UTF-8\r\n");
ret.append ("Content-Type: application/json; charset=UTF-8\r\n");
ret.append ("Server: " + systemName () + "-json-rpc/");
ret.append (BuildInfo::getFullVersionString ());
ret.append ("\r\n");
ret.append ("\r\n");
ret.append (strMsg);
ret.append ("\r\n");
return ret;
}
int ReadHTTPStatus (std::basic_istream<char>& stream)
{
std::string str;
getline (stream, str);
std::vector<std::string> vWords;
boost::split (vWords, str, boost::is_any_of (" "));
if (vWords.size () < 2)
return 500;
return atoi (vWords[1].c_str ());
}
int ReadHTTPHeader (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet)
{
int nLen = 0;
for (;;)
{
std::string str;
std::getline (stream, str);
if (str.empty () || str == "\r")
break;
std::string::size_type nColon = str.find (":");
if (nColon != std::string::npos)
{
std::string strHeader = str.substr (0, nColon);
boost::trim (strHeader);
boost::to_lower (strHeader);
std::string strValue = str.substr (nColon + 1);
boost::trim (strValue);
mapHeadersRet[strHeader] = strValue;
if (strHeader == "content-length")
nLen = atoi (strValue.c_str ());
}
}
return nLen;
}
int ReadHTTP (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet)
{
mapHeadersRet.clear ();
strMessageRet = "";
// Read status
int nStatus = ReadHTTPStatus (stream);
// Read header
int nLen = ReadHTTPHeader (stream, mapHeadersRet);
if (nLen < 0 || nLen > gMaxHTTPHeaderSize)
return 500;
// Read message
if (nLen > 0)
{
std::vector<char> vch (nLen);
stream.read (&vch[0], nLen);
strMessageRet = std::string (vch.begin (), vch.end ());
}
return nStatus;
}
std::string JSONRPCReply (Json::Value const& result, Json::Value const& error, Json::Value const& id)
{
Json::Value reply (Json::objectValue);
reply[jss::result] = result;
//reply["error"]=error;
//reply["id"]=id;
return to_string (reply) + "\n";
output ("Server: " + systemName () + "-json-rpc/");
output (BuildInfo::getFullVersionString ());
output ("\r\n"
"\r\n");
output (content);
output ("\r\n");
}
} // ripple

View File

@@ -21,22 +21,11 @@
#define RIPPLE_SERVER_JSONRPCUTIL_H_INCLUDED
#include <ripple/json/json_value.h>
#include <ripple/rpc/Output.h>
namespace ripple {
// VFALCO These functions are all deprecated they are inefficient and have poor signatures.
extern std::string JSONRPCReply (Json::Value const& result, Json::Value const& error, Json::Value const& id);
extern Json::Value JSONRPCError (int code, std::string const& message);
// VFALCO This needs to be rewritten to use beast::http::message
extern std::string HTTPReply (int nStatus, std::string const& strMsg);
// VFALCO NOTE This one looks like it does some sort of stream i/o
extern int ReadHTTP (std::basic_istream<char>& stream,
std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet);
void HTTPReply (int nStatus, std::string const& strMsg, RPC::Output);
} // ripple

View File

@@ -28,10 +28,12 @@
#include <ripple/overlay/Overlay.h>
#include <ripple/resource/Manager.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Coroutine.h>
#include <beast/crypto/base64.h>
#include <beast/cxx14/algorithm.h> // <algorithm>
#include <beast/http/rfc2616.h>
#include <boost/algorithm/string.hpp>
#include <boost/type_traits.hpp>
#include <boost/optional.hpp>
#include <boost/regex.hpp>
#include <algorithm>
@@ -146,6 +148,36 @@ ServerHandlerImp::onHandoff (HTTP::Session& session,
return Handoff{};
}
static inline
RPC::Output makeOutput (HTTP::Session& session)
{
return [&](boost::string_ref const& b)
{
session.write (b.data(), b.size());
};
}
namespace {
void runCoroutine (RPC::Coroutine coroutine, JobQueue& jobQueue)
{
if (!coroutine)
return;
coroutine();
if (!coroutine)
return;
// Reschedule the job on the job queue.
jobQueue.addJob (
jtCLIENT, "RPC-Coroutine",
[coroutine, &jobQueue] (Job&)
{
runCoroutine (coroutine, jobQueue);
});
}
} // namespace
void
ServerHandlerImp::onRequest (HTTP::Session& session)
{
@@ -153,14 +185,17 @@ ServerHandlerImp::onRequest (HTTP::Session& session)
if (! authorized (session.port(),
build_map(session.request().headers)))
{
session.write (HTTPReply (403, "Forbidden"));
HTTPReply (403, "Forbidden", makeOutput (session));
session.close (true);
return;
}
m_jobQueue.addJob (jtCLIENT, "RPC-Client", std::bind (
&ServerHandlerImp::processSession, this, std::placeholders::_1,
session.detach()));
auto detach = session.detach();
RPC::Coroutine::YieldFunction yieldFunction =
[this, detach] (Yield const& y) { processSession (detach, y); };
RPC::Coroutine coroutine (yieldFunction);
runCoroutine (std::move(coroutine), m_jobQueue);
}
void
@@ -177,36 +212,44 @@ ServerHandlerImp::onStopped (HTTP::Server&)
//------------------------------------------------------------------------------
// ServerHandlerImp will yield after emitting serverOutputChunkSize bytes.
// If this value is 0, it means "yield after each output"
// A negative value means "never yield"
// TODO(tom): negotiate a spot for this in Configs.
const int serverOutputChunkSize = -1;
// Dispatched on the job queue
void
ServerHandlerImp::processSession (Job& job,
std::shared_ptr<HTTP::Session> const& session)
ServerHandlerImp::processSession (
std::shared_ptr<HTTP::Session> const& session, Yield const& yield)
{
session->write (processRequest (session->port(),
to_string(session->body()), session->remoteAddress().at_port(0)));
auto output = makeOutput (*session);
if (serverOutputChunkSize >= 0)
{
output = RPC::chunkedYieldingOutput (
output, yield, serverOutputChunkSize);
}
processRequest (
session->port(),
to_string (session->body()),
session->remoteAddress().at_port (0),
output,
yield);
if (session->request().keep_alive())
{
session->complete();
}
else
{
session->close (true);
}
}
std::string
ServerHandlerImp::createResponse (
int statusCode,
std::string const& description)
{
return HTTPReply (statusCode, description);
}
// VFALCO ARGH! returning a single std::string for the entire response?
std::string
ServerHandlerImp::processRequest (HTTP::Port const& port,
std::string const& request, beast::IP::Endpoint const& remoteIPAddress)
void
ServerHandlerImp::processRequest (
HTTP::Port const& port,
std::string const& request,
beast::IP::Endpoint const& remoteIPAddress,
Output output,
Yield yield)
{
Json::Value jsonRPC;
{
@@ -216,7 +259,8 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
jsonRPC.isNull () ||
! jsonRPC.isObject ())
{
return createResponse (400, "Unable to parse request");
HTTPReply (400, "Unable to parse request", output);
return;
}
}
@@ -239,25 +283,36 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
usage = m_resourceManager.newInboundEndpoint(remoteIPAddress);
if (usage.disconnect ())
return createResponse (503, "Server is overloaded");
{
HTTPReply (503, "Server is overloaded", output);
return;
}
// Parse id now so errors from here on will have the id
//
// VFALCO NOTE Except that "id" isn't included in the following errors.
//
Json::Value const id = jsonRPC ["id"];
Json::Value const method = jsonRPC ["method"];
if (method.isNull ())
return createResponse (400, "Null method");
{
HTTPReply (400, "Null method", output);
return;
}
if (! method.isString ())
return createResponse (400, "method is not string");
{
HTTPReply (400, "method is not string", output);
return;
}
std::string strMethod = method.asString ();
if (strMethod.empty())
return createResponse (400, "method is empty");
{
HTTPReply (400, "method is empty", output);
return;
}
// Parse params
Json::Value params = jsonRPC ["params"];
@@ -266,7 +321,10 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
params = Json::Value (Json::arrayValue);
else if (!params.isArray ())
return HTTPReply (400, "params unparseable");
{
HTTPReply (400, "params unparseable", output);
return;
}
// VFALCO TODO Shouldn't we handle this earlier?
//
@@ -275,25 +333,28 @@ ServerHandlerImp::processRequest (HTTP::Port const& port,
// VFALCO TODO Needs implementing
// FIXME Needs implementing
// XXX This needs rate limiting to prevent brute forcing password.
return HTTPReply (403, "Forbidden");
HTTPReply (403, "Forbidden", output);
return;
}
std::string response;
RPCHandler rpcHandler (m_networkOPs);
Resource::Charge loadType = Resource::feeReferenceRPC;
m_journal.debug << "Query: " << strMethod << params;
Json::Value const result (rpcHandler.doRpcCommand (
strMethod, params, role, loadType));
auto result = rpcHandler.doRpcCommand (
strMethod, params, role, loadType, yield);
m_journal.debug << "Reply: " << result;
usage.charge (loadType);
response = JSONRPCReply (result, Json::Value (), id);
Json::Value reply (Json::objectValue);
reply[jss::result] = std::move (result);
auto response = to_string (reply);
response += '\n';
return createResponse (200, response);
HTTPReply (200, response, output);
}
//------------------------------------------------------------------------------

View File

@@ -23,6 +23,7 @@
#include <ripple/core/Job.h>
#include <ripple/server/ServerHandler.h>
#include <ripple/server/Session.h>
#include <ripple/rpc/Output.h>
#include <ripple/rpc/RPCHandler.h>
namespace ripple {
@@ -49,6 +50,9 @@ public:
~ServerHandlerImp();
private:
using Output = RPC::Output;
using Yield = RPC::Yield;
void
setup (Setup const& setup, beast::Journal journal) override;
@@ -104,15 +108,11 @@ private:
//--------------------------------------------------------------------------
void
processSession (Job& job,
std::shared_ptr<HTTP::Session> const& session);
processSession (std::shared_ptr<HTTP::Session> const&, Yield const&);
std::string
createResponse (int statusCode, std::string const& description);
std::string
void
processRequest (HTTP::Port const& port, std::string const& request,
beast::IP::Endpoint const& remoteIPAddress);
beast::IP::Endpoint const& remoteIPAddress, Output, Yield);
//
// PropertyStream