rippled
Loading...
Searching...
No Matches
ServerHandler.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/main/Application.h>
21#include <xrpld/core/ConfigSections.h>
22#include <xrpld/core/JobQueue.h>
23#include <xrpld/overlay/Overlay.h>
24#include <xrpld/rpc/RPCHandler.h>
25#include <xrpld/rpc/Role.h>
26#include <xrpld/rpc/ServerHandler.h>
27#include <xrpld/rpc/detail/RPCHelpers.h>
28#include <xrpld/rpc/detail/Tuning.h>
29#include <xrpld/rpc/json_body.h>
30#include <xrpl/basics/Log.h>
31#include <xrpl/basics/base64.h>
32#include <xrpl/basics/contract.h>
33#include <xrpl/basics/make_SSLContext.h>
34#include <xrpl/beast/net/IPAddressConversion.h>
35#include <xrpl/beast/rfc2616.h>
36#include <xrpl/json/json_reader.h>
37#include <xrpl/json/to_string.h>
38#include <xrpl/protocol/ErrorCodes.h>
39#include <xrpl/protocol/RPCErr.h>
40#include <xrpl/resource/Fees.h>
41#include <xrpl/resource/ResourceManager.h>
42#include <xrpl/server/Server.h>
43#include <xrpl/server/SimpleWriter.h>
44#include <xrpl/server/detail/JSONRPCUtil.h>
45
46#include <boost/algorithm/string.hpp>
47#include <boost/beast/http/fields.hpp>
48#include <boost/beast/http/string_body.hpp>
49
50#include <algorithm>
51#include <stdexcept>
52
53namespace ripple {
54
55static bool
57{
58 return request.version() >= 11 && request.target() == "/" &&
59 request.body().size() == 0 &&
60 request.method() == boost::beast::http::verb::get;
61}
62
63static Handoff
65 http_request_type const& request,
66 boost::beast::http::status status)
67{
68 using namespace boost::beast::http;
69 Handoff handoff;
70 response<string_body> msg;
71 msg.version(request.version());
72 msg.result(status);
73 msg.insert("Server", BuildInfo::getFullVersionString());
74 msg.insert("Content-Type", "text/html");
75 msg.insert("Connection", "close");
76 msg.body() = "Invalid protocol.";
77 msg.prepare_payload();
78 handoff.response = std::make_shared<SimpleWriter>(msg);
79 return handoff;
80}
81
82// VFALCO TODO Rewrite to use boost::beast::http::fields
83static bool
85{
86 if (port.user.empty() || port.password.empty())
87 return true;
88
89 auto const it = h.find("authorization");
90 if ((it == h.end()) || (it->second.substr(0, 6) != "Basic "))
91 return false;
92 std::string strUserPass64 = it->second.substr(6);
93 boost::trim(strUserPass64);
94 std::string strUserPass = base64_decode(strUserPass64);
95 std::string::size_type nColon = strUserPass.find(":");
96 if (nColon == std::string::npos)
97 return false;
98 std::string strUser = strUserPass.substr(0, nColon);
99 std::string strPassword = strUserPass.substr(nColon + 1);
100 return strUser == port.user && strPassword == port.password;
101}
102
105 Application& app,
106 boost::asio::io_service& io_service,
107 JobQueue& jobQueue,
108 NetworkOPs& networkOPs,
109 Resource::Manager& resourceManager,
111 : app_(app)
112 , m_resourceManager(resourceManager)
113 , m_journal(app_.journal("Server"))
114 , m_networkOPs(networkOPs)
115 , m_server(make_Server(*this, io_service, app_.journal("Server")))
116 , m_jobQueue(jobQueue)
117{
118 auto const& group(cm.group("rpc"));
119 rpc_requests_ = group->make_counter("requests");
120 rpc_size_ = group->make_event("size");
121 rpc_time_ = group->make_event("time");
122}
123
125{
126 m_server = nullptr;
127}
128
129void
131{
132 setup_ = setup;
133 endpoints_ = m_server->ports(setup.ports);
134
135 // fix auto ports
136 for (auto& port : setup_.ports)
137 {
138 if (auto it = endpoints_.find(port.name); it != endpoints_.end())
139 {
140 auto const endpointPort = it->second.port();
141 if (!port.port)
142 port.port = endpointPort;
143
144 if (!setup_.client.port &&
145 (port.protocol.count("http") > 0 ||
146 port.protocol.count("https") > 0))
147 setup_.client.port = endpointPort;
148
149 if (!setup_.overlay.port() && (port.protocol.count("peer") > 0))
150 setup_.overlay.port(endpointPort);
151 }
152 }
153}
154
155//------------------------------------------------------------------------------
156
157void
159{
160 m_server->close();
161 {
163 condition_.wait(lock, [this] { return stopped_; });
164 }
165}
166
167//------------------------------------------------------------------------------
168
169bool
171 Session& session,
172 boost::asio::ip::tcp::endpoint endpoint)
173{
174 auto const& port = session.port();
175
176 auto const c = [this, &port]() {
178 return ++count_[port];
179 }();
180
181 if (port.limit && c >= port.limit)
182 {
183 JLOG(m_journal.trace())
184 << port.name << " is full; dropping " << endpoint;
185 return false;
186 }
187
188 return true;
189}
190
193 Session& session,
195 http_request_type&& request,
196 boost::asio::ip::tcp::endpoint const& remote_address)
197{
198 using namespace boost::beast;
199 auto const& p{session.port().protocol};
200 bool const is_ws{
201 p.count("ws") > 0 || p.count("ws2") > 0 || p.count("wss") > 0 ||
202 p.count("wss2") > 0};
203
204 if (websocket::is_upgrade(request))
205 {
206 if (!is_ws)
207 return statusRequestResponse(request, http::status::unauthorized);
208
210 try
211 {
212 ws = session.websocketUpgrade();
213 }
214 catch (std::exception const& e)
215 {
216 JLOG(m_journal.error())
217 << "Exception upgrading websocket: " << e.what() << "\n";
219 request, http::status::internal_server_error);
220 }
221
222 auto is{std::make_shared<WSInfoSub>(m_networkOPs, ws)};
223 auto const beast_remote_address =
225 is->getConsumer() = requestInboundEndpoint(
227 beast_remote_address,
230 session.port(),
231 Json::Value(),
232 beast_remote_address,
233 is->user()),
234 is->user(),
235 is->forwarded_for());
236 ws->appDefined = std::move(is);
237 ws->run();
238
239 Handoff handoff;
240 handoff.moved = true;
241 return handoff;
242 }
243
244 if (bundle && p.count("peer") > 0)
245 return app_.overlay().onHandoff(
246 std::move(bundle), std::move(request), remote_address);
247
248 if (is_ws && isStatusRequest(request))
249 return statusResponse(request);
250
251 // Otherwise pass to legacy onRequest or websocket
252 return {};
253}
254
255static inline Json::Output
257{
258 return [&](boost::beast::string_view const& b) {
259 session.write(b.data(), b.size());
260 };
261}
262
264build_map(boost::beast::http::fields const& h)
265{
267 for (auto const& e : h)
268 {
269 // key cannot be a std::string_view because it needs to be used in
270 // map and along with iterators
271 std::string key(e.name_string());
272 std::transform(key.begin(), key.end(), key.begin(), [](auto kc) {
273 return std::tolower(static_cast<unsigned char>(kc));
274 });
275 c[key] = e.value();
276 }
277 return c;
278}
279
280template <class ConstBufferSequence>
281static std::string
282buffers_to_string(ConstBufferSequence const& bs)
283{
284 using boost::asio::buffer_cast;
285 using boost::asio::buffer_size;
286 std::string s;
287 s.reserve(buffer_size(bs));
288 // Use auto&& so the right thing happens whether bs returns a copy or
289 // a reference
290 for (auto&& b : bs)
291 s.append(buffer_cast<char const*>(b), buffer_size(b));
292 return s;
293}
294
295void
297{
298 // Make sure RPC is enabled on the port
299 if (session.port().protocol.count("http") == 0 &&
300 session.port().protocol.count("https") == 0)
301 {
302 HTTPReply(403, "Forbidden", makeOutput(session), app_.journal("RPC"));
303 session.close(true);
304 return;
305 }
306
307 // Check user/password authorization
308 if (!authorized(session.port(), build_map(session.request())))
309 {
310 HTTPReply(403, "Forbidden", makeOutput(session), app_.journal("RPC"));
311 session.close(true);
312 return;
313 }
314
315 std::shared_ptr<Session> detachedSession = session.detach();
316 auto const postResult = m_jobQueue.postCoro(
318 "RPC-Client",
319 [this, detachedSession](std::shared_ptr<JobQueue::Coro> coro) {
320 processSession(detachedSession, coro);
321 });
322 if (postResult == nullptr)
323 {
324 // The coroutine was rejected, probably because we're shutting down.
325 HTTPReply(
326 503,
327 "Service Unavailable",
328 makeOutput(*detachedSession),
329 app_.journal("RPC"));
330 detachedSession->close(true);
331 return;
332 }
333}
334
335void
339{
340 Json::Value jv;
341 auto const size = boost::asio::buffer_size(buffers);
342 if (size > RPC::Tuning::maxRequestSize ||
343 !Json::Reader{}.parse(jv, buffers) || !jv.isObject())
344 {
346 jvResult[jss::type] = jss::error;
347 jvResult[jss::error] = "jsonInvalid";
348 jvResult[jss::value] = buffers_to_string(buffers);
349 boost::beast::multi_buffer sb;
350 Json::stream(jvResult, [&sb](auto const p, auto const n) {
351 sb.commit(boost::asio::buffer_copy(
352 sb.prepare(n), boost::asio::buffer(p, n)));
353 });
354 JLOG(m_journal.trace()) << "Websocket sending '" << jvResult << "'";
355 session->send(
356 std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
357 session->complete();
358 return;
359 }
360
361 JLOG(m_journal.trace()) << "Websocket received '" << jv << "'";
362
363 auto const postResult = m_jobQueue.postCoro(
365 "WS-Client",
366 [this, session, jv = std::move(jv)](
368 auto const jr = this->processSession(session, coro, jv);
369 auto const s = to_string(jr);
370 auto const n = s.length();
371 boost::beast::multi_buffer sb(n);
372 sb.commit(boost::asio::buffer_copy(
373 sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
374 session->send(
375 std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
376 session->complete();
377 });
378 if (postResult == nullptr)
379 {
380 // The coroutine was rejected, probably because we're shutting down.
381 session->close({boost::beast::websocket::going_away, "Shutting Down"});
382 }
383}
384
385void
386ServerHandler::onClose(Session& session, boost::system::error_code const&)
387{
389 --count_[session.port()];
390}
391
392void
394{
396 stopped_ = true;
398}
399
400//------------------------------------------------------------------------------
401
402template <class T>
403void
405 Json::Value const& request,
406 T const& duration,
407 beast::Journal& journal)
408{
409 using namespace std::chrono_literals;
410 auto const level = (duration >= 10s) ? journal.error()
411 : (duration >= 1s) ? journal.warn()
412 : journal.debug();
413
414 JLOG(level) << "RPC request processing duration = "
415 << std::chrono::duration_cast<std::chrono::microseconds>(
416 duration)
417 .count()
418 << " microseconds. request = " << request;
419}
420
423 std::shared_ptr<WSSession> const& session,
425 Json::Value const& jv)
426{
427 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
428 if (is->getConsumer().disconnect(m_journal))
429 {
430 session->close(
431 {boost::beast::websocket::policy_error, "threshold exceeded"});
432 // FIX: This rpcError is not delivered since the session
433 // was just closed.
434 return rpcError(rpcSLOW_DOWN);
435 }
436
437 // Requests without "command" are invalid.
440 try
441 {
442 auto apiVersion =
444 if (apiVersion == RPC::apiInvalidVersion ||
445 (!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
446 (jv.isMember(jss::command) && !jv[jss::command].isString()) ||
447 (jv.isMember(jss::method) && !jv[jss::method].isString()) ||
448 (jv.isMember(jss::command) && jv.isMember(jss::method) &&
449 jv[jss::command].asString() != jv[jss::method].asString()))
450 {
451 jr[jss::type] = jss::response;
452 jr[jss::status] = jss::error;
453 jr[jss::error] = apiVersion == RPC::apiInvalidVersion
454 ? jss::invalid_API_version
455 : jss::missingCommand;
456 jr[jss::request] = jv;
457 if (jv.isMember(jss::id))
458 jr[jss::id] = jv[jss::id];
459 if (jv.isMember(jss::jsonrpc))
460 jr[jss::jsonrpc] = jv[jss::jsonrpc];
461 if (jv.isMember(jss::ripplerpc))
462 jr[jss::ripplerpc] = jv[jss::ripplerpc];
463 if (jv.isMember(jss::api_version))
464 jr[jss::api_version] = jv[jss::api_version];
465
466 is->getConsumer().charge(Resource::feeMalformedRPC);
467 return jr;
468 }
469
470 auto required = RPC::roleRequired(
471 apiVersion,
473 jv.isMember(jss::command) ? jv[jss::command].asString()
474 : jv[jss::method].asString());
475 auto role = requestRole(
476 required,
477 session->port(),
478 jv,
479 beast::IP::from_asio(session->remote_endpoint().address()),
480 is->user());
481 if (Role::FORBID == role)
482 {
483 loadType = Resource::feeMalformedRPC;
484 jr[jss::result] = rpcError(rpcFORBIDDEN);
485 }
486 else
487 {
488 RPC::JsonContext context{
489 {app_.journal("RPCHandler"),
490 app_,
491 loadType,
492 app_.getOPs(),
494 is->getConsumer(),
495 role,
496 coro,
497 is,
498 apiVersion},
499 jv,
500 {is->user(), is->forwarded_for()}};
501
502 auto start = std::chrono::system_clock::now();
503 RPC::doCommand(context, jr[jss::result]);
505 logDuration(jv, end - start, m_journal);
506 }
507 }
508 catch (std::exception const& ex)
509 {
510 jr[jss::result] = RPC::make_error(rpcINTERNAL);
511 JLOG(m_journal.error())
512 << "Exception while processing WS: " << ex.what() << "\n"
513 << "Input JSON: " << Json::Compact{Json::Value{jv}};
514 }
515
516 is->getConsumer().charge(loadType);
517 if (is->getConsumer().warn())
518 jr[jss::warning] = jss::load;
519
520 // Currently we will simply unwrap errors returned by the RPC
521 // API, in the future maybe we can make the responses
522 // consistent.
523 //
524 // Regularize result. This is duplicate code.
525 if (jr[jss::result].isMember(jss::error))
526 {
527 jr = jr[jss::result];
528 jr[jss::status] = jss::error;
529
530 auto rq = jv;
531
532 if (rq.isObject())
533 {
534 if (rq.isMember(jss::passphrase.c_str()))
535 rq[jss::passphrase.c_str()] = "<masked>";
536 if (rq.isMember(jss::secret.c_str()))
537 rq[jss::secret.c_str()] = "<masked>";
538 if (rq.isMember(jss::seed.c_str()))
539 rq[jss::seed.c_str()] = "<masked>";
540 if (rq.isMember(jss::seed_hex.c_str()))
541 rq[jss::seed_hex.c_str()] = "<masked>";
542 }
543
544 jr[jss::request] = rq;
545 }
546 else
547 {
548 if (jr[jss::result].isMember("forwarded") &&
549 jr[jss::result]["forwarded"])
550 jr = jr[jss::result];
551 jr[jss::status] = jss::success;
552 }
553
554 if (jv.isMember(jss::id))
555 jr[jss::id] = jv[jss::id];
556 if (jv.isMember(jss::jsonrpc))
557 jr[jss::jsonrpc] = jv[jss::jsonrpc];
558 if (jv.isMember(jss::ripplerpc))
559 jr[jss::ripplerpc] = jv[jss::ripplerpc];
560 if (jv.isMember(jss::api_version))
561 jr[jss::api_version] = jv[jss::api_version];
562
563 jr[jss::type] = jss::response;
564 return jr;
565}
566
567// Run as a coroutine.
568void
570 std::shared_ptr<Session> const& session,
572{
574 session->port(),
575 buffers_to_string(session->request().body().data()),
576 session->remoteAddress().at_port(0),
577 makeOutput(*session),
578 coro,
579 forwardedFor(session->request()),
580 [&] {
581 auto const iter = session->request().find("X-User");
582 if (iter != session->request().end())
583 return iter->value();
584 return boost::beast::string_view{};
585 }());
586
587 if (beast::rfc2616::is_keep_alive(session->request()))
588 session->complete();
589 else
590 session->close(true);
591}
592
593static Json::Value
595{
597 sub["code"] = code;
598 sub["message"] = std::move(message);
600 r["error"] = sub;
601 return r;
602}
603
604Json::Int constexpr method_not_found = -32601;
605Json::Int constexpr server_overloaded = -32604;
606Json::Int constexpr forbidden = -32605;
607Json::Int constexpr wrong_version = -32606;
608
609void
610ServerHandler::processRequest(
611 Port const& port,
612 std::string const& request,
613 beast::IP::Endpoint const& remoteIPAddress,
614 Output&& output,
616 std::string_view forwardedFor,
617 std::string_view user)
618{
619 auto rpcJ = app_.journal("RPC");
620
621 Json::Value jsonOrig;
622 {
623 Json::Reader reader;
624 if ((request.size() > RPC::Tuning::maxRequestSize) ||
625 !reader.parse(request, jsonOrig) || !jsonOrig ||
626 !jsonOrig.isObject())
627 {
628 HTTPReply(
629 400,
630 "Unable to parse request: " + reader.getFormatedErrorMessages(),
631 output,
632 rpcJ);
633 return;
634 }
635 }
636
637 bool batch = false;
638 unsigned size = 1;
639 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
640 {
641 batch = true;
642 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
643 {
644 HTTPReply(400, "Malformed batch request", output, rpcJ);
645 return;
646 }
647 size = jsonOrig[jss::params].size();
648 }
649
651 auto const start(std::chrono::high_resolution_clock::now());
652 for (unsigned i = 0; i < size; ++i)
653 {
654 Json::Value const& jsonRPC =
655 batch ? jsonOrig[jss::params][i] : jsonOrig;
656
657 if (!jsonRPC.isObject())
658 {
660 r[jss::request] = jsonRPC;
661 r[jss::error] =
662 make_json_error(method_not_found, "Method not found");
663 reply.append(r);
664 continue;
665 }
666
667 unsigned apiVersion = RPC::apiVersionIfUnspecified;
668 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
669 jsonRPC[jss::params].size() > 0 &&
670 jsonRPC[jss::params][0u].isObject())
671 {
672 apiVersion = RPC::getAPIVersionNumber(
673 jsonRPC[jss::params][Json::UInt(0)],
674 app_.config().BETA_RPC_API);
675 }
676
677 if (apiVersion == RPC::apiVersionIfUnspecified && batch)
678 {
679 // for batch request, api_version may be at a different level
680 apiVersion =
681 RPC::getAPIVersionNumber(jsonRPC, app_.config().BETA_RPC_API);
682 }
683
684 if (apiVersion == RPC::apiInvalidVersion)
685 {
686 if (!batch)
687 {
688 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
689 return;
690 }
692 r[jss::request] = jsonRPC;
693 r[jss::error] = make_json_error(
694 wrong_version, jss::invalid_API_version.c_str());
695 reply.append(r);
696 continue;
697 }
698
699 /* ------------------------------------------------------------------ */
700 auto role = Role::FORBID;
701 auto required = Role::FORBID;
702 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
703 required = RPC::roleRequired(
704 apiVersion,
705 app_.config().BETA_RPC_API,
706 jsonRPC[jss::method].asString());
707
708 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
709 jsonRPC[jss::params].size() > 0 &&
710 jsonRPC[jss::params][Json::UInt(0)].isObjectOrNull())
711 {
712 role = requestRole(
713 required,
714 port,
715 jsonRPC[jss::params][Json::UInt(0)],
716 remoteIPAddress,
717 user);
718 }
719 else
720 {
721 role = requestRole(
722 required, port, Json::objectValue, remoteIPAddress, user);
723 }
724
725 Resource::Consumer usage;
726 if (isUnlimited(role))
727 {
728 usage = m_resourceManager.newUnlimitedEndpoint(remoteIPAddress);
729 }
730 else
731 {
732 usage = m_resourceManager.newInboundEndpoint(
733 remoteIPAddress, role == Role::PROXY, forwardedFor);
734 if (usage.disconnect(m_journal))
735 {
736 if (!batch)
737 {
738 HTTPReply(503, "Server is overloaded", output, rpcJ);
739 return;
740 }
741 Json::Value r = jsonRPC;
742 r[jss::error] =
743 make_json_error(server_overloaded, "Server is overloaded");
744 reply.append(r);
745 continue;
746 }
747 }
748
749 if (role == Role::FORBID)
750 {
751 usage.charge(Resource::feeMalformedRPC);
752 if (!batch)
753 {
754 HTTPReply(403, "Forbidden", output, rpcJ);
755 return;
756 }
757 Json::Value r = jsonRPC;
758 r[jss::error] = make_json_error(forbidden, "Forbidden");
759 reply.append(r);
760 continue;
761 }
762
763 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
764 {
765 usage.charge(Resource::feeMalformedRPC);
766 if (!batch)
767 {
768 HTTPReply(400, "Null method", output, rpcJ);
769 return;
770 }
771 Json::Value r = jsonRPC;
772 r[jss::error] = make_json_error(method_not_found, "Null method");
773 reply.append(r);
774 continue;
775 }
776
777 Json::Value const& method = jsonRPC[jss::method];
778 if (!method.isString())
779 {
780 usage.charge(Resource::feeMalformedRPC);
781 if (!batch)
782 {
783 HTTPReply(400, "method is not string", output, rpcJ);
784 return;
785 }
786 Json::Value r = jsonRPC;
787 r[jss::error] =
788 make_json_error(method_not_found, "method is not string");
789 reply.append(r);
790 continue;
791 }
792
793 std::string strMethod = method.asString();
794 if (strMethod.empty())
795 {
796 usage.charge(Resource::feeMalformedRPC);
797 if (!batch)
798 {
799 HTTPReply(400, "method is empty", output, rpcJ);
800 return;
801 }
802 Json::Value r = jsonRPC;
803 r[jss::error] =
804 make_json_error(method_not_found, "method is empty");
805 reply.append(r);
806 continue;
807 }
808
809 // Extract request parameters from the request Json as `params`.
810 //
811 // If the field "params" is empty, `params` is an empty object.
812 //
813 // Otherwise, that field must be an array of length 1 (why?)
814 // and we take that first entry and validate that it's an object.
815 Json::Value params;
816 if (!batch)
817 {
818 params = jsonRPC[jss::params];
819 if (!params)
821
822 else if (!params.isArray() || params.size() != 1)
823 {
824 usage.charge(Resource::feeMalformedRPC);
825 HTTPReply(400, "params unparseable", output, rpcJ);
826 return;
827 }
828 else
829 {
830 params = std::move(params[0u]);
831 if (!params.isObjectOrNull())
832 {
833 usage.charge(Resource::feeMalformedRPC);
834 HTTPReply(400, "params unparseable", output, rpcJ);
835 return;
836 }
837 }
838 }
839 else // batch
840 {
841 params = jsonRPC;
842 }
843
844 std::string ripplerpc = "1.0";
845 if (params.isMember(jss::ripplerpc))
846 {
847 if (!params[jss::ripplerpc].isString())
848 {
849 usage.charge(Resource::feeMalformedRPC);
850 if (!batch)
851 {
852 HTTPReply(400, "ripplerpc is not a string", output, rpcJ);
853 return;
854 }
855
856 Json::Value r = jsonRPC;
857 r[jss::error] = make_json_error(
858 method_not_found, "ripplerpc is not a string");
859 reply.append(r);
860 continue;
861 }
862 ripplerpc = params[jss::ripplerpc].asString();
863 }
864
869 if (role != Role::IDENTIFIED && role != Role::PROXY)
870 {
872 user.remove_suffix(user.size());
873 }
874
875 JLOG(m_journal.debug()) << "Query: " << strMethod << params;
876
877 // Provide the JSON-RPC method as the field "command" in the request.
878 params[jss::command] = strMethod;
879 JLOG(m_journal.trace())
880 << "doRpcCommand:" << strMethod << ":" << params;
881
882 Resource::Charge loadType = Resource::feeReferenceRPC;
883
884 RPC::JsonContext context{
885 {m_journal,
886 app_,
887 loadType,
888 m_networkOPs,
889 app_.getLedgerMaster(),
890 usage,
891 role,
892 coro,
894 apiVersion},
895 params,
896 {user, forwardedFor}};
897 Json::Value result;
898
899 auto start = std::chrono::system_clock::now();
900
901 try
902 {
903 RPC::doCommand(context, result);
904 }
905 catch (std::exception const& ex)
906 {
907 result = RPC::make_error(rpcINTERNAL);
908 JLOG(m_journal.error()) << "Internal error : " << ex.what()
909 << " when processing request: "
910 << Json::Compact{Json::Value{params}};
911 }
912
914
915 logDuration(params, end - start, m_journal);
916
917 usage.charge(loadType);
918 if (usage.warn())
919 result[jss::warning] = jss::load;
920
922 if (ripplerpc >= "2.0")
923 {
924 if (result.isMember(jss::error))
925 {
926 result[jss::status] = jss::error;
927 result["code"] = result[jss::error_code];
928 result["message"] = result[jss::error_message];
929 result.removeMember(jss::error_message);
930 JLOG(m_journal.debug()) << "rpcError: " << result[jss::error]
931 << ": " << result[jss::error_message];
932 r[jss::error] = std::move(result);
933 }
934 else
935 {
936 result[jss::status] = jss::success;
937 r[jss::result] = std::move(result);
938 }
939 }
940 else
941 {
942 // Always report "status". On an error report the request as
943 // received.
944 if (result.isMember(jss::error))
945 {
946 auto rq = params;
947
948 if (rq.isObject())
949 { // But mask potentially sensitive information.
950 if (rq.isMember(jss::passphrase.c_str()))
951 rq[jss::passphrase.c_str()] = "<masked>";
952 if (rq.isMember(jss::secret.c_str()))
953 rq[jss::secret.c_str()] = "<masked>";
954 if (rq.isMember(jss::seed.c_str()))
955 rq[jss::seed.c_str()] = "<masked>";
956 if (rq.isMember(jss::seed_hex.c_str()))
957 rq[jss::seed_hex.c_str()] = "<masked>";
958 }
959
960 result[jss::status] = jss::error;
961 result[jss::request] = rq;
962
963 JLOG(m_journal.debug()) << "rpcError: " << result[jss::error]
964 << ": " << result[jss::error_message];
965 }
966 else
967 {
968 result[jss::status] = jss::success;
969 }
970 r[jss::result] = std::move(result);
971 }
972
973 if (params.isMember(jss::jsonrpc))
974 r[jss::jsonrpc] = params[jss::jsonrpc];
975 if (params.isMember(jss::ripplerpc))
976 r[jss::ripplerpc] = params[jss::ripplerpc];
977 if (params.isMember(jss::id))
978 r[jss::id] = params[jss::id];
979 if (batch)
980 reply.append(std::move(r));
981 else
982 reply = std::move(r);
983
984 if (reply.isMember(jss::result) &&
985 reply[jss::result].isMember(jss::result))
986 {
987 reply = reply[jss::result];
988 if (reply.isMember(jss::status))
989 {
990 reply[jss::result][jss::status] = reply[jss::status];
991 reply.removeMember(jss::status);
992 }
993 }
994 }
995
996 // If we're returning an error_code, use that to determine the HTTP status.
997 int const httpStatus = [&reply]() {
998 // This feature is enabled with ripplerpc version 3.0 and above.
999 // Before ripplerpc version 3.0 always return 200.
1000 if (reply.isMember(jss::ripplerpc) &&
1001 reply[jss::ripplerpc].isString() &&
1002 reply[jss::ripplerpc].asString() >= "3.0")
1003 {
1004 // If there's an error_code, use that to determine the HTTP Status.
1005 if (reply.isMember(jss::error) &&
1006 reply[jss::error].isMember(jss::error_code) &&
1007 reply[jss::error][jss::error_code].isInt())
1008 {
1009 int const errCode = reply[jss::error][jss::error_code].asInt();
1010 return RPC::error_code_http_status(
1011 static_cast<error_code_i>(errCode));
1012 }
1013 }
1014 // Return OK.
1015 return 200;
1016 }();
1017
1018 auto response = to_string(reply);
1019
1020 rpc_time_.notify(std::chrono::duration_cast<std::chrono::milliseconds>(
1022 ++rpc_requests_;
1023 rpc_size_.notify(beast::insight::Event::value_type{response.size()});
1024
1025 response += '\n';
1026
1027 if (auto stream = m_journal.debug())
1028 {
1029 static const int maxSize = 10000;
1030 if (response.size() <= maxSize)
1031 stream << "Reply: " << response;
1032 else
1033 stream << "Reply: " << response.substr(0, maxSize);
1034 }
1035
1036 HTTPReply(httpStatus, response, output, rpcJ);
1037}
1038
1039//------------------------------------------------------------------------------
1040
1041/* This response is used with load balancing.
1042 If the server is overloaded, status 500 is reported. Otherwise status 200
1043 is reported, meaning the server can accept more connections.
1044*/
1045Handoff
1046ServerHandler::statusResponse(http_request_type const& request) const
1047{
1048 using namespace boost::beast::http;
1049 Handoff handoff;
1050 response<string_body> msg;
1051 std::string reason;
1052 if (app_.serverOkay(reason))
1053 {
1054 msg.result(boost::beast::http::status::ok);
1055 msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
1056 " Test page for rippled</title></head><body><h1>" + systemName() +
1057 " Test</h1><p>This page shows rippled http(s) "
1058 "connectivity is working.</p></body></html>";
1059 }
1060 else
1061 {
1062 msg.result(boost::beast::http::status::internal_server_error);
1063 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason +
1064 "</BODY></HTML>";
1065 }
1066 msg.version(request.version());
1067 msg.insert("Server", BuildInfo::getFullVersionString());
1068 msg.insert("Content-Type", "text/html");
1069 msg.insert("Connection", "close");
1070 msg.prepare_payload();
1071 handoff.response = std::make_shared<SimpleWriter>(msg);
1072 return handoff;
1073}
1074
1075//------------------------------------------------------------------------------
1076
1077void
1078ServerHandler::Setup::makeContexts()
1079{
1080 for (auto& p : ports)
1081 {
1082 if (p.secure())
1083 {
1084 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1085 p.context = make_SSLContext(p.ssl_ciphers);
1086 else
1087 p.context = make_SSLContextAuthed(
1088 p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1089 }
1090 else
1091 {
1092 p.context = std::make_shared<boost::asio::ssl::context>(
1093 boost::asio::ssl::context::sslv23);
1094 }
1095 }
1096}
1097
1098static Port
1099to_Port(ParsedPort const& parsed, std::ostream& log)
1100{
1101 Port p;
1102 p.name = parsed.name;
1103
1104 if (!parsed.ip)
1105 {
1106 log << "Missing 'ip' in [" << p.name << "]";
1107 Throw<std::exception>();
1108 }
1109 p.ip = *parsed.ip;
1110
1111 if (!parsed.port)
1112 {
1113 log << "Missing 'port' in [" << p.name << "]";
1114 Throw<std::exception>();
1115 }
1116 p.port = *parsed.port;
1117
1118 if (parsed.protocol.empty())
1119 {
1120 log << "Missing 'protocol' in [" << p.name << "]";
1121 Throw<std::exception>();
1122 }
1123 p.protocol = parsed.protocol;
1124
1125 p.user = parsed.user;
1126 p.password = parsed.password;
1127 p.admin_user = parsed.admin_user;
1128 p.admin_password = parsed.admin_password;
1129 p.ssl_key = parsed.ssl_key;
1130 p.ssl_cert = parsed.ssl_cert;
1131 p.ssl_chain = parsed.ssl_chain;
1132 p.ssl_ciphers = parsed.ssl_ciphers;
1133 p.pmd_options = parsed.pmd_options;
1134 p.ws_queue_limit = parsed.ws_queue_limit;
1135 p.limit = parsed.limit;
1136 p.admin_nets_v4 = parsed.admin_nets_v4;
1137 p.admin_nets_v6 = parsed.admin_nets_v6;
1140
1141 return p;
1142}
1143
1144static std::vector<Port>
1145parse_Ports(Config const& config, std::ostream& log)
1146{
1147 std::vector<Port> result;
1148
1149 if (!config.exists("server"))
1150 {
1151 log << "Required section [server] is missing";
1152 Throw<std::exception>();
1153 }
1154
1155 ParsedPort common;
1156 parse_Port(common, config["server"], log);
1157
1158 auto const& names = config.section("server").values();
1159 result.reserve(names.size());
1160 for (auto const& name : names)
1161 {
1162 if (!config.exists(name))
1163 {
1164 log << "Missing section: [" << name << "]";
1165 Throw<std::exception>();
1166 }
1167
1168 // grpc ports are parsed by GRPCServer class. Do not validate
1169 // grpc port information in this file.
1170 if (name == SECTION_PORT_GRPC)
1171 continue;
1172
1173 ParsedPort parsed = common;
1174 parse_Port(parsed, config[name], log);
1175 result.push_back(to_Port(parsed, log));
1176 }
1177
1178 if (config.standalone())
1179 {
1180 auto it = result.begin();
1181
1182 while (it != result.end())
1183 {
1184 auto& p = it->protocol;
1185
1186 // Remove the peer protocol, and if that would
1187 // leave the port empty, remove the port as well
1188 if (p.erase("peer") && p.empty())
1189 it = result.erase(it);
1190 else
1191 ++it;
1192 }
1193 }
1194 else
1195 {
1196 auto const count =
1197 std::count_if(result.cbegin(), result.cend(), [](Port const& p) {
1198 return p.protocol.count("peer") != 0;
1199 });
1200
1201 if (count > 1)
1202 {
1203 log << "Error: More than one peer protocol configured in [server]";
1204 Throw<std::exception>();
1205 }
1206
1207 if (count == 0)
1208 log << "Warning: No peer protocol configured";
1209 }
1210
1211 return result;
1212}
1213
1214// Fill out the client portion of the Setup
1215static void
1217{
1218 decltype(setup.ports)::const_iterator iter;
1219 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1220 if (iter->protocol.count("http") > 0 ||
1221 iter->protocol.count("https") > 0)
1222 break;
1223 if (iter == setup.ports.cend())
1224 return;
1225 setup.client.secure = iter->protocol.count("https") > 0;
1226 setup.client.ip = beast::IP::is_unspecified(iter->ip)
1227 ?
1228 // VFALCO HACK! to make localhost work
1229 (iter->ip.is_v6() ? "::1" : "127.0.0.1")
1230 : iter->ip.to_string();
1231 setup.client.port = iter->port;
1232 setup.client.user = iter->user;
1233 setup.client.password = iter->password;
1234 setup.client.admin_user = iter->admin_user;
1235 setup.client.admin_password = iter->admin_password;
1236}
1237
1238// Fill out the overlay portion of the Setup
1239static void
1241{
1242 auto const iter = std::find_if(
1243 setup.ports.cbegin(), setup.ports.cend(), [](Port const& port) {
1244 return port.protocol.count("peer") != 0;
1245 });
1246 if (iter == setup.ports.cend())
1247 {
1248 setup.overlay = {};
1249 return;
1250 }
1251 setup.overlay = {iter->ip, iter->port};
1252}
1253
1254ServerHandler::Setup
1256{
1258 setup.ports = parse_Ports(config, log);
1259
1260 setup_Client(setup);
1261 setup_Overlay(setup);
1262
1263 return setup;
1264}
1265
1268 Application& app,
1269 boost::asio::io_service& io_service,
1270 JobQueue& jobQueue,
1271 NetworkOPs& networkOPs,
1272 Resource::Manager& resourceManager,
1273 CollectorManager& cm)
1274{
1275 return std::make_unique<ServerHandler>(
1277 app,
1278 io_service,
1279 jobQueue,
1280 networkOPs,
1281 resourceManager,
1282 cm);
1283}
1284
1285} // namespace ripple
T append(T... args)
T begin(T... args)
Decorator for streaming out compact json.
Definition: json_writer.h:318
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
std::string getFormatedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:148
bool isArray() const
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
bool isObjectOrNull() const
Int asInt() const
Definition: json_value.cpp:509
bool isString() const
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:897
bool isObject() const
Value removeMember(const char *key)
Remove and return the named member.
Definition: json_value.cpp:922
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isNull() const
isNull() tests to see if this field is null.
Definition: json_value.cpp:986
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
bool isInt() const
Definition: json_value.cpp:998
A version-independent IP address and port combination.
Definition: IPEndpoint.h:39
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
virtual Config & config()=0
virtual Overlay & overlay()=0
virtual beast::Journal journal(std::string const &name)=0
virtual NetworkOPs & getOPs()=0
virtual LedgerMaster & getLedgerMaster()=0
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Section & section(std::string const &name)
Returns the section with the given name.
Provides the beast::insight::Collector service.
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
bool standalone() const
Definition: Config.h:337
bool BETA_RPC_API
Definition: Config.h:288
A pool of threads to perform work.
Definition: JobQueue.h:55
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Definition: JobQueue.h:410
Provides server functionality for clients.
Definition: NetworkOPs.h:87
virtual Handoff onHandoff(std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)=0
Conditionally accept an incoming HTTP request.
A consumption charge.
Definition: Charge.h:31
An endpoint that consumes resources.
Definition: Consumer.h:35
bool warn()
Returns true if the consumer should be warned.
Definition: Consumer.cpp:117
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
Definition: Consumer.cpp:124
Disposition charge(Charge const &fee, std::string const &context={})
Apply a load charge to the consumer.
Definition: Consumer.cpp:106
Tracks load and resource consumption.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition: BasicConfig.h:79
Resource::Manager & m_resourceManager
Definition: ServerHandler.h:87
std::condition_variable condition_
Definition: ServerHandler.h:98
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
std::unique_ptr< Server > m_server
Definition: ServerHandler.h:90
beast::insight::Event rpc_size_
Definition: ServerHandler.h:95
Setup const & setup() const
beast::insight::Counter rpc_requests_
Definition: ServerHandler.h:94
beast::Journal m_journal
Definition: ServerHandler.h:88
void onClose(Session &session, boost::system::error_code const &)
Handoff statusResponse(http_request_type const &request) const
NetworkOPs & m_networkOPs
Definition: ServerHandler.h:89
Application & app_
Definition: ServerHandler.h:86
beast::insight::Event rpc_time_
Definition: ServerHandler.h:96
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
std::map< std::reference_wrapper< Port const >, int > count_
void onStopped(Server &)
void onRequest(Session &session)
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output &&, std::shared_ptr< JobQueue::Coro > coro, std::string_view forwardedFor, std::string_view user)
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remote_address)
A multi-protocol server.
Definition: ServerImpl.h:48
Persistent state information for a connection session.
Definition: Session.h:43
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
virtual void close(bool graceful)=0
Close the session.
virtual http_request_type & request()=0
Returns the current HTTP request.
void write(std::string const &s)
Send a copy of data asynchronously.
Definition: Session.h:76
T count(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T insert(T... args)
T make_shared(T... args)
void stream(Json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
Definition: json_writer.h:301
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
int Int
Definition: json_forwards.h:26
unsigned int UInt
Definition: json_forwards.h:27
Endpoint from_asio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition: IPAddress.h:61
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition: rfc2616.h:388
std::string const & getFullVersionString()
Full server version string.
Definition: BuildInfo.cpp:81
static int constexpr maxRequestSize
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Definition: ErrorCodes.cpp:185
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
Definition: RPCHandler.cpp:260
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
Definition: RPCHandler.cpp:221
static constexpr auto apiInvalidVersion
Definition: ApiVersion.h:56
Charge const feeReferenceRPC
Charge const feeMalformedRPC
TER authorized(ApplyContext const &ctx, AccountID const &dst)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
void HTTPReply(int nStatus, std::string const &strMsg, Json::Output const &, beast::Journal j)
Definition: JSONRPCUtil.cpp:58
static std::vector< Port > parse_Ports(Config const &config, std::ostream &log)
static Port to_Port(ParsedPort const &parsed, std::ostream &log)
static Json::Output makeOutput(Session &session)
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, std::string_view user, std::string_view forwardedFor)
Definition: Role.cpp:142
Json::Int constexpr wrong_version
error_code_i
Definition: ErrorCodes.h:40
@ rpcSLOW_DOWN
Definition: ErrorCodes.h:57
@ rpcINTERNAL
Definition: ErrorCodes.h:130
@ rpcFORBIDDEN
Definition: ErrorCodes.h:48
static Json::Value make_json_error(Json::Int code, Json::Value &&message)
std::string base64_decode(std::string_view data)
Definition: base64.cpp:248
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_service &io_service, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition: Port.cpp:213
Json::Value rpcError(int iError)
Definition: RPCErr.cpp:31
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition: Role.cpp:125
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
Json::Int constexpr method_not_found
Json::Int constexpr forbidden
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &&log)
void logDuration(Json::Value const &request, T const &duration, beast::Journal &journal)
std::string_view forwardedFor(http_request_type const &request)
Definition: Role.cpp:262
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handoff.h:33
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
static std::string buffers_to_string(ConstBufferSequence const &bs)
static void setup_Client(ServerHandler::Setup &setup)
std::shared_ptr< boost::asio::ssl::context > make_SSLContextAuthed(std::string const &keyFile, std::string const &certFile, std::string const &chainFile, std::string const &cipherList)
Create an authenticated SSL context using the specified files.
std::unique_ptr< Server > make_Server(Handler &handler, boost::asio::io_service &io_service, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition: Server.h:35
Overlay::Setup setup_Overlay(BasicConfig const &config)
@ jtCLIENT_RPC
Definition: Job.h:49
@ jtCLIENT_WEBSOCKET
Definition: Job.h:50
Role requestRole(Role const &required, Port const &port, Json::Value const &params, beast::IP::Endpoint const &remoteIp, std::string_view user)
Return the allowed privilege role.
Definition: Role.cpp:95
static std::map< std::string, std::string > build_map(boost::beast::http::fields const &h)
static bool isStatusRequest(http_request_type const &request)
Json::Int constexpr server_overloaded
T push_back(T... args)
T remove_suffix(T... args)
T reserve(T... args)
T size(T... args)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
Used to indicate the result of a server connection handoff.
Definition: Handoff.h:40
std::shared_ptr< Writer > response
Definition: Handoff.h:49
std::string ssl_ciphers
Definition: Port.h:110
boost::beast::websocket::permessage_deflate pmd_options
Definition: Port.h:111
std::optional< std::uint16_t > port
Definition: Port.h:116
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition: Port.h:117
std::string user
Definition: Port.h:103
std::string ssl_key
Definition: Port.h:107
std::uint16_t ws_queue_limit
Definition: Port.h:113
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition: Port.h:120
std::set< std::string, boost::beast::iless > protocol
Definition: Port.h:102
std::string admin_password
Definition: Port.h:106
std::string name
Definition: Port.h:101
std::string ssl_chain
Definition: Port.h:109
std::string password
Definition: Port.h:104
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition: Port.h:118
std::string ssl_cert
Definition: Port.h:108
std::string admin_user
Definition: Port.h:105
std::optional< boost::asio::ip::address > ip
Definition: Port.h:115
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition: Port.h:119
Configuration information for a Server listening port.
Definition: Port.h:50
std::uint16_t port
Definition: Port.h:55
std::string ssl_chain
Definition: Port.h:67
std::string password
Definition: Port.h:62
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition: Port.h:58
std::set< std::string, boost::beast::iless > protocol
Definition: Port.h:56
std::string ssl_cert
Definition: Port.h:66
int limit
Definition: Port.h:74
std::string ssl_key
Definition: Port.h:65
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition: Port.h:60
std::string admin_user
Definition: Port.h:63
std::string user
Definition: Port.h:61
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition: Port.h:59
boost::asio::ip::address ip
Definition: Port.h:54
std::uint16_t ws_queue_limit
Definition: Port.h:77
std::string ssl_ciphers
Definition: Port.h:68
std::string admin_password
Definition: Port.h:64
std::string name
Definition: Port.h:53
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition: Port.h:57
boost::beast::websocket::permessage_deflate pmd_options
Definition: Port.h:69
boost::asio::ip::tcp::endpoint overlay
Definition: ServerHandler.h:76
std::vector< Port > ports
Definition: ServerHandler.h:56
T substr(T... args)
T transform(T... args)
T what(T... args)