Refactor Server (RIPD-1120):

* Make Handler a template argument
This commit is contained in:
Vinnie Falco
2016-05-20 08:44:13 -04:00
parent 80a9a2bf5d
commit 289c8c9f09
53 changed files with 997 additions and 1176 deletions

View File

@@ -35,7 +35,7 @@
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/resource/Fees.h>
#include <ripple/server/Role.h>
#include <ripple/rpc/Role.h>
#include <ripple/resource/Fees.h>
namespace ripple {

View File

@@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/rpc/Role.h>
namespace ripple {
bool
passwordUnrequiredOrSentCorrect (Port const& port,
Json::Value const& params) {
assert(! port.admin_ip.empty ());
bool const passwordRequired = (!port.admin_user.empty() ||
!port.admin_password.empty());
return !passwordRequired ||
((params["admin_password"].isString() &&
params["admin_password"].asString() == port.admin_password) &&
(params["admin_user"].isString() &&
params["admin_user"].asString() == port.admin_user));
}
bool
ipAllowed (beast::IP::Address const& remoteIp,
std::vector<beast::IP::Address> const& adminIp)
{
return std::find_if (adminIp.begin (), adminIp.end (),
[&remoteIp](beast::IP::Address const& ip) { return ip.is_any () ||
ip == remoteIp; }) != adminIp.end ();
}
bool
isAdmin (Port const& port, Json::Value const& params,
beast::IP::Address const& remoteIp)
{
return ipAllowed (remoteIp, port.admin_ip) &&
passwordUnrequiredOrSentCorrect (port, params);
}
Role
requestRole (Role const& required, Port const& port,
Json::Value const& params, beast::IP::Endpoint const& remoteIp,
std::string const& user)
{
if (isAdmin(port, params, remoteIp.address()))
return Role::ADMIN;
if (required == Role::ADMIN)
return Role::FORBID;
if (isIdentified(port, remoteIp.address(), user))
return Role::IDENTIFIED;
return Role::GUEST;
}
/**
* ADMIN and IDENTIFIED roles shall have unlimited resources.
*/
bool
isUnlimited (Role const& required, Port const& port,
Json::Value const&params, beast::IP::Endpoint const& remoteIp,
std::string const& user)
{
Role role = requestRole(required, port, params, remoteIp, user);
if (role == Role::ADMIN || role == Role::IDENTIFIED)
return true;
else
return false;
}
bool
isUnlimited (Role const& role)
{
return role == Role::ADMIN || role == Role::IDENTIFIED;
}
Resource::Consumer
requestInboundEndpoint (Resource::Manager& manager,
beast::IP::Endpoint const& remoteAddress,
Port const& port, std::string const& user)
{
if (isUnlimited (Role::GUEST, port, Json::Value(), remoteAddress, user))
return manager.newUnlimitedEndpoint (to_string (remoteAddress));
return manager.newInboundEndpoint(remoteAddress);
}
bool
isIdentified (Port const& port, beast::IP::Address const& remoteIp,
std::string const& user)
{
return ! user.empty() && ipAllowed (remoteIp, port.secure_gateway_ip);
}
}

View File

@@ -0,0 +1,874 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/beast/rfc2616.h>
#include <ripple/beast/net/IPAddressConversion.h>
#include <ripple/json/json_reader.h>
#include <ripple/rpc/json_body.h>
#include <ripple/rpc/ServerHandler.h>
#include <ripple/server/Server.h>
#include <ripple/server/impl/JSONRPCUtil.h>
#include <ripple/rpc/impl/ServerHandlerImp.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/make_SSLContext.h>
#include <ripple/core/JobQueue.h>
#include <ripple/json/to_string.h>
#include <ripple/net/RPCErr.h>
#include <ripple/overlay/Overlay.h>
#include <ripple/resource/ResourceManager.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/RPCHandler.h>
#include <beast/core/detail/base64.hpp>
#include <beast/http/headers.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/type_traits.hpp>
#include <boost/optional.hpp>
#include <boost/regex.hpp>
#include <algorithm>
#include <stdexcept>
namespace ripple {
ServerHandlerImp::ServerHandlerImp (Application& app, Stoppable& parent,
boost::asio::io_service& io_service, JobQueue& jobQueue,
NetworkOPs& networkOPs, Resource::Manager& resourceManager,
CollectorManager& cm)
: Stoppable("ServerHandler", parent)
, app_ (app)
, m_resourceManager (resourceManager)
, m_journal (app_.journal("Server"))
, m_networkOPs (networkOPs)
, m_server (make_Server(
*this, io_service, app_.journal("Server")))
, m_jobQueue (jobQueue)
{
auto const& group (cm.group ("rpc"));
rpc_requests_ = group->make_counter ("requests");
rpc_size_ = group->make_event ("size");
rpc_time_ = group->make_event ("time");
}
ServerHandlerImp::~ServerHandlerImp()
{
m_server = nullptr;
}
void
ServerHandlerImp::setup (Setup const& setup, beast::Journal journal)
{
setup_ = setup;
m_server->ports (setup.ports);
}
//------------------------------------------------------------------------------
void
ServerHandlerImp::onStop()
{
m_server->close();
}
//------------------------------------------------------------------------------
bool
ServerHandlerImp::onAccept (Session& session,
boost::asio::ip::tcp::endpoint endpoint)
{
std::lock_guard<std::mutex> l(countlock_);
auto const c = ++count_[session.port()];
if (session.port().limit && c >= session.port().limit)
{
JLOG (m_journal.trace()) <<
session.port().name << " is full; dropping " <<
endpoint;
return false;
}
return true;
}
auto
ServerHandlerImp::onHandoff (Session& session,
std::unique_ptr <beast::asio::ssl_bundle>&& bundle,
http_request_type&& request,
boost::asio::ip::tcp::endpoint remote_address) ->
Handoff
{
if(isWebsocketUpgrade(request))
{
Handoff handoff;
if(session.port().protocol.count("wss2") > 0)
{
auto const ws = session.websocketUpgrade();
auto is = std::make_shared<WSInfoSub>(m_networkOPs, ws);
is->getConsumer() = requestInboundEndpoint(
m_resourceManager,
beast::IPAddressConversion::from_asio(remote_address),
session.port(), is->user());
ws->appDefined = std::move(is);
ws->run();
handoff.moved = true;
return handoff;
}
if(session.port().protocol.count("wss") > 0)
return handoff; // Pass to websocket
}
if(session.port().protocol.count("peer") > 0)
{
return app_.overlay().onHandoff(std::move(bundle),
std::move(request), remote_address);
}
// Pass to legacy onRequest
return {};
}
auto
ServerHandlerImp::onHandoff (Session& session,
boost::asio::ip::tcp::socket&& socket,
http_request_type&& request,
boost::asio::ip::tcp::endpoint remote_address) ->
Handoff
{
Handoff handoff;
if(session.port().protocol.count("ws2") > 0 &&
isWebsocketUpgrade (request))
{
auto const ws = session.websocketUpgrade();
auto is = std::make_shared<WSInfoSub>(m_networkOPs, ws);
is->getConsumer() = requestInboundEndpoint(
m_resourceManager,
beast::IPAddressConversion::from_asio(remote_address),
session.port(), is->user());
ws->appDefined = std::move(is);
ws->run();
handoff.moved = true;
}
// Otherwise pass to legacy onRequest or websocket
return handoff;
}
static inline
Json::Output makeOutput (Session& session)
{
return [&](boost::string_ref const& b)
{
session.write (b.data(), b.size());
};
}
// HACK!
static
std::map<std::string, std::string>
build_map(beast::http::headers const& h)
{
std::map <std::string, std::string> c;
for (auto const& e : h)
{
auto key (e.first);
// TODO Replace with safe C++14 version
std::transform (key.begin(), key.end(), key.begin(), ::tolower);
c [key] = e.second;
}
return c;
}
template<class ConstBufferSequence>
static
std::string
buffers_to_string(ConstBufferSequence const& bs)
{
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
std::string s;
s.reserve(buffer_size(bs));
for(auto const& b : bs)
s.append(buffer_cast<char const*>(b),
buffer_size(b));
return s;
}
void
ServerHandlerImp::onRequest (Session& session)
{
// Make sure RPC is enabled on the port
if (session.port().protocol.count("http") == 0 &&
session.port().protocol.count("https") == 0)
{
HTTPReply (403, "Forbidden", makeOutput (session), app_.journal ("RPC"));
session.close (true);
return;
}
// Check user/password authorization
if (! authorized (
session.port(), build_map(session.request().headers)))
{
HTTPReply (403, "Forbidden", makeOutput (session), app_.journal ("RPC"));
session.close (true);
return;
}
m_jobQueue.postCoro(jtCLIENT, "RPC-Client",
[this, detach = session.detach()](std::shared_ptr<JobCoro> jc)
{
processSession(detach, jc);
});
}
void
ServerHandlerImp::onWSMessage(
std::shared_ptr<WSSession> session,
std::vector<boost::asio::const_buffer> const& buffers)
{
Json::Value jv;
auto const size = boost::asio::buffer_size(buffers);
if (size > RPC::Tuning::maxRequestSize ||
! Json::Reader{}.parse(jv, buffers) ||
! jv ||
! jv.isObject())
{
Json::Value jvResult(Json::objectValue);
jvResult[jss::type] = jss::error;
jvResult[jss::error] = "jsonInvalid";
jvResult[jss::value] = buffers_to_string(buffers);
beast::streambuf sb;
Json::stream(jvResult,
[&sb](auto const p, auto const n)
{
sb.commit(boost::asio::buffer_copy(
sb.prepare(n), boost::asio::buffer(p, n)));
});
JLOG(m_journal.trace())
<< "Websocket sending '" << jvResult << "'";
session->send(std::make_shared<
StreambufWSMsg<decltype(sb)>>(std::move(sb)));
session->complete();
return;
}
JLOG(m_journal.trace())
<< "Websocket received '" << jv << "'";
m_jobQueue.postCoro(jtCLIENT, "WS-Client",
[this, session = std::move(session),
jv = std::move(jv)](auto const& jc)
{
auto const jr =
this->processSession(session, jc, jv);
auto const s = to_string(jr);
auto const n = s.length();
beast::streambuf sb(n);
sb.commit(boost::asio::buffer_copy(
sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
session->send(std::make_shared<
StreambufWSMsg<decltype(sb)>>(std::move(sb)));
session->complete();
});
}
void
ServerHandlerImp::onClose (Session& session,
boost::system::error_code const&)
{
std::lock_guard<std::mutex> l(countlock_);
--count_[session.port()];
}
void
ServerHandlerImp::onStopped (Server&)
{
stopped();
}
//------------------------------------------------------------------------------
Json::Value
ServerHandlerImp::processSession(
std::shared_ptr<WSSession> const& session,
std::shared_ptr<JobCoro> const& coro,
Json::Value const& jv)
{
auto is = std::static_pointer_cast<WSInfoSub> (session->appDefined);
if (is->getConsumer().disconnect())
{
session->close();
return rpcError(rpcSLOW_DOWN);
}
// Requests without "command" are invalid.
Json::Value jr(Json::objectValue);
if (! jv.isMember (jss::command))
{
jr[jss::type] = jss::response;
jr[jss::status] = jss::error;
jr[jss::error] = jss::missingCommand;
jr[jss::request] = jv;
if (jv.isMember (jss::id))
jr[jss::id] = jv[jss::id];
is->getConsumer().charge(Resource::feeInvalidRPC);
return jr;
}
Resource::Charge loadType = Resource::feeReferenceRPC;
auto required = RPC::roleRequired(jv[jss::command].asString());
auto role = requestRole(
required,
session->port(),
jv,
beast::IP::from_asio(session->remote_endpoint().address()),
is->user());
if (Role::FORBID == role)
{
jr[jss::result] = rpcError (rpcFORBIDDEN);
}
else
{
RPC::Context context{
app_.journal("RPCHandler"),
jv,
app_,
loadType,
app_.getOPs(),
app_.getLedgerMaster(),
is->getConsumer(),
role,
coro,
is,
{is->user(), is->forwarded_for()}
};
RPC::doCommand(context, jr[jss::result]);
}
is->getConsumer().charge(loadType);
if (is->getConsumer().warn())
jr[jss::warning] = jss::load;
// Currently we will simply unwrap errors returned by the RPC
// API, in the future maybe we can make the responses
// consistent.
//
// Regularize result. This is duplicate code.
if (jr[jss::result].isMember(jss::error))
{
jr = jr[jss::result];
jr[jss::status] = jss::error;
jr[jss::request] = jv;
}
else
{
jr[jss::status] = jss::success;
// For testing resource limits on this connection.
if (is->getConsumer().isUnlimited() &&
jv[jss::command].asString() == "ping")
jr[jss::unlimited] = true;
}
if (jv.isMember(jss::id))
jr[jss::id] = jv[jss::id];
jr[jss::type] = jss::response;
return jr;
}
// Run as a coroutine.
void
ServerHandlerImp::processSession (std::shared_ptr<Session> const& session,
std::shared_ptr<JobCoro> jobCoro)
{
processRequest (
session->port(), buffers_to_string(
session->request().body.data()),
session->remoteAddress().at_port (0),
makeOutput (*session), jobCoro,
[&]
{
auto const iter =
session->request().headers.find(
"X-Forwarded-For");
if(iter != session->request().headers.end())
return iter->second;
return std::string{};
}(),
[&]
{
auto const iter =
session->request().headers.find(
"X-User");
if(iter != session->request().headers.end())
return iter->second;
return std::string{};
}());
if(is_keep_alive(session->request()))
session->complete();
else
session->close (true);
}
void
ServerHandlerImp::processRequest (Port const& port,
std::string const& request, beast::IP::Endpoint const& remoteIPAddress,
Output&& output, std::shared_ptr<JobCoro> jobCoro,
std::string forwardedFor, std::string user)
{
auto rpcJ = app_.journal ("RPC");
Json::Value jsonRPC;
{
Json::Reader reader;
if ((request.size () > RPC::Tuning::maxRequestSize) ||
! reader.parse (request, jsonRPC) ||
! jsonRPC ||
! jsonRPC.isObject ())
{
HTTPReply (400, "Unable to parse request", output, rpcJ);
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) {
HTTPReply (400, "Null method", output, rpcJ);
return;
}
if (!method.isString ()) {
HTTPReply (400, "method is not string", output, rpcJ);
return;
}
/* ---------------------------------------------------------------------- */
auto role = Role::FORBID;
auto required = RPC::roleRequired(id.asString());
if (jsonRPC.isMember("params") &&
jsonRPC["params"].isArray() &&
jsonRPC["params"].size() > 0 &&
jsonRPC["params"][Json::UInt(0)].isObject())
{
role = requestRole(required, port, jsonRPC["params"][Json::UInt(0)],
remoteIPAddress, user);
}
else
{
role = requestRole(required, port, Json::objectValue,
remoteIPAddress, user);
}
/**
* Clear header-assigned values if not positively identified from a
* secure_gateway.
*/
if (role != Role::IDENTIFIED)
{
forwardedFor.clear();
user.clear();
}
Resource::Consumer usage;
if (isUnlimited(role))
{
usage = m_resourceManager.newUnlimitedEndpoint(
remoteIPAddress.to_string());
}
else
{
usage = m_resourceManager.newInboundEndpoint(remoteIPAddress);
if (usage.disconnect())
{
HTTPReply(503, "Server is overloaded", output, rpcJ);
return;
}
}
std::string strMethod = method.asString ();
if (strMethod.empty())
{
HTTPReply (400, "method is empty", output, rpcJ);
return;
}
// Extract request parameters from the request Json as `params`.
//
// If the field "params" is empty, `params` is an empty object.
//
// Otherwise, that field must be an array of length 1 (why?)
// and we take that first entry and validate that it's an object.
Json::Value params = jsonRPC [jss::params];
if (! params)
params = Json::Value (Json::objectValue);
else if (!params.isArray () || params.size() != 1)
{
HTTPReply (400, "params unparseable", output, rpcJ);
return;
}
else
{
params = std::move (params[0u]);
if (!params.isObject())
{
HTTPReply (400, "params unparseable", output, rpcJ);
return;
}
}
// VFALCO TODO Shouldn't we handle this earlier?
//
if (role == Role::FORBID)
{
// VFALCO TODO Needs implementing
// FIXME Needs implementing
// XXX This needs rate limiting to prevent brute forcing password.
HTTPReply (403, "Forbidden", output, rpcJ);
return;
}
JLOG(m_journal.debug()) << "Query: " << strMethod << params;
// Provide the JSON-RPC method as the field "command" in the request.
params[jss::command] = strMethod;
JLOG (m_journal.trace())
<< "doRpcCommand:" << strMethod << ":" << params;
Resource::Charge loadType = Resource::feeReferenceRPC;
auto const start (std::chrono::high_resolution_clock::now ());
RPC::Context context {m_journal, params, app_, loadType, m_networkOPs,
app_.getLedgerMaster(), usage, role, jobCoro, InfoSub::pointer(),
{user, forwardedFor}};
Json::Value result;
RPC::doCommand (context, result);
// Always report "status". On an error report the request as received.
if (result.isMember (jss::error))
{
result[jss::status] = jss::error;
result[jss::request] = params;
JLOG (m_journal.debug()) <<
"rpcError: " << result [jss::error] <<
": " << result [jss::error_message];
}
else
{
result[jss::status] = jss::success;
}
Json::Value reply (Json::objectValue);
reply[jss::result] = std::move (result);
auto response = to_string (reply);
rpc_time_.notify (static_cast <beast::insight::Event::value_type> (
std::chrono::duration_cast <std::chrono::milliseconds> (
std::chrono::high_resolution_clock::now () - start)));
++rpc_requests_;
rpc_size_.notify (static_cast <beast::insight::Event::value_type> (
response.size ()));
response += '\n';
usage.charge (loadType);
if (auto stream = m_journal.debug())
{
static const int maxSize = 10000;
if (response.size() <= maxSize)
stream << "Reply: " << response;
else
stream << "Reply: " << response.substr (0, maxSize);
}
HTTPReply (200, response, output, rpcJ);
}
//------------------------------------------------------------------------------
// Returns `true` if the HTTP request is a Websockets Upgrade
// http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header#Use_with_WebSockets
bool
ServerHandlerImp::isWebsocketUpgrade (http_request_type const& request)
{
if (is_upgrade(request))
return request.headers["Upgrade"] == "websocket";
return false;
}
// VFALCO TODO Rewrite to use beast::http::headers
bool
ServerHandlerImp::authorized (Port const& port,
std::map<std::string, std::string> const& h)
{
if (port.user.empty() || port.password.empty())
return true;
auto const it = h.find ("authorization");
if ((it == h.end ()) || (it->second.substr (0, 6) != "Basic "))
return false;
std::string strUserPass64 = it->second.substr (6);
boost::trim (strUserPass64);
std::string strUserPass = beast::detail::base64_decode (strUserPass64);
std::string::size_type nColon = strUserPass.find (":");
if (nColon == std::string::npos)
return false;
std::string strUser = strUserPass.substr (0, nColon);
std::string strPassword = strUserPass.substr (nColon + 1);
return strUser == port.user && strPassword == port.password;
}
//------------------------------------------------------------------------------
void
ServerHandler::Setup::makeContexts()
{
for(auto& p : ports)
{
if (p.secure())
{
if (p.ssl_key.empty() && p.ssl_cert.empty() &&
p.ssl_chain.empty())
p.context = make_SSLContext();
else
p.context = make_SSLContextAuthed (
p.ssl_key, p.ssl_cert, p.ssl_chain);
}
else
{
p.context = std::make_shared<
boost::asio::ssl::context>(
boost::asio::ssl::context::sslv23);
}
}
}
static
Port
to_Port(ParsedPort const& parsed, std::ostream& log)
{
Port p;
p.name = parsed.name;
if (! parsed.ip)
{
log << "Missing 'ip' in [" << p.name << "]\n";
Throw<std::exception> ();
}
p.ip = *parsed.ip;
if (! parsed.port)
{
log << "Missing 'port' in [" << p.name << "]\n";
Throw<std::exception> ();
}
else if (*parsed.port == 0)
{
log << "Port " << *parsed.port << "in [" << p.name << "] is invalid\n";
Throw<std::exception> ();
}
p.port = *parsed.port;
if (parsed.admin_ip)
p.admin_ip = *parsed.admin_ip;
if (parsed.secure_gateway_ip)
p.secure_gateway_ip = *parsed.secure_gateway_ip;
if (parsed.protocol.empty())
{
log << "Missing 'protocol' in [" << p.name << "]\n";
Throw<std::exception> ();
}
p.protocol = parsed.protocol;
if (p.websockets() &&
(parsed.protocol.count("peer") > 0 ||
parsed.protocol.count("http") > 0 ||
parsed.protocol.count("https") > 0))
{
log << "Invalid protocol combination in [" << p.name << "]\n";
Throw<std::exception> ();
}
p.user = parsed.user;
p.password = parsed.password;
p.admin_user = parsed.admin_user;
p.admin_password = parsed.admin_password;
p.ssl_key = parsed.ssl_key;
p.ssl_cert = parsed.ssl_cert;
p.ssl_chain = parsed.ssl_chain;
return p;
}
static
std::vector<Port>
parse_Ports (
Config const& config,
std::ostream& log)
{
std::vector<Port> result;
if (! config.exists("server"))
{
log <<
"Required section [server] is missing\n";
Throw<std::exception> ();
}
ParsedPort common;
parse_Port (common, config["server"], log);
auto const& names = config.section("server").values();
result.reserve(names.size());
for (auto const& name : names)
{
if (! config.exists(name))
{
log <<
"Missing section: [" << name << "]\n";
Throw<std::exception> ();
}
ParsedPort parsed = common;
parsed.name = name;
parse_Port(parsed, config[name], log);
result.push_back(to_Port(parsed, log));
}
if (config.standalone())
{
auto it = result.begin ();
while (it != result.end())
{
auto& p = it->protocol;
// Remove the peer protocol, and if that would
// leave the port empty, remove the port as well
if (p.erase ("peer") && p.empty())
it = result.erase (it);
else
++it;
}
}
else
{
auto const count = std::count_if (
result.cbegin(), result.cend(),
[](Port const& p)
{
return p.protocol.count("peer") != 0;
});
if (count > 1)
{
log << "Error: More than one peer protocol configured in [server]\n";
Throw<std::exception> ();
}
if (count == 0)
log << "Warning: No peer protocol configured\n";
}
return result;
}
// Fill out the client portion of the Setup
static
void
setup_Client (ServerHandler::Setup& setup)
{
decltype(setup.ports)::const_iterator iter;
for (iter = setup.ports.cbegin();
iter != setup.ports.cend(); ++iter)
if (iter->protocol.count("http") > 0 ||
iter->protocol.count("https") > 0)
break;
if (iter == setup.ports.cend())
return;
setup.client.secure =
iter->protocol.count("https") > 0;
setup.client.ip = iter->ip.to_string();
// VFALCO HACK! to make localhost work
if (setup.client.ip == "0.0.0.0")
setup.client.ip = "127.0.0.1";
setup.client.port = iter->port;
setup.client.user = iter->user;
setup.client.password = iter->password;
setup.client.admin_user = iter->admin_user;
setup.client.admin_password = iter->admin_password;
}
// Fill out the overlay portion of the Setup
static
void
setup_Overlay (ServerHandler::Setup& setup)
{
auto const iter = std::find_if(
setup.ports.cbegin(), setup.ports.cend(),
[](Port const& port)
{
return port.protocol.count("peer") != 0;
});
if (iter == setup.ports.cend())
{
setup.overlay.port = 0;
return;
}
setup.overlay.ip = iter->ip;
setup.overlay.port = iter->port;
}
ServerHandler::Setup
setup_ServerHandler(
Config const& config,
std::ostream& log)
{
ServerHandler::Setup setup;
setup.ports = parse_Ports(config, log);
setup_Client(setup);
setup_Overlay(setup);
return setup;
}
std::unique_ptr <ServerHandler>
make_ServerHandler (Application& app, Stoppable& parent,
boost::asio::io_service& io_service, JobQueue& jobQueue,
NetworkOPs& networkOPs, Resource::Manager& resourceManager,
CollectorManager& cm)
{
return std::make_unique<ServerHandlerImp>(app, parent,
io_service, jobQueue, networkOPs, resourceManager, cm);
}
} // ripple

View File

@@ -0,0 +1,181 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_RPC_SERVERHANDLERIMP_H_INCLUDED
#define RIPPLE_RPC_SERVERHANDLERIMP_H_INCLUDED
#include <ripple/core/Job.h>
#include <ripple/core/JobCoro.h>
#include <ripple/rpc/impl/WSInfoSub.h>
#include <ripple/server/Server.h>
#include <ripple/server/Session.h>
#include <ripple/server/WSSession.h>
#include <ripple/rpc/RPCHandler.h>
#include <ripple/app/main/CollectorManager.h>
#include <map>
#include <mutex>
#include <vector>
namespace ripple {
inline
bool operator< (Port const& lhs, Port const& rhs)
{
return lhs.name < rhs.name;
}
class ServerHandlerImp
: public Stoppable
{
public:
struct Setup
{
std::vector<Port> ports;
// Memberspace
struct client_t
{
bool secure = false;
std::string ip;
std::uint16_t port = 0;
std::string user;
std::string password;
std::string admin_user;
std::string admin_password;
};
// Configuration when acting in client role
client_t client;
// Configuration for the Overlay
struct overlay_t
{
boost::asio::ip::address ip;
std::uint16_t port = 0;
};
overlay_t overlay;
void
makeContexts();
};
private:
Application& app_;
Resource::Manager& m_resourceManager;
beast::Journal m_journal;
NetworkOPs& m_networkOPs;
std::unique_ptr<Server> m_server;
Setup setup_;
JobQueue& m_jobQueue;
beast::insight::Counter rpc_requests_;
beast::insight::Event rpc_size_;
beast::insight::Event rpc_time_;
std::mutex countlock_;
std::map<std::reference_wrapper<Port const>, int> count_;
public:
ServerHandlerImp (Application& app, Stoppable& parent,
boost::asio::io_service& io_service, JobQueue& jobQueue,
NetworkOPs& networkOPs, Resource::Manager& resourceManager,
CollectorManager& cm);
~ServerHandlerImp();
using Output = Json::Output;
void
setup (Setup const& setup, beast::Journal journal);
Setup const&
setup() const
{
return setup_;
}
//
// Stoppable
//
void
onStop();
//
// Handler
//
bool
onAccept (Session& session,
boost::asio::ip::tcp::endpoint endpoint);
Handoff
onHandoff (Session& session,
std::unique_ptr <beast::asio::ssl_bundle>&& bundle,
http_request_type&& request,
boost::asio::ip::tcp::endpoint remote_address);
Handoff
onHandoff (Session& session, boost::asio::ip::tcp::socket&& socket,
http_request_type&& request,
boost::asio::ip::tcp::endpoint remote_address);
void
onRequest (Session& session);
void
onWSMessage(std::shared_ptr<WSSession> session,
std::vector<boost::asio::const_buffer> const& buffers);
void
onClose (Session& session,
boost::system::error_code const&);
void
onStopped (Server&);
//--------------------------------------------------------------------------
Json::Value
processSession(
std::shared_ptr<WSSession> const& session,
std::shared_ptr<JobCoro> const& coro,
Json::Value const& jv);
void
processSession (std::shared_ptr<Session> const&,
std::shared_ptr<JobCoro> jobCoro);
void
processRequest (Port const& port, std::string const& request,
beast::IP::Endpoint const& remoteIPAddress, Output&&,
std::shared_ptr<JobCoro> jobCoro,
std::string forwardedFor, std::string user);
private:
bool
isWebsocketUpgrade (http_request_type const& request);
bool
authorized (Port const& port,
std::map<std::string, std::string> const& h);
};
}
#endif

View File

@@ -21,7 +21,7 @@
#define RIPPLE_RPC_TRANSACTIONSIGN_H_INCLUDED
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/server/Role.h>
#include <ripple/rpc/Role.h>
#include <ripple/ledger/ApplyView.h>
namespace ripple {

View File

@@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_RPC_WSINFOSUB_H
#define RIPPLE_RPC_WSINFOSUB_H
#include <ripple/server/WSSession.h>
#include <ripple/net/InfoSub.h>
#include <ripple/beast/net/IPAddressConversion.h>
#include <ripple/json/Output.h>
#include <ripple/json/to_string.h>
#include <ripple/rpc/Role.h>
#include <memory>
#include <string>
namespace ripple {
class WSInfoSub : public InfoSub
{
std::weak_ptr<WSSession> ws_;
std::string user_;
std::string fwdfor_;
public:
WSInfoSub(Source& source, std::shared_ptr<WSSession> const& ws)
: InfoSub(source)
, ws_(ws)
{
auto const& h = ws->request().headers;
auto it = h.find("X-User");
if (it != h.end() &&
isIdentified(
ws->port(), beast::IPAddressConversion::from_asio(
ws->remote_endpoint()).address(), it->second))
{
user_ = it->second;
it = h.find("X-Forwarded-For");
if (it != h.end())
fwdfor_ = it->second;
}
}
std::string
user() const
{
return user_;
}
std::string
forwarded_for() const
{
return fwdfor_;
}
void
send(Json::Value const& jv, bool)
{
auto sp = ws_.lock();
if(! sp)
return;
beast::streambuf sb;
stream(jv,
[&](void const* data, std::size_t n)
{
sb.commit(boost::asio::buffer_copy(
sb.prepare(n), boost::asio::buffer(data, n)));
});
auto m = std::make_shared<
StreambufWSMsg<decltype(sb)>>(
std::move(sb));
sp->send(m);
}
};
} // ripple
#endif