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