Skip to content

Commit

Permalink
Merge pull request #4288
Browse files Browse the repository at this point in the history
ed5769f Move AcceptedConnection class to rpcserver.h. (Jeff Garzik)
854d013 RPC code movement: separate out JSON-RPC execution logic from HTTP server logic (Jeff Garzik)
c912e22 RPC cleanup: Improve HTTP server replies (Jeff Garzik)
  • Loading branch information
laanwj committed Jun 27, 2014
2 parents 19168c3 + ed5769f commit d77f761
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 71 deletions.
17 changes: 14 additions & 3 deletions src/rpcprotocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ static string rfc1123Time()
return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime());
}

string HTTPReply(int nStatus, const string& strMsg, bool keepalive)
string HTTPReply(int nStatus, const string& strMsg, bool keepalive,
bool headersOnly, const char *contentType)
{
if (nStatus == HTTP_UNAUTHORIZED)
return strprintf("HTTP/1.0 401 Authorization Required\r\n"
Expand All @@ -73,19 +74,27 @@ string HTTPReply(int nStatus, const string& strMsg, bool keepalive)
"</HEAD>\r\n"
"<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
"</HTML>\r\n", rfc1123Time(), FormatFullVersion());

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 = "";

bool useInternalContent = false;
if (nStatus != HTTP_OK) {
contentType = "text/plain";
useInternalContent = true;
}

return strprintf(
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Connection: %s\r\n"
"Content-Length: %u\r\n"
"Content-Type: application/json\r\n"
"Content-Type: %s\r\n"
"Server: bitcoin-json-rpc/%s\r\n"
"\r\n"
"%s",
Expand All @@ -94,8 +103,10 @@ string HTTPReply(int nStatus, const string& strMsg, bool keepalive)
rfc1123Time(),
keepalive ? "keep-alive" : "close",
strMsg.size(),
contentType,
FormatFullVersion(),
strMsg);
(headersOnly ? "" :
(useInternalContent ? cStatus : strMsg.c_str())));
}

bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto,
Expand Down
4 changes: 3 additions & 1 deletion src/rpcprotocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidi
};

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);
std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive,
bool headerOnly = false,
const char *contentType = "application/json");
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);
Expand Down
139 changes: 72 additions & 67 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,16 +393,6 @@ bool ClientAllowed(const boost::asio::ip::address& address)
return false;
}

class AcceptedConnection
{
public:
virtual ~AcceptedConnection() {}

virtual std::iostream& stream() = 0;
virtual std::string peer_address_to_string() const = 0;
virtual void close() = 0;
};

template <typename Protocol>
class AcceptedConnectionImpl : public AcceptedConnection
{
Expand Down Expand Up @@ -819,6 +809,71 @@ static string JSONRPCExecBatch(const Array& vReq)
return write_string(Value(ret), false) + "\n";
}

static bool HTTPReq_JSONRPC(AcceptedConnection *conn,
string& strRequest,
map<string, string>& mapHeaders,
bool fRun)
{
// Check authorization
if (mapHeaders.count("authorization") == 0)
{
conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush;
return false;
}

if (!HTTPAuthorized(mapHeaders))
{
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string());
/* Deter brute-forcing short passwords.
If this results in a DoS the user really
shouldn't have their RPC port exposed. */
if (mapArgs["-rpcpassword"].size() < 20)
MilliSleep(250);

conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush;
return false;
}

JSONRequest jreq;
try
{
// Parse request
Value valRequest;
if (!read_string(strRequest, valRequest))
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");

string strReply;

// singleton request
if (valRequest.type() == obj_type) {
jreq.parse(valRequest);

Value result = tableRPC.execute(jreq.strMethod, jreq.params);

// Send reply
strReply = JSONRPCReply(result, Value::null, jreq.id);

// array of requests
} else if (valRequest.type() == array_type)
strReply = JSONRPCExecBatch(valRequest.get_array());
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");

conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush;
}
catch (Object& objError)
{
ErrorReply(conn->stream(), objError, jreq.id);
return false;
}
catch (std::exception& e)
{
ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
return false;
}
return true;
}

void ServiceConnection(AcceptedConnection *conn)
{
bool fRun = true;
Expand All @@ -835,67 +890,17 @@ void ServiceConnection(AcceptedConnection *conn)
// Read HTTP message headers and body
ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto);

if (strURI != "/") {
conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush;
break;
}

// Check authorization
if (mapHeaders.count("authorization") == 0)
{
conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush;
break;
}
if (!HTTPAuthorized(mapHeaders))
{
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string());
/* Deter brute-forcing short passwords.
If this results in a DoS the user really
shouldn't have their RPC port exposed. */
if (mapArgs["-rpcpassword"].size() < 20)
MilliSleep(250);

conn->stream() << HTTPReply(HTTP_UNAUTHORIZED, "", false) << std::flush;
break;
}
// HTTP Keep-Alive is false; close connection immediately
if (mapHeaders["connection"] == "close")
fRun = false;

JSONRequest jreq;
try
{
// Parse request
Value valRequest;
if (!read_string(strRequest, valRequest))
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");

string strReply;

// singleton request
if (valRequest.type() == obj_type) {
jreq.parse(valRequest);

Value result = tableRPC.execute(jreq.strMethod, jreq.params);

// Send reply
strReply = JSONRPCReply(result, Value::null, jreq.id);

// array of requests
} else if (valRequest.type() == array_type)
strReply = JSONRPCExecBatch(valRequest.get_array());
else
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");

conn->stream() << HTTPReply(HTTP_OK, strReply, fRun) << std::flush;
}
catch (Object& objError)
{
ErrorReply(conn->stream(), objError, jreq.id);
break;
if (strURI == "/") {
if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun))
break;
}
catch (std::exception& e)
{
ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);

else {
conn->stream() << HTTPReply(HTTP_NOT_FOUND, "", false) << std::flush;
break;
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
class CBlockIndex;
class CNetAddr;

class AcceptedConnection
{
public:
virtual ~AcceptedConnection() {}

virtual std::iostream& stream() = 0;
virtual std::string peer_address_to_string() const = 0;
virtual void close() = 0;
};

/* Start RPC threads */
void StartRPCThreads();
/* Alternative to StartRPCThreads for the GUI, when no server is
Expand Down

0 comments on commit d77f761

Please sign in to comment.