xrpld
Loading...
Searching...
No Matches
ServerHandler.cpp
1#include <xrpld/rpc/ServerHandler.h>
2
3#include <xrpld/app/main/Application.h>
4#include <xrpld/overlay/Overlay.h>
5#include <xrpld/rpc/RPCHandler.h>
6#include <xrpld/rpc/Role.h>
7#include <xrpld/rpc/detail/Tuning.h>
8#include <xrpld/rpc/detail/WSInfoSub.h>
9
10#include <xrpl/basics/Log.h>
11#include <xrpl/basics/base64.h>
12#include <xrpl/basics/contract.h>
13#include <xrpl/basics/make_SSLContext.h>
14#include <xrpl/beast/net/IPAddress.h>
15#include <xrpl/beast/net/IPAddressConversion.h>
16#include <xrpl/beast/rfc2616.h>
17#include <xrpl/beast/utility/Journal.h>
18#include <xrpl/config/Constants.h>
19#include <xrpl/core/Job.h>
20#include <xrpl/core/JobQueue.h>
21#include <xrpl/json/Output.h>
22#include <xrpl/json/json_forwards.h>
23#include <xrpl/json/json_reader.h>
24#include <xrpl/json/json_value.h>
25#include <xrpl/json/json_writer.h>
26#include <xrpl/json/to_string.h>
27#include <xrpl/protocol/ApiVersion.h>
28#include <xrpl/protocol/BuildInfo.h>
29#include <xrpl/protocol/ErrorCodes.h>
30#include <xrpl/protocol/RPCErr.h>
31#include <xrpl/protocol/SystemParameters.h>
32#include <xrpl/protocol/jss.h>
33#include <xrpl/resource/Charge.h>
34#include <xrpl/resource/Consumer.h>
35#include <xrpl/resource/Fees.h>
36#include <xrpl/resource/ResourceManager.h>
37#include <xrpl/server/Handoff.h>
38#include <xrpl/server/InfoSub.h>
39#include <xrpl/server/NetworkOPs.h>
40#include <xrpl/server/Port.h>
41#include <xrpl/server/Server.h>
42#include <xrpl/server/Session.h>
43#include <xrpl/server/SimpleWriter.h>
44#include <xrpl/server/WSSession.h>
45#include <xrpl/server/detail/JSONRPCUtil.h>
46
47#include <boost/algorithm/string/trim.hpp>
48#include <boost/asio/buffer.hpp>
49#include <boost/asio/io_context.hpp>
50#include <boost/asio/ip/tcp.hpp>
51#include <boost/beast/core/multi_buffer.hpp>
52#include <boost/beast/http/fields.hpp>
53#include <boost/beast/http/status.hpp>
54#include <boost/beast/http/string_body.hpp>
55#include <boost/beast/http/verb.hpp>
56#include <boost/beast/websocket/impl/rfc6455.hpp>
57#include <boost/beast/websocket/rfc6455.hpp>
58#include <boost/system/detail/error_code.hpp>
59
60#include <algorithm>
61#include <cctype>
62#include <chrono>
63#include <exception>
64#include <map>
65#include <memory>
66#include <mutex>
67#include <ostream>
68#include <string>
69#include <string_view>
70#include <utility>
71#include <vector>
72
73namespace xrpl {
74
75class Peer;
76class LedgerMaster;
77class Transaction;
78class ValidatorKeys;
79class CanonicalTXSet;
80
81static bool
83{
84 return request.version() >= 11 && request.target() == "/" && request.body().size() == 0 &&
85 request.method() == boost::beast::http::verb::get;
86}
87
88static Handoff
89statusRequestResponse(http_request_type const& request, boost::beast::http::status status)
90{
91 using namespace boost::beast::http;
92 Handoff handoff;
93 response<string_body> msg;
94 msg.version(request.version());
95 msg.result(status);
96 msg.insert("Server", BuildInfo::getFullVersionString());
97 msg.insert("Content-Type", "text/html");
98 msg.insert("Connection", "close");
99 msg.body() = "Invalid protocol.";
100 msg.prepare_payload();
102 return handoff;
103}
104
105// VFALCO TODO Rewrite to use boost::beast::http::fields
106static bool
108{
109 if (port.user.empty() || port.password.empty())
110 return true;
111
112 auto const it = h.find("authorization");
113 if ((it == h.end()) || (!it->second.starts_with("Basic ")))
114 return false;
115 std::string strUserPass64 = it->second.substr(6);
116 boost::trim(strUserPass64);
117 std::string const strUserPass = base64Decode(strUserPass64);
118 std::string::size_type const nColon = strUserPass.find(':');
119 if (nColon == std::string::npos)
120 return false;
121 std::string const strUser = strUserPass.substr(0, nColon);
122 std::string const strPassword = strUserPass.substr(nColon + 1);
123 return strUser == port.user && strPassword == port.password;
124}
125
128 Application& app,
129 boost::asio::io_context& ioContext,
130 JobQueue& jobQueue,
131 NetworkOPs& networkOPs,
132 Resource::Manager& resourceManager,
134 : app_(app)
135 , resourceManager_(resourceManager)
136 , journal_(app_.getJournal("Server"))
137 , networkOPs_(networkOPs)
138 , server_(makeServer(*this, ioContext, app_.getJournal("Server")))
139 , jobQueue_(jobQueue)
140{
141 auto const& group(cm.group("rpc"));
142 rpcRequests_ = group->makeCounter("requests");
143 rpcSize_ = group->makeEvent("size");
144 rpcTime_ = group->makeEvent("time");
145}
146
148{
149 server_ = nullptr;
150}
151
152void
154{
155 setup_ = setup;
156 endpoints_ = server_->ports(setup.ports);
157
158 // fix auto ports
159 for (auto& port : setup_.ports)
160 {
161 if (auto it = endpoints_.find(port.name); it != endpoints_.end())
162 {
163 auto const endpointPort = it->second.port();
164 if (port.port == 0u)
165 port.port = endpointPort;
166
167 if ((setup_.client.port == 0u) &&
168 (port.protocol.contains("http") || port.protocol.contains("https")))
169 setup_.client.port = endpointPort;
170
171 if ((setup_.overlay.port() == 0u) && (port.protocol.contains("peer")))
172 setup_.overlay.port(endpointPort);
173 }
174 }
175}
176
177//------------------------------------------------------------------------------
178
179void
181{
182 server_->close();
183 {
185 condition_.wait(lock, [this] { return stopped_; });
186 }
187}
188
189//------------------------------------------------------------------------------
190
191bool
192ServerHandler::onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
193{
194 auto const& port = session.port();
195
196 auto const c = [this, &port]() {
197 std::scoped_lock const lock(mutex_);
198 return ++count_[port];
199 }();
200
201 if ((port.limit != 0) && c >= port.limit)
202 {
203 JLOG(journal_.trace()) << port.name << " is full; dropping " << endpoint;
204 return false;
205 }
206
207 return true;
208}
209
212 Session& session,
214 http_request_type&& request,
215 boost::asio::ip::tcp::endpoint const& remoteAddress)
216{
217 using namespace boost::beast;
218 auto const& p{session.port().protocol};
219 bool const isWs{
220 p.contains("ws") || p.contains("ws2") || p.contains("wss") || p.contains("wss2")};
221
222 if (websocket::is_upgrade(request))
223 {
224 if (!isWs)
225 return statusRequestResponse(request, http::status::unauthorized);
226
228 try
229 {
230 ws = session.websocketUpgrade();
231 }
232 catch (std::exception const& e)
233 {
234 JLOG(journal_.error()) << "Exception upgrading websocket: " << e.what() << "\n";
235 return statusRequestResponse(request, http::status::internal_server_error);
236 }
237
239 auto const beastRemoteAddress = beast::IPAddressConversion::fromAsio(remoteAddress);
240 is->getConsumer() = requestInboundEndpoint(
242 beastRemoteAddress,
243 requestRole(Role::GUEST, session.port(), json::Value(), beastRemoteAddress, is->user()),
244 is->user(),
245 is->forwardedFor());
246 ws->appDefined = std::move(is);
247 ws->run();
248
249 Handoff handoff;
250 handoff.moved = true;
251 return handoff;
252 }
253
254 if (bundle && p.contains("peer"))
255 return app_.getOverlay().onHandoff(std::move(bundle), std::move(request), remoteAddress);
256
257 if (isWs && isStatusRequest(request))
258 return statusResponse(request);
259
260 // Otherwise pass to legacy onRequest or websocket
261 return {};
262}
263
264static inline json::Output
266{
267 return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
268}
269
271buildMap(boost::beast::http::fields const& h)
272{
274 for (auto const& e : h)
275 {
276 // key cannot be a std::string_view because it needs to be used in
277 // map and along with iterators
278 std::string key(e.name_string());
280 key, key.begin(), [](auto kc) { return std::tolower(static_cast<unsigned char>(kc)); });
281 c[key] = e.value();
282 }
283 return c;
284}
285
286template <class ConstBufferSequence>
287static std::string
288buffersToString(ConstBufferSequence const& bs)
289{
290 using boost::asio::buffer_size;
291 std::string s;
292 s.reserve(buffer_size(bs));
293 // Use auto&& so the right thing happens whether bs returns a copy or
294 // a reference
295 for (auto&& b : bs)
296 s.append(static_cast<char const*>(b.data()), buffer_size(b));
297 return s;
298}
299
300void
302{
303 // Make sure RPC is enabled on the port
304 if (!session.port().protocol.contains("http") && !session.port().protocol.contains("https"))
305 {
306 httpReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC"));
307 session.close(true);
308 return;
309 }
310
311 // Check user/password authorization
312 if (!authorized(session.port(), buildMap(session.request())))
313 {
314 httpReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC"));
315 session.close(true);
316 return;
317 }
318
319 std::shared_ptr<Session> const detachedSession = session.detach();
320 auto const postResult = jobQueue_.postCoro(
321 JtClientRpc, "RPC-Client", [this, detachedSession](std::shared_ptr<JobQueue::Coro> coro) {
322 processSession(detachedSession, coro);
323 });
324 if (postResult == nullptr)
325 {
326 // The coroutine was rejected, probably because we're shutting down.
327 httpReply(503, "Service Unavailable", makeOutput(*detachedSession), app_.getJournal("RPC"));
328 detachedSession->close(true);
329 return;
330 }
331}
332
333void
337{
338 json::Value jv;
339 auto const size = boost::asio::buffer_size(buffers);
340 if (size > RPC::Tuning::kMaxRequestSize || !json::Reader{}.parse(jv, buffers) || !jv.isObject())
341 {
343 jvResult[jss::type] = jss::error;
344 jvResult[jss::error] = "jsonInvalid";
345 jvResult[jss::value] = buffersToString(buffers);
346 boost::beast::multi_buffer sb;
347 json::stream(jvResult, [&sb](auto const p, auto const n) {
348 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(p, n)));
349 });
350 JLOG(journal_.trace()) << "Websocket sending '" << jvResult << "'";
351 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
352 session->complete();
353 return;
354 }
355
356 JLOG(journal_.trace()) << "Websocket received '" << jv << "'";
357
358 auto const postResult = jobQueue_.postCoro(
360 "WS-Client",
361 [this, session, jv = std::move(jv)](std::shared_ptr<JobQueue::Coro> const& coro) {
362 auto const jr = this->processSession(session, coro, jv);
363 auto const s = to_string(jr);
364 auto const n = s.length();
365 boost::beast::multi_buffer sb(n);
366 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
367 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
368 session->complete();
369 });
370 if (postResult == nullptr)
371 {
372 // The coroutine was rejected, probably because we're shutting down.
373 session->close({boost::beast::websocket::going_away, "Shutting Down"});
374 }
375}
376
377void
378ServerHandler::onClose(Session& session, boost::system::error_code const&)
379{
380 std::scoped_lock const lock(mutex_);
381 --count_[session.port()];
382}
383
384void
386{
387 std::scoped_lock const lock(mutex_);
388 stopped_ = true;
389 condition_.notify_one();
390}
391
392//------------------------------------------------------------------------------
393
394template <class T>
395void
396logDuration(json::Value const& request, T const& duration, beast::Journal& journal)
397{
398 using namespace std::chrono_literals;
399 auto const level = [&]() {
400 if (duration >= 10s)
401 return journal.error();
402 if (duration >= 1s)
403 return journal.warn();
404 return journal.debug();
405 }();
406
407 JLOG(level) << "RPC request processing duration = "
409 << " microseconds. request = " << request;
410}
411
414 std::shared_ptr<WSSession> const& session,
416 json::Value const& jv)
417{
418 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
419 if (is->getConsumer().disconnect(journal_))
420 {
421 session->close({boost::beast::websocket::policy_error, "threshold exceeded"});
422 // FIX: This rpcError is not delivered since the session
423 // was just closed.
424 return rpcError(RpcSlowDown);
425 }
426
427 // Requests without "command" are invalid.
430 try
431 {
432 auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().betaRpcApi);
433 if (apiVersion == RPC::kApiInvalidVersion ||
434 (!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
435 (jv.isMember(jss::command) && !jv[jss::command].isString()) ||
436 (jv.isMember(jss::method) && !jv[jss::method].isString()) ||
437 (jv.isMember(jss::command) && jv.isMember(jss::method) &&
438 jv[jss::command].asString() != jv[jss::method].asString()))
439 {
440 jr[jss::type] = jss::response;
441 jr[jss::status] = jss::error;
442 jr[jss::error] = apiVersion == RPC::kApiInvalidVersion ? jss::invalid_API_version
443 : jss::missingCommand;
444 jr[jss::request] = jv;
445 if (jv.isMember(jss::id))
446 jr[jss::id] = jv[jss::id];
447 if (jv.isMember(jss::jsonrpc))
448 jr[jss::jsonrpc] = jv[jss::jsonrpc];
449 if (jv.isMember(jss::ripplerpc))
450 jr[jss::ripplerpc] = jv[jss::ripplerpc];
451 if (jv.isMember(jss::api_version))
452 jr[jss::api_version] = jv[jss::api_version];
453
454 is->getConsumer().charge(Resource::kFeeMalformedRpc);
455 return jr;
456 }
457
458 auto required = RPC::roleRequired(
459 apiVersion,
460 app_.config().betaRpcApi,
461 jv.isMember(jss::command) ? jv[jss::command].asString() : jv[jss::method].asString());
462 auto role = requestRole(
463 required,
464 session->port(),
465 jv,
466 beast::IP::fromAsio(session->remoteEndpoint().address()),
467 is->user());
468 if (Role::FORBID == role)
469 {
471 jr[jss::result] = rpcError(RpcForbidden);
472 }
473 else
474 {
475 RPC::JsonContext context{
476 {.j = app_.getJournal("RPCHandler"),
477 .app = app_,
478 .loadType = loadType,
479 .netOps = app_.getOPs(),
480 .ledgerMaster = app_.getLedgerMaster(),
481 .consumer = is->getConsumer(),
482 .role = role,
483 .coro = coro,
484 .infoSub = is,
485 .apiVersion = apiVersion},
486 jv,
487 {.user = is->user(), .forwardedFor = is->forwardedFor()}};
488
489 auto start = std::chrono::system_clock::now();
490 RPC::doCommand(context, jr[jss::result]);
492 logDuration(jv, end - start, journal_);
493 }
494 }
495 catch (std::exception const& ex)
496 {
497 // LCOV_EXCL_START
498 jr[jss::result] = RPC::makeError(RpcInternal);
499 JLOG(journal_.error()) << "Exception while processing WS: " << ex.what() << "\n"
500 << "Input JSON: " << json::Compact{json::Value{jv}};
501 // LCOV_EXCL_STOP
502 }
503
504 is->getConsumer().charge(loadType);
505 if (is->getConsumer().warn())
506 jr[jss::warning] = jss::load;
507
508 // Currently we will simply unwrap errors returned by the RPC
509 // API, in the future maybe we can make the responses
510 // consistent.
511 //
512 // Regularize result. This is duplicate code.
513 if (jr[jss::result].isMember(jss::error))
514 {
515 jr = jr[jss::result];
516 jr[jss::status] = jss::error;
517
518 auto rq = jv;
519
520 if (rq.isObject())
521 {
522 if (rq.isMember(jss::passphrase.cStr()))
523 rq[jss::passphrase.cStr()] = "<masked>";
524 if (rq.isMember(jss::secret.cStr()))
525 rq[jss::secret.cStr()] = "<masked>";
526 if (rq.isMember(jss::seed.cStr()))
527 rq[jss::seed.cStr()] = "<masked>";
528 if (rq.isMember(jss::seed_hex.cStr()))
529 rq[jss::seed_hex.cStr()] = "<masked>";
530 }
531
532 jr[jss::request] = rq;
533 }
534 else
535 {
536 if (jr[jss::result].isMember("forwarded") && jr[jss::result]["forwarded"])
537 jr = jr[jss::result];
538 jr[jss::status] = jss::success;
539 }
540
541 if (jv.isMember(jss::id))
542 jr[jss::id] = jv[jss::id];
543 if (jv.isMember(jss::jsonrpc))
544 jr[jss::jsonrpc] = jv[jss::jsonrpc];
545 if (jv.isMember(jss::ripplerpc))
546 jr[jss::ripplerpc] = jv[jss::ripplerpc];
547 if (jv.isMember(jss::api_version))
548 jr[jss::api_version] = jv[jss::api_version];
549
550 jr[jss::type] = jss::response;
551 return jr;
552}
553
554// Run as a coroutine.
555void
557 std::shared_ptr<Session> const& session,
559{
561 session->port(),
562 buffersToString(session->request().body().data()),
563 session->remoteAddress().atPort(0),
564 makeOutput(*session),
565 coro,
566 forwardedFor(session->request()),
567 [&] {
568 auto const iter = session->request().find("X-User");
569 if (iter != session->request().end())
570 return iter->value();
571 return boost::beast::string_view{};
572 }());
573
574 if (beast::rfc2616::isKeepAlive(session->request()))
575 {
576 session->complete();
577 }
578 else
579 {
580 session->close(true);
581 }
582}
583
584static json::Value
586{
588 sub["code"] = code;
589 sub["message"] = std::move(message);
591 r["error"] = sub;
592 return r;
593}
594
595constexpr json::Int kMethodNotFound = -32601;
596constexpr json::Int kServerOverloaded = -32604;
597constexpr json::Int kForbidden = -32605;
598constexpr json::Int kWrongVersion = -32606;
599
600void
602 Port const& port,
603 std::string const& request,
604 beast::IP::Endpoint const& remoteIPAddress,
605 Output const& output,
608 std::string_view user)
609{
610 auto rpcJ = app_.getJournal("RPC");
611
612 json::Value jsonOrig;
613 {
614 json::Reader reader;
615 if ((request.size() > RPC::Tuning::kMaxRequestSize) || !reader.parse(request, jsonOrig) ||
616 !jsonOrig || !jsonOrig.isObject())
617 {
618 httpReply(
619 400,
620 "Unable to parse request: " + reader.getFormattedErrorMessages(),
621 output,
622 rpcJ);
623 return;
624 }
625 }
626
627 bool batch = false;
628 unsigned size = 1;
629 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
630 {
631 batch = true;
632 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
633 {
634 httpReply(400, "Malformed batch request", output, rpcJ);
635 return;
636 }
637 size = jsonOrig[jss::params].size();
638 }
639
641 auto const start(std::chrono::high_resolution_clock::now());
642 for (unsigned i = 0; i < size; ++i)
643 {
644 json::Value const& jsonRPC = batch ? jsonOrig[jss::params][i] : jsonOrig;
645
646 if (!jsonRPC.isObject())
647 {
649 r[jss::request] = jsonRPC;
650 r[jss::error] = makeJsonError(kMethodNotFound, "Method not found");
651 reply.append(r);
652 continue;
653 }
654
655 unsigned apiVersion = RPC::kApiVersionIfUnspecified;
656 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
657 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject())
658 {
659 apiVersion = RPC::getAPIVersionNumber(
660 jsonRPC[jss::params][json::UInt(0)], app_.config().betaRpcApi);
661 }
662
663 if (apiVersion == RPC::kApiVersionIfUnspecified && batch)
664 {
665 // for batch request, api_version may be at a different level
666 apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().betaRpcApi);
667 }
668
669 if (apiVersion == RPC::kApiInvalidVersion)
670 {
671 if (!batch)
672 {
673 httpReply(400, jss::invalid_API_version.cStr(), output, rpcJ);
674 return;
675 }
677 r[jss::request] = jsonRPC;
678 r[jss::error] = makeJsonError(kWrongVersion, jss::invalid_API_version.cStr());
679 reply.append(r);
680 continue;
681 }
682
683 /* ------------------------------------------------------------------ */
684 auto role = Role::FORBID;
685 auto required = Role::FORBID;
686 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
687 {
688 required = RPC::roleRequired(
689 apiVersion, app_.config().betaRpcApi, jsonRPC[jss::method].asString());
690 }
691
692 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
693 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][json::UInt(0)].isObjectOrNull())
694 {
695 role = requestRole(
696 required, port, jsonRPC[jss::params][json::UInt(0)], remoteIPAddress, user);
697 }
698 else
699 {
700 role = requestRole(required, port, json::ValueType::Object, remoteIPAddress, user);
701 }
702
703 Resource::Consumer usage;
704 if (isUnlimited(role))
705 {
706 usage = resourceManager_.newUnlimitedEndpoint(remoteIPAddress);
707 }
708 else
709 {
710 usage = resourceManager_.newInboundEndpoint(
711 remoteIPAddress, role == Role::PROXY, forwardedFor);
712 if (usage.disconnect(journal_))
713 {
714 if (!batch)
715 {
716 httpReply(503, "Server is overloaded", output, rpcJ);
717 return;
718 }
719 json::Value r = jsonRPC;
720 r[jss::error] = makeJsonError(kServerOverloaded, "Server is overloaded");
721 reply.append(r);
722 continue;
723 }
724 }
725
726 if (role == Role::FORBID)
727 {
729 if (!batch)
730 {
731 httpReply(403, "Forbidden", output, rpcJ);
732 return;
733 }
734 json::Value r = jsonRPC;
735 r[jss::error] = makeJsonError(kForbidden, "Forbidden");
736 reply.append(r);
737 continue;
738 }
739
740 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
741 {
743 if (!batch)
744 {
745 httpReply(400, "Null method", output, rpcJ);
746 return;
747 }
748 json::Value r = jsonRPC;
749 r[jss::error] = makeJsonError(kMethodNotFound, "Null method");
750 reply.append(r);
751 continue;
752 }
753
754 json::Value const& method = jsonRPC[jss::method];
755 if (!method.isString())
756 {
758 if (!batch)
759 {
760 httpReply(400, "method is not string", output, rpcJ);
761 return;
762 }
763 json::Value r = jsonRPC;
764 r[jss::error] = makeJsonError(kMethodNotFound, "method is not string");
765 reply.append(r);
766 continue;
767 }
768
769 std::string const strMethod = method.asString();
770 if (strMethod.empty())
771 {
773 if (!batch)
774 {
775 httpReply(400, "method is empty", output, rpcJ);
776 return;
777 }
778 json::Value r = jsonRPC;
779 r[jss::error] = makeJsonError(kMethodNotFound, "method is empty");
780 reply.append(r);
781 continue;
782 }
783
784 // Extract request parameters from the request Json as `params`.
785 //
786 // If the field "params" is empty, `params` is an empty object.
787 //
788 // Otherwise, that field must be an array of length 1 (why?)
789 // and we take that first entry and validate that it's an object.
790 json::Value params;
791 if (!batch)
792 {
793 params = jsonRPC[jss::params];
794 if (!params)
795 {
797 }
798 else if (!params.isArray() || params.size() != 1)
799 {
801 httpReply(400, "params unparsable", output, rpcJ);
802 return;
803 }
804 else
805 {
806 params = std::move(params[0u]);
807 if (!params.isObjectOrNull())
808 {
810 httpReply(400, "params unparsable", output, rpcJ);
811 return;
812 }
813 }
814 }
815 else // batch
816 {
817 params = jsonRPC;
818 }
819
820 std::string ripplerpc = "1.0";
821 if (params.isMember(jss::ripplerpc))
822 {
823 if (!params[jss::ripplerpc].isString())
824 {
826 if (!batch)
827 {
828 httpReply(400, "ripplerpc is not a string", output, rpcJ);
829 return;
830 }
831
832 json::Value r = jsonRPC;
833 r[jss::error] = makeJsonError(kMethodNotFound, "ripplerpc is not a string");
834 reply.append(r);
835 continue;
836 }
837 ripplerpc = params[jss::ripplerpc].asString();
838 }
839
844 if (role != Role::IDENTIFIED && role != Role::PROXY)
845 {
846 forwardedFor.remove_suffix(forwardedFor.size());
847 user.remove_suffix(user.size());
848 }
849
850 JLOG(journal_.debug()) << "Query: " << strMethod << params;
851
852 // Provide the JSON-RPC method as the field "command" in the request.
853 params[jss::command] = strMethod;
854 JLOG(journal_.trace()) << "doRpcCommand:" << strMethod << ":" << params;
855
857
858 RPC::JsonContext context{
859 {.j = journal_,
860 .app = app_,
861 .loadType = loadType,
862 .netOps = networkOPs_,
863 .ledgerMaster = app_.getLedgerMaster(),
864 .consumer = usage,
865 .role = role,
866 .coro = coro,
867 .infoSub = InfoSub::pointer(),
868 .apiVersion = apiVersion},
869 params,
870 {.user = user, .forwardedFor = forwardedFor}};
871 json::Value result;
872
873 auto start = std::chrono::system_clock::now();
874
875 try
876 {
877 RPC::doCommand(context, result);
878 }
879 catch (std::exception const& ex)
880 {
881 // LCOV_EXCL_START
882 result = RPC::makeError(RpcInternal);
883 JLOG(journal_.error())
884 << "Internal error : " << ex.what()
885 << " when processing request: " << json::Compact{json::Value{params}};
886 // LCOV_EXCL_STOP
887 }
888
890
891 logDuration(params, end - start, journal_);
892
893 usage.charge(loadType);
894 if (usage.warn())
895 result[jss::warning] = jss::load;
896
898 if (ripplerpc >= "2.0")
899 {
900 if (result.isMember(jss::error))
901 {
902 result[jss::status] = jss::error;
903 result["code"] = result[jss::error_code];
904 result["message"] = result[jss::error_message];
905 result.removeMember(jss::error_message);
906 JLOG(journal_.debug())
907 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
908 r[jss::error] = std::move(result);
909 }
910 else
911 {
912 result[jss::status] = jss::success;
913 r[jss::result] = std::move(result);
914 }
915 }
916 else
917 {
918 // Always report "status". On an error report the request as
919 // received.
920 if (result.isMember(jss::error))
921 {
922 auto rq = params;
923
924 if (rq.isObject())
925 { // But mask potentially sensitive information.
926 if (rq.isMember(jss::passphrase.cStr()))
927 rq[jss::passphrase.cStr()] = "<masked>";
928 if (rq.isMember(jss::secret.cStr()))
929 rq[jss::secret.cStr()] = "<masked>";
930 if (rq.isMember(jss::seed.cStr()))
931 rq[jss::seed.cStr()] = "<masked>";
932 if (rq.isMember(jss::seed_hex.cStr()))
933 rq[jss::seed_hex.cStr()] = "<masked>";
934 }
935
936 result[jss::status] = jss::error;
937 result[jss::request] = rq;
938
939 JLOG(journal_.debug())
940 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
941 }
942 else
943 {
944 result[jss::status] = jss::success;
945 }
946 r[jss::result] = std::move(result);
947 }
948
949 if (params.isMember(jss::jsonrpc))
950 r[jss::jsonrpc] = params[jss::jsonrpc];
951 if (params.isMember(jss::ripplerpc))
952 r[jss::ripplerpc] = params[jss::ripplerpc];
953 if (params.isMember(jss::id))
954 r[jss::id] = params[jss::id];
955 if (batch)
956 {
957 reply.append(std::move(r));
958 }
959 else
960 {
961 reply = std::move(r);
962 }
963
964 if (reply.isMember(jss::result) && reply[jss::result].isMember(jss::result))
965 {
966 reply = reply[jss::result];
967 if (reply.isMember(jss::status))
968 {
969 reply[jss::result][jss::status] = reply[jss::status];
970 reply.removeMember(jss::status);
971 }
972 }
973 }
974
975 // If we're returning an error_code, use that to determine the HTTP status.
976 int const httpStatus = [&reply]() {
977 // This feature is enabled with ripplerpc version 3.0 and above.
978 // Before ripplerpc version 3.0 always return 200.
979 if (reply.isMember(jss::ripplerpc) && reply[jss::ripplerpc].isString() &&
980 reply[jss::ripplerpc].asString() >= "3.0")
981 {
982 // If there's an error_code, use that to determine the HTTP Status.
983 if (reply.isMember(jss::error) && reply[jss::error].isMember(jss::error_code) &&
984 reply[jss::error][jss::error_code].isInt())
985 {
986 int const errCode = reply[jss::error][jss::error_code].asInt();
987 return RPC::errorCodeHttpStatus(static_cast<ErrorCodeI>(errCode));
988 }
989 }
990 // Return OK.
991 return 200;
992 }();
993
994 auto response = to_string(reply);
995
996 rpcTime_.notify(
999 ++rpcRequests_;
1000 rpcSize_.notify(beast::insight::Event::value_type{response.size()});
1001
1002 response += '\n';
1003
1004 if (auto stream = journal_.debug())
1005 {
1006 static int const kMaxSize = 10000;
1007 if (response.size() <= kMaxSize)
1008 {
1009 stream << "Reply: " << response;
1010 }
1011 else
1012 {
1013 stream << "Reply: " << response.substr(0, kMaxSize);
1014 }
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
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>Test page for " + systemName() +
1037 "</title></head><body><h1>Test</h1><p>This page shows " + systemName() +
1038 " http(s) connectivity is working.</p></body></html>";
1039 }
1040 else
1041 {
1042 msg.result(boost::beast::http::status::internal_server_error);
1043 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason + "</BODY></HTML>";
1044 }
1045 msg.version(request.version());
1046 msg.insert("Server", BuildInfo::getFullVersionString());
1047 msg.insert("Content-Type", "text/html");
1048 msg.insert("Connection", "close");
1049 msg.prepare_payload();
1051 return handoff;
1052}
1053
1054//------------------------------------------------------------------------------
1055
1056void
1058{
1059 for (auto& p : ports)
1060 {
1061 if (p.secure())
1062 {
1063 if (p.sslKey.empty() && p.sslCert.empty() && p.sslChain.empty())
1064 {
1065 p.context = makeSslContext(p.sslCiphers);
1066 }
1067 else
1068 {
1069 p.context = makeSslContextAuthed(p.sslKey, p.sslCert, p.sslChain, p.sslCiphers);
1070 }
1071 }
1072 else
1073 {
1074 p.context =
1075 std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
1076 }
1077 }
1078}
1079
1080static Port
1081toPort(ParsedPort const& parsed, std::ostream& log)
1082{
1083 Port p;
1084 p.name = parsed.name;
1085
1086 if (!parsed.ip)
1087 {
1088 log << "Missing 'ip' in [" << p.name << "]";
1090 }
1091 p.ip = *parsed.ip;
1092
1093 if (!parsed.port)
1094 {
1095 log << "Missing 'port' in [" << p.name << "]";
1097 }
1098 p.port = *parsed.port;
1099
1100 if (parsed.protocol.empty())
1101 {
1102 log << "Missing 'protocol' in [" << p.name << "]";
1104 }
1105 p.protocol = parsed.protocol;
1106
1107 p.user = parsed.user;
1108 p.password = parsed.password;
1109 p.adminUser = parsed.adminUser;
1110 p.adminPassword = parsed.adminPassword;
1111 p.sslKey = parsed.sslKey;
1112 p.sslCert = parsed.sslCert;
1113 p.sslChain = parsed.sslChain;
1114 p.sslCiphers = parsed.sslCiphers;
1115 p.pmdOptions = parsed.pmdOptions;
1116 p.wsQueueLimit = parsed.wsQueueLimit;
1117 p.limit = parsed.limit;
1118 p.adminNetsV4 = parsed.adminNetsV4;
1119 p.adminNetsV6 = parsed.adminNetsV6;
1122
1123 return p;
1124}
1125
1126static std::vector<Port>
1127parsePorts(Config const& config, std::ostream& log)
1128{
1129 std::vector<Port> result;
1130
1131 if (!config.exists(Sections::kServer))
1132 {
1133 log << "Required section [server] is missing";
1135 }
1136
1137 ParsedPort common;
1138 parsePort(common, config[Sections::kServer], log);
1139
1140 auto const& names = config.section(Sections::kServer).values();
1141 result.reserve(names.size());
1142 for (auto const& name : names)
1143 {
1144 if (!config.exists(name))
1145 {
1146 log << "Missing section: [" << name << "]";
1148 }
1149
1150 // grpc ports are parsed by GRPCServer class. Do not validate
1151 // grpc port information in this file.
1152 if (name == Sections::kPortGrpc)
1153 continue;
1154
1155 ParsedPort parsed = common;
1156 parsePort(parsed, config[name], log);
1157 result.push_back(toPort(parsed, log));
1158 }
1159
1160 if (config.standalone())
1161 {
1162 auto it = result.begin();
1163
1164 while (it != result.end())
1165 {
1166 auto& p = it->protocol;
1167
1168 // Remove the peer protocol, and if that would
1169 // leave the port empty, remove the port as well
1170 if ((p.erase("peer") != 0u) && p.empty())
1171 {
1172 it = result.erase(it);
1173 }
1174 else
1175 {
1176 ++it;
1177 }
1178 }
1179 }
1180 else
1181 {
1182 auto const count = std::ranges::count_if(
1183 result, [](Port const& p) { return p.protocol.contains("peer"); });
1184
1185 if (count > 1)
1186 {
1187 log << "Error: More than one peer protocol configured in [server]";
1189 }
1190
1191 if (count == 0)
1192 log << "Warning: No peer protocol configured";
1193 }
1194
1195 return result;
1196}
1197
1198// Fill out the client portion of the Setup
1199static void
1201{
1202 decltype(setup.ports)::const_iterator iter;
1203 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1204 {
1205 if (iter->protocol.contains("http") || iter->protocol.contains("https"))
1206 break;
1207 }
1208 if (iter == setup.ports.cend())
1209 return;
1210 setup.client.secure = iter->protocol.contains("https");
1211 if (beast::IP::isUnspecified(iter->ip))
1212 {
1213 // VFALCO HACK! to make localhost work
1214 setup.client.ip = iter->ip.is_v6() ? "::1" : "127.0.0.1";
1215 }
1216 else
1217 {
1218 setup.client.ip = iter->ip.to_string();
1219 }
1220 setup.client.port = iter->port;
1221 setup.client.user = iter->user;
1222 setup.client.password = iter->password;
1223 setup.client.adminUser = iter->adminUser;
1224 setup.client.adminPassword = iter->adminPassword;
1225}
1226
1227// Fill out the overlay portion of the Setup
1228static void
1230{
1231 auto const iter = std::ranges::find_if(
1232 setup.ports, [](Port const& port) { return port.protocol.contains("peer"); });
1233 if (iter == setup.ports.cend())
1234 {
1235 setup.overlay = {};
1236 return;
1237 }
1238 setup.overlay = {iter->ip, iter->port};
1239}
1240
1241ServerHandler::Setup
1243{
1245 setup.ports = parsePorts(config, log);
1246
1247 setupClient(setup);
1248 setupOverlay(setup);
1249
1250 return setup;
1251}
1252
1255 Application& app,
1256 boost::asio::io_context& ioContext,
1257 JobQueue& jobQueue,
1258 NetworkOPs& networkOPs,
1259 Resource::Manager& resourceManager,
1260 CollectorManager& cm)
1261{
1264 app,
1265 ioContext,
1266 jobQueue,
1267 networkOPs,
1268 resourceManager,
1269 cm);
1270}
1271
1272} // namespace xrpl
T append(T... args)
T begin(T... args)
A version-independent IP address and port combination.
Definition IPEndpoint.h:17
A generic endpoint for log messages.
Definition Journal.h:38
Stream error() const
Definition Journal.h:315
Stream debug() const
Definition Journal.h:297
Stream warn() const
Definition Journal.h:309
EventImpl::value_type value_type
Definition Event.h:23
Decorator for streaming out compact json.
Unserialize a JSON document into a Value.
Definition json_reader.h:17
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
Represents a JSON value.
Definition json_value.h:130
Value removeMember(char const *key)
Remove and return the named member.
bool isNull() const
isNull() tests to see if this field is null.
bool isObject() const
bool isArray() const
bool isString() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
bool isInt() const
std::string asString() const
Returns the unquoted string value.
bool isObjectOrNull() const
bool isMember(char const *key) const
Return true if the object has a member named key.
Int asInt() const
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.
Holds transactions which were deferred to the next pass of consensus.
Provides the beast::insight::Collector service.
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
bool standalone() const
Definition Config.h:316
std::shared_ptr< InfoSub > pointer
Definition InfoSub.h:47
A pool of threads to perform work.
Definition JobQueue.h:43
Provides server functionality for clients.
Definition NetworkOPs.h:71
Represents a peer connection in the overlay.
A consumption charge.
Definition Charge.h:9
An endpoint that consumes resources.
Definition Consumer.h:15
bool warn()
Returns true if the consumer should be warned.
Definition Consumer.cpp:100
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
Definition Consumer.cpp:107
Disposition charge(Charge const &fee, std::string const &context={})
Apply a load charge to the consumer.
Definition Consumer.cpp:89
Tracks load and resource consumption.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition BasicConfig.h:58
std::condition_variable condition_
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_context &ioContext, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
friend std::unique_ptr< ServerHandler > makeServerHandler(Application &app, boost::asio::io_context &, JobQueue &, NetworkOPs &, Resource::Manager &, CollectorManager &cm)
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remoteAddress)
beast::insight::Event rpcTime_
Resource::Manager & resourceManager_
beast::insight::Event rpcSize_
Application & app_
json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, json::Value const &jv)
void onClose(Session &session, boost::system::error_code const &)
Setup const & setup() const
NetworkOPs & networkOPs_
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
void onStopped(Server &)
void setup(Setup const &setup, beast::Journal journal)
Handoff statusResponse(http_request_type const &request) const
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output const &, std::shared_ptr< JobQueue::Coro > coro, std::string_view forwardedFor, std::string_view user)
std::map< std::reference_wrapper< Port const >, int > count_
void onRequest(Session &session)
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
beast::Journal journal_
std::unique_ptr< Server > server_
beast::insight::Counter rpcRequests_
A multi-protocol server.
Definition ServerImpl.h:29
Persistent state information for a connection session.
Definition Session.h:24
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual void close(bool graceful)=0
Close the session.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
void write(std::string_view s)
Send a copy of data asynchronously.
Definition Session.h:57
virtual http_request_type & request()=0
Returns the current HTTP request.
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
Validator keys and manifest as set in configuration file.
T contains(T... args)
T count_if(T... args)
T duration_cast(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)
T make_unique(T... args)
Endpoint fromAsio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool isUnspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:37
bool isKeepAlive(boost::beast::http::message< IsRequest, Body, Fields > const &m)
Definition rfc2616.h:361
int Int
void stream(json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
unsigned int UInt
@ Array
array value (ordered list)
Definition json_value.h:25
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
std::function< void(boost::beast::string_view const &)> Output
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:82
static constexpr int kMaxRequestSize
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
static constexpr auto kApiInvalidVersion
Definition ApiVersion.h:40
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
int errorCodeHttpStatus(ErrorCodeI code)
Returns http status that corresponds to the error code.
static constexpr auto kApiVersionIfUnspecified
Definition ApiVersion.h:43
unsigned int getAPIVersionNumber(json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Definition ApiVersion.h:99
Status doCommand(RPC::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
Charge const kFeeMalformedRpc
Charge const kFeeReferenceRpc
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static void setupClient(ServerHandler::Setup &setup)
std::string base64Decode(std::string_view data)
static json::Value makeJsonError(json::Int code, json::Value &&message)
ErrorCodeI
Definition ErrorCodes.h:22
@ RpcInternal
Definition ErrorCodes.h:112
@ RpcSlowDown
Definition ErrorCodes.h:39
@ RpcForbidden
Definition ErrorCodes.h:30
ServerHandler::Setup setupServerHandler(Config const &config, std::ostream &log)
constexpr json::Int kWrongVersion
void parsePort(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:195
static Port toPort(ParsedPort const &parsed, std::ostream &log)
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:88
std::unique_ptr< Server > makeServer(Handler &handler, boost::asio::io_context &ioContext, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition Server.h:15
static std::map< std::string, std::string > buildMap(boost::beast::http::fields const &h)
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
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:132
constexpr json::Int kMethodNotFound
static std::vector< Port > parsePorts(Config const &config, std::ostream &log)
std::string_view forwardedFor(http_request_type const &request)
Definition Role.cpp:251
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void logDuration(json::Value const &request, T const &duration, beast::Journal &journal)
static json::Output makeOutput(Session &session)
Dir::ConstIterator const_iterator
Definition Dir.cpp:16
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
@ IDENTIFIED
Definition Role.h:24
@ GUEST
Definition Role.h:24
@ FORBID
Definition Role.h:24
@ PROXY
Definition Role.h:24
void httpReply(int nStatus, std::string const &strMsg, json::Output const &, beast::Journal j)
@ JtClientWebsocket
Definition Job.h:32
@ JtClientRpc
Definition Job.h:31
std::shared_ptr< boost::asio::ssl::context > makeSslContextAuthed(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.
constexpr json::Int kForbidden
static std::string const & systemName()
std::shared_ptr< boost::asio::ssl::context > makeSslContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
static std::string buffersToString(ConstBufferSequence const &bs)
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:115
constexpr json::Int kServerOverloaded
Overlay::Setup setupOverlay(BasicConfig const &config, beast::Journal j)
static bool isStatusRequest(http_request_type const &request)
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
T static_pointer_cast(T... args)
T push_back(T... args)
T remove_suffix(T... args)
T reserve(T... args)
T size(T... args)
static IP::Endpoint fromAsio(boost::asio::ip::address const &address)
Used to indicate the result of a server connection handoff.
Definition Handoff.h:18
std::shared_ptr< Writer > response
Definition Handoff.h:27
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:80
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:95
std::optional< boost::asio::ip::address > ip
Definition Port.h:93
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:96
std::string sslKey
Definition Port.h:85
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
Definition Port.h:98
std::string name
Definition Port.h:79
std::string sslCiphers
Definition Port.h:88
std::string adminUser
Definition Port.h:83
std::string user
Definition Port.h:81
std::optional< std::uint16_t > port
Definition Port.h:94
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
Definition Port.h:97
std::string sslCert
Definition Port.h:86
std::string password
Definition Port.h:82
std::uint16_t wsQueueLimit
Definition Port.h:91
boost::beast::websocket::permessage_deflate pmdOptions
Definition Port.h:89
std::string adminPassword
Definition Port.h:84
std::string sslChain
Definition Port.h:87
Configuration information for a Server listening port.
Definition Port.h:28
std::string sslCiphers
Definition Port.h:46
int limit
Definition Port.h:52
std::string sslCert
Definition Port.h:44
std::string password
Definition Port.h:40
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:36
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:34
boost::beast::websocket::permessage_deflate pmdOptions
Definition Port.h:47
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
Definition Port.h:37
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:35
std::string adminUser
Definition Port.h:41
std::uint16_t wsQueueLimit
Definition Port.h:55
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
Definition Port.h:38
std::string sslKey
Definition Port.h:43
std::string adminPassword
Definition Port.h:42
boost::asio::ip::address ip
Definition Port.h:32
std::string sslChain
Definition Port.h:45
std::string name
Definition Port.h:31
std::string user
Definition Port.h:39
std::uint16_t port
Definition Port.h:33
static constexpr auto kServer
Definition Constants.h:55
static constexpr auto kPortGrpc
Definition Constants.h:44
boost::asio::ip::tcp::endpoint overlay
std::vector< Port > ports
T substr(T... args)
T transform(T... args)
T what(T... args)