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/StringUtilities.h>
12#include <xrpl/basics/base64.h>
13#include <xrpl/basics/contract.h>
14#include <xrpl/basics/make_SSLContext.h>
15#include <xrpl/beast/net/IPAddress.h>
16#include <xrpl/beast/net/IPAddressConversion.h>
17#include <xrpl/beast/rfc2616.h>
18#include <xrpl/beast/utility/Journal.h>
19#include <xrpl/config/Constants.h>
20#include <xrpl/core/Job.h>
21#include <xrpl/core/JobQueue.h>
22#include <xrpl/json/Output.h>
23#include <xrpl/json/json_forwards.h>
24#include <xrpl/json/json_reader.h>
25#include <xrpl/json/json_value.h>
26#include <xrpl/json/json_writer.h>
27#include <xrpl/json/to_string.h>
28#include <xrpl/protocol/ApiVersion.h>
29#include <xrpl/protocol/BuildInfo.h>
30#include <xrpl/protocol/ErrorCodes.h>
31#include <xrpl/protocol/RPCErr.h>
32#include <xrpl/protocol/SystemParameters.h>
33#include <xrpl/protocol/jss.h>
34#include <xrpl/resource/Charge.h>
35#include <xrpl/resource/Consumer.h>
36#include <xrpl/resource/Fees.h>
37#include <xrpl/resource/ResourceManager.h>
38#include <xrpl/server/Handoff.h>
39#include <xrpl/server/InfoSub.h>
40#include <xrpl/server/NetworkOPs.h>
41#include <xrpl/server/Port.h>
42#include <xrpl/server/Server.h>
43#include <xrpl/server/Session.h>
44#include <xrpl/server/SimpleWriter.h>
45#include <xrpl/server/WSSession.h>
46#include <xrpl/server/detail/JSONRPCUtil.h>
47
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(HttpRequestType 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", build_info::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 strUserPass64 = trimWhitespace(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 HttpRequestType&& 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 [&](std::string_view 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 jr[jss::status] = jss::success;
537 }
538
539 if (jv.isMember(jss::id))
540 jr[jss::id] = jv[jss::id];
541 if (jv.isMember(jss::jsonrpc))
542 jr[jss::jsonrpc] = jv[jss::jsonrpc];
543 if (jv.isMember(jss::ripplerpc))
544 jr[jss::ripplerpc] = jv[jss::ripplerpc];
545 if (jv.isMember(jss::api_version))
546 jr[jss::api_version] = jv[jss::api_version];
547
548 jr[jss::type] = jss::response;
549 return jr;
550}
551
552// Run as a coroutine.
553void
555 std::shared_ptr<Session> const& session,
557{
559 session->port(),
560 buffersToString(session->request().body().data()),
561 session->remoteAddress().atPort(0),
562 makeOutput(*session),
563 coro,
564 forwardedFor(session->request()),
565 [&] -> std::string_view {
566 auto const iter = session->request().find("X-User");
567 if (iter != session->request().end())
568 return iter->value();
569 return {};
570 }());
571
572 if (beast::rfc2616::isKeepAlive(session->request()))
573 {
574 session->complete();
575 }
576 else
577 {
578 session->close(true);
579 }
580}
581
582static json::Value
584{
586 sub["code"] = code;
587 sub["message"] = std::move(message);
589 r["error"] = sub;
590 return r;
591}
592
593constexpr json::Int kMethodNotFound = -32601;
594constexpr json::Int kServerOverloaded = -32604;
595constexpr json::Int kForbidden = -32605;
596constexpr json::Int kWrongVersion = -32606;
597
598void
600 Port const& port,
601 std::string const& request,
602 beast::ip::Endpoint const& remoteIPAddress,
603 Output const& output,
606 std::string_view user)
607{
608 auto rpcJ = app_.getJournal("RPC");
609
610 json::Value jsonOrig;
611 {
612 json::Reader reader;
613 if ((request.size() > rpc::tuning::kMaxRequestSize) || !reader.parse(request, jsonOrig) ||
614 !jsonOrig || !jsonOrig.isObject())
615 {
616 httpReply(
617 400,
618 "Unable to parse request: " + reader.getFormattedErrorMessages(),
619 output,
620 rpcJ);
621 return;
622 }
623 }
624
625 bool batch = false;
626 unsigned size = 1;
627 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
628 {
629 batch = true;
630 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
631 {
632 httpReply(400, "Malformed batch request", output, rpcJ);
633 return;
634 }
635 size = jsonOrig[jss::params].size();
636 }
637
639 auto const start(std::chrono::high_resolution_clock::now());
640 for (unsigned i = 0; i < size; ++i)
641 {
642 json::Value const& jsonRPC = batch ? jsonOrig[jss::params][i] : jsonOrig;
643
644 if (!jsonRPC.isObject())
645 {
647 r[jss::request] = jsonRPC;
648 r[jss::error] = makeJsonError(kMethodNotFound, "Method not found");
649 reply.append(r);
650 continue;
651 }
652
653 unsigned apiVersion = rpc::kApiVersionIfUnspecified;
654 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
655 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject())
656 {
657 apiVersion = rpc::getAPIVersionNumber(
658 jsonRPC[jss::params][json::UInt(0)], app_.config().betaRpcApi);
659 }
660
661 if (apiVersion == rpc::kApiVersionIfUnspecified && batch)
662 {
663 // for batch request, api_version may be at a different level
664 apiVersion = rpc::getAPIVersionNumber(jsonRPC, app_.config().betaRpcApi);
665 }
666
667 if (apiVersion == rpc::kApiInvalidVersion)
668 {
669 if (!batch)
670 {
671 httpReply(400, jss::invalid_API_version.cStr(), output, rpcJ);
672 return;
673 }
675 r[jss::request] = jsonRPC;
676 r[jss::error] = makeJsonError(kWrongVersion, jss::invalid_API_version.cStr());
677 reply.append(r);
678 continue;
679 }
680
681 /* ------------------------------------------------------------------ */
682 auto role = Role::FORBID;
683 auto required = Role::FORBID;
684 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
685 {
686 required = rpc::roleRequired(
687 apiVersion, app_.config().betaRpcApi, jsonRPC[jss::method].asString());
688 }
689
690 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
691 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][json::UInt(0)].isObjectOrNull())
692 {
693 role = requestRole(
694 required, port, jsonRPC[jss::params][json::UInt(0)], remoteIPAddress, user);
695 }
696 else
697 {
698 role = requestRole(required, port, json::ValueType::Object, remoteIPAddress, user);
699 }
700
701 resource::Consumer usage;
702 if (isUnlimited(role))
703 {
704 usage = resourceManager_.newUnlimitedEndpoint(remoteIPAddress);
705 }
706 else
707 {
708 usage = resourceManager_.newInboundEndpoint(
709 remoteIPAddress, role == Role::PROXY, forwardedFor);
710 if (usage.disconnect(journal_))
711 {
712 if (!batch)
713 {
714 httpReply(503, "Server is overloaded", output, rpcJ);
715 return;
716 }
717 json::Value r = jsonRPC;
718 r[jss::error] = makeJsonError(kServerOverloaded, "Server is overloaded");
719 reply.append(r);
720 continue;
721 }
722 }
723
724 if (role == Role::FORBID)
725 {
727 if (!batch)
728 {
729 httpReply(403, "Forbidden", output, rpcJ);
730 return;
731 }
732 json::Value r = jsonRPC;
733 r[jss::error] = makeJsonError(kForbidden, "Forbidden");
734 reply.append(r);
735 continue;
736 }
737
738 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
739 {
741 if (!batch)
742 {
743 httpReply(400, "Null method", output, rpcJ);
744 return;
745 }
746 json::Value r = jsonRPC;
747 r[jss::error] = makeJsonError(kMethodNotFound, "Null method");
748 reply.append(r);
749 continue;
750 }
751
752 json::Value const& method = jsonRPC[jss::method];
753 if (!method.isString())
754 {
756 if (!batch)
757 {
758 httpReply(400, "method is not string", output, rpcJ);
759 return;
760 }
761 json::Value r = jsonRPC;
762 r[jss::error] = makeJsonError(kMethodNotFound, "method is not string");
763 reply.append(r);
764 continue;
765 }
766
767 std::string const strMethod = method.asString();
768 if (strMethod.empty())
769 {
771 if (!batch)
772 {
773 httpReply(400, "method is empty", output, rpcJ);
774 return;
775 }
776 json::Value r = jsonRPC;
777 r[jss::error] = makeJsonError(kMethodNotFound, "method is empty");
778 reply.append(r);
779 continue;
780 }
781
782 // Extract request parameters from the request Json as `params`.
783 //
784 // If the field "params" is empty, `params` is an empty object.
785 //
786 // Otherwise, that field must be an array of length 1 (why?)
787 // and we take that first entry and validate that it's an object.
788 json::Value params;
789 if (!batch)
790 {
791 params = jsonRPC[jss::params];
792 if (!params)
793 {
795 }
796 else if (!params.isArray() || params.size() != 1)
797 {
799 httpReply(400, "params unparsable", output, rpcJ);
800 return;
801 }
802 else
803 {
804 params = std::move(params[0u]);
805 if (!params.isObjectOrNull())
806 {
808 httpReply(400, "params unparsable", output, rpcJ);
809 return;
810 }
811 }
812 }
813 else // batch
814 {
815 params = jsonRPC;
816 }
817
818 std::string ripplerpc = "1.0";
819 if (params.isMember(jss::ripplerpc))
820 {
821 if (!params[jss::ripplerpc].isString())
822 {
824 if (!batch)
825 {
826 httpReply(400, "ripplerpc is not a string", output, rpcJ);
827 return;
828 }
829
830 json::Value r = jsonRPC;
831 r[jss::error] = makeJsonError(kMethodNotFound, "ripplerpc is not a string");
832 reply.append(r);
833 continue;
834 }
835 ripplerpc = params[jss::ripplerpc].asString();
836 }
837
842 if (role != Role::IDENTIFIED && role != Role::PROXY)
843 {
844 forwardedFor.remove_suffix(forwardedFor.size());
845 user.remove_suffix(user.size());
846 }
847
848 JLOG(journal_.debug()) << "Query: " << strMethod << params;
849
850 // Provide the JSON-RPC method as the field "command" in the request.
851 params[jss::command] = strMethod;
852 JLOG(journal_.trace()) << "doRpcCommand:" << strMethod << ":" << params;
853
855
856 rpc::JsonContext context{
857 {.j = journal_,
858 .app = app_,
859 .loadType = loadType,
860 .netOps = networkOPs_,
861 .ledgerMaster = app_.getLedgerMaster(),
862 .consumer = usage,
863 .role = role,
864 .coro = coro,
865 .infoSub = InfoSub::pointer(),
866 .apiVersion = apiVersion},
867 params,
868 {.user = user, .forwardedFor = forwardedFor}};
869 json::Value result;
870
871 auto start = std::chrono::system_clock::now();
872
873 try
874 {
875 rpc::doCommand(context, result);
876 }
877 catch (std::exception const& ex)
878 {
879 // LCOV_EXCL_START
880 result = rpc::makeError(RpcInternal);
881 JLOG(journal_.error())
882 << "Internal error : " << ex.what()
883 << " when processing request: " << json::Compact{json::Value{params}};
884 // LCOV_EXCL_STOP
885 }
886
888
889 logDuration(params, end - start, journal_);
890
891 usage.charge(loadType);
892 if (usage.warn())
893 result[jss::warning] = jss::load;
894
896 if (ripplerpc >= "2.0")
897 {
898 if (result.isMember(jss::error))
899 {
900 result[jss::status] = jss::error;
901 result["code"] = result[jss::error_code];
902 result["message"] = result[jss::error_message];
903 result.removeMember(jss::error_message);
904 JLOG(journal_.debug())
905 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
906 r[jss::error] = std::move(result);
907 }
908 else
909 {
910 result[jss::status] = jss::success;
911 r[jss::result] = std::move(result);
912 }
913 }
914 else
915 {
916 // Always report "status". On an error report the request as
917 // received.
918 if (result.isMember(jss::error))
919 {
920 auto rq = params;
921
922 if (rq.isObject())
923 { // But mask potentially sensitive information.
924 if (rq.isMember(jss::passphrase.cStr()))
925 rq[jss::passphrase.cStr()] = "<masked>";
926 if (rq.isMember(jss::secret.cStr()))
927 rq[jss::secret.cStr()] = "<masked>";
928 if (rq.isMember(jss::seed.cStr()))
929 rq[jss::seed.cStr()] = "<masked>";
930 if (rq.isMember(jss::seed_hex.cStr()))
931 rq[jss::seed_hex.cStr()] = "<masked>";
932 }
933
934 result[jss::status] = jss::error;
935 result[jss::request] = rq;
936
937 JLOG(journal_.debug())
938 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
939 }
940 else
941 {
942 result[jss::status] = jss::success;
943 }
944 r[jss::result] = std::move(result);
945 }
946
947 if (params.isMember(jss::jsonrpc))
948 r[jss::jsonrpc] = params[jss::jsonrpc];
949 if (params.isMember(jss::ripplerpc))
950 r[jss::ripplerpc] = params[jss::ripplerpc];
951 if (params.isMember(jss::id))
952 r[jss::id] = params[jss::id];
953 if (batch)
954 {
955 reply.append(std::move(r));
956 }
957 else
958 {
959 reply = std::move(r);
960 }
961
962 if (reply.isMember(jss::result) && reply[jss::result].isMember(jss::result))
963 {
964 reply = reply[jss::result];
965 if (reply.isMember(jss::status))
966 {
967 reply[jss::result][jss::status] = reply[jss::status];
968 reply.removeMember(jss::status);
969 }
970 }
971 }
972
973 // If we're returning an error_code, use that to determine the HTTP status.
974 int const httpStatus = [&reply]() {
975 // This feature is enabled with ripplerpc version 3.0 and above.
976 // Before ripplerpc version 3.0 always return 200.
977 if (reply.isMember(jss::ripplerpc) && reply[jss::ripplerpc].isString() &&
978 reply[jss::ripplerpc].asString() >= "3.0")
979 {
980 // If there's an error_code, use that to determine the HTTP Status.
981 if (reply.isMember(jss::error) && reply[jss::error].isMember(jss::error_code) &&
982 reply[jss::error][jss::error_code].isInt())
983 {
984 int const errCode = reply[jss::error][jss::error_code].asInt();
985 return rpc::errorCodeHttpStatus(static_cast<ErrorCodeI>(errCode));
986 }
987 }
988 // Return OK.
989 return 200;
990 }();
991
992 auto response = to_string(reply);
993
994 rpcTime_.notify(
997 ++rpcRequests_;
998 rpcSize_.notify(beast::insight::Event::value_type{response.size()});
999
1000 response += '\n';
1001
1002 if (auto stream = journal_.debug())
1003 {
1004 static int const kMaxSize = 10000;
1005 if (response.size() <= kMaxSize)
1006 {
1007 stream << "Reply: " << response;
1008 }
1009 else
1010 {
1011 stream << "Reply: " << response.substr(0, kMaxSize);
1012 }
1013 }
1014
1015 httpReply(httpStatus, response, output, rpcJ);
1016}
1017
1018//------------------------------------------------------------------------------
1019
1020/* This response is used with load balancing.
1021 If the server is overloaded, status 500 is reported. Otherwise status 200
1022 is reported, meaning the server can accept more connections.
1023*/
1024Handoff
1026{
1027 using namespace boost::beast::http;
1028 Handoff handoff;
1029 response<string_body> msg;
1030 std::string reason;
1031 if (app_.serverOkay(reason))
1032 {
1033 msg.result(boost::beast::http::status::ok);
1034 msg.body() = "<!DOCTYPE html><html><head><title>Test page for " + systemName() +
1035 "</title></head><body><h1>Test</h1><p>This page shows " + systemName() +
1036 " http(s) connectivity is working.</p></body></html>";
1037 }
1038 else
1039 {
1040 msg.result(boost::beast::http::status::internal_server_error);
1041 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason + "</BODY></HTML>";
1042 }
1043 msg.version(request.version());
1045 msg.insert("Content-Type", "text/html");
1046 msg.insert("Connection", "close");
1047 msg.prepare_payload();
1049 return handoff;
1050}
1051
1052//------------------------------------------------------------------------------
1053
1054void
1056{
1057 for (auto& p : ports)
1058 {
1059 if (p.secure())
1060 {
1061 if (p.sslKey.empty() && p.sslCert.empty() && p.sslChain.empty())
1062 {
1063 p.context = makeSslContext(p.sslCiphers);
1064 }
1065 else
1066 {
1067 p.context = makeSslContextAuthed(p.sslKey, p.sslCert, p.sslChain, p.sslCiphers);
1068 }
1069 }
1070 else
1071 {
1072 p.context =
1073 std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
1074 }
1075 }
1076}
1077
1078static Port
1079toPort(ParsedPort const& parsed, std::ostream& log)
1080{
1081 Port p;
1082 p.name = parsed.name;
1083
1084 if (!parsed.ip)
1085 {
1086 log << "Missing 'ip' in [" << p.name << "]";
1088 }
1089 p.ip = *parsed.ip;
1090
1091 if (!parsed.port)
1092 {
1093 log << "Missing 'port' in [" << p.name << "]";
1095 }
1096 p.port = *parsed.port;
1097
1098 if (parsed.protocol.empty())
1099 {
1100 log << "Missing 'protocol' in [" << p.name << "]";
1102 }
1103 p.protocol = parsed.protocol;
1104
1105 p.user = parsed.user;
1106 p.password = parsed.password;
1107 p.adminUser = parsed.adminUser;
1108 p.adminPassword = parsed.adminPassword;
1109 p.sslKey = parsed.sslKey;
1110 p.sslCert = parsed.sslCert;
1111 p.sslChain = parsed.sslChain;
1112 p.sslCiphers = parsed.sslCiphers;
1113 p.pmdOptions = parsed.pmdOptions;
1114 p.wsQueueLimit = parsed.wsQueueLimit;
1115 p.limit = parsed.limit;
1116 p.adminNetsV4 = parsed.adminNetsV4;
1117 p.adminNetsV6 = parsed.adminNetsV6;
1120
1121 return p;
1122}
1123
1124static std::vector<Port>
1125parsePorts(Config const& config, std::ostream& log)
1126{
1127 std::vector<Port> result;
1128
1129 if (!config.exists(Sections::kServer))
1130 {
1131 log << "Required section [server] is missing";
1133 }
1134
1135 ParsedPort common;
1136 parsePort(common, config[Sections::kServer], log);
1137
1138 auto const& names = config.section(Sections::kServer).values();
1139 result.reserve(names.size());
1140 for (auto const& name : names)
1141 {
1142 if (!config.exists(name))
1143 {
1144 log << "Missing section: [" << name << "]";
1146 }
1147
1148 // grpc ports are parsed by GRPCServer class. Do not validate
1149 // grpc port information in this file.
1150 if (name == Sections::kPortGrpc)
1151 continue;
1152
1153 ParsedPort parsed = common;
1154 parsePort(parsed, config[name], log);
1155 result.push_back(toPort(parsed, log));
1156 }
1157
1158 if (config.standalone())
1159 {
1160 auto it = result.begin();
1161
1162 while (it != result.end())
1163 {
1164 auto& p = it->protocol;
1165
1166 // Remove the peer protocol, and if that would
1167 // leave the port empty, remove the port as well
1168 if ((p.erase("peer") != 0u) && p.empty())
1169 {
1170 it = result.erase(it);
1171 }
1172 else
1173 {
1174 ++it;
1175 }
1176 }
1177 }
1178 else
1179 {
1180 auto const count = std::ranges::count_if(
1181 result, [](Port const& p) { return p.protocol.contains("peer"); });
1182
1183 if (count > 1)
1184 {
1185 log << "Error: More than one peer protocol configured in [server]";
1187 }
1188
1189 if (count == 0)
1190 log << "Warning: No peer protocol configured";
1191 }
1192
1193 return result;
1194}
1195
1196// Fill out the client portion of the Setup
1197static void
1199{
1200 decltype(setup.ports)::const_iterator iter;
1201 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1202 {
1203 if (iter->protocol.contains("http") || iter->protocol.contains("https"))
1204 break;
1205 }
1206 if (iter == setup.ports.cend())
1207 return;
1208 setup.client.secure = iter->protocol.contains("https");
1209 if (beast::ip::isUnspecified(iter->ip))
1210 {
1211 // VFALCO HACK! to make localhost work
1212 setup.client.ip = iter->ip.is_v6() ? "::1" : "127.0.0.1";
1213 }
1214 else
1215 {
1216 setup.client.ip = iter->ip.to_string();
1217 }
1218 setup.client.port = iter->port;
1219 setup.client.user = iter->user;
1220 setup.client.password = iter->password;
1221 setup.client.adminUser = iter->adminUser;
1222 setup.client.adminPassword = iter->adminPassword;
1223}
1224
1225// Fill out the overlay portion of the Setup
1226static void
1228{
1229 auto const iter = std::ranges::find_if(
1230 setup.ports, [](Port const& port) { return port.protocol.contains("peer"); });
1231 if (iter == setup.ports.cend())
1232 {
1233 setup.overlay = {};
1234 return;
1235 }
1236 setup.overlay = {iter->ip, iter->port};
1237}
1238
1239ServerHandler::Setup
1241{
1243 setup.ports = parsePorts(config, log);
1244
1245 setupClient(setup);
1246 setupOverlay(setup);
1247
1248 return setup;
1249}
1250
1253 Application& app,
1254 boost::asio::io_context& ioContext,
1255 JobQueue& jobQueue,
1256 NetworkOPs& networkOPs,
1257 resource::Manager& resourceManager,
1258 CollectorManager& cm)
1259{
1262 app,
1263 ioContext,
1264 jobQueue,
1265 networkOPs,
1266 resourceManager,
1267 cm);
1268}
1269
1270} // namespace xrpl
T append(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream error() const
Definition Journal.h:362
Stream debug() const
Definition Journal.h:344
Stream warn() const
Definition Journal.h:356
EventImpl::value_type value_type
Definition Event.h:24
A version-independent IP address and port combination.
Definition IPEndpoint.h:24
Decorator for streaming out compact json.
Unserialize a JSON document into a Value.
Definition json_reader.h:20
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:117
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
std::shared_ptr< InfoSub > pointer
Definition InfoSub.h:92
A pool of threads to perform work.
Definition JobQueue.h:60
Tracks the current ledger and any ledgers in the process of closing.
Provides server functionality for clients.
Definition NetworkOPs.h:82
Represents a peer connection in the overlay.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition BasicConfig.h:68
std::condition_variable condition_
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_context &ioContext, JobQueue &jobQueue, NetworkOPs &networkOPs, resource::Manager &resourceManager, CollectorManager &cm)
Handoff onHandoff(Session &session, std::unique_ptr< StreamType > &&bundle, HttpRequestType &&request, boost::asio::ip::tcp::endpoint const &remoteAddress)
beast::insight::Event rpcTime_
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 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)
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)
friend std::unique_ptr< ServerHandler > makeServerHandler(Application &app, boost::asio::io_context &, JobQueue &, NetworkOPs &, resource::Manager &, CollectorManager &cm)
void onStopped(Server &)
void setup(Setup const &setup, beast::Journal journal)
resource::Manager & resourceManager_
Handoff statusResponse(HttpRequestType const &request) const
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:37
Persistent state information for a connection session.
Definition Session.h:25
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:69
virtual HttpRequestType & 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.
A consumption charge.
Definition Charge.h:13
An endpoint that consumes resources.
Definition Consumer.h:20
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
bool warn()
Returns true if the consumer should be warned.
Definition Consumer.cpp:100
Tracks load and resource consumption.
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)
bool isUnspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:44
Endpoint fromAsio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool isKeepAlive(boost::beast::http::message< IsRequest, Body, Fields > const &m)
Definition rfc2616.h:367
int Int
std::function< void(std::string_view)> Output
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:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:77
Charge const kFeeMalformedRpc
Charge const kFeeReferenceRpc
static constexpr int kMaxRequestSize
Role roleRequired(unsigned int version, bool betaEnabled, std::string_view method)
static constexpr auto kApiVersionIfUnspecified
Definition ApiVersion.h:44
unsigned int getAPIVersionNumber(json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Definition ApiVersion.h:100
int errorCodeHttpStatus(ErrorCodeI code)
Returns http status that corresponds to the error code.
static constexpr auto kApiInvalidVersion
Definition ApiVersion.h:41
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
Status doCommand(rpc::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
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:23
@ RpcInternal
Definition ErrorCodes.h:113
@ RpcSlowDown
Definition ErrorCodes.h:40
@ RpcForbidden
Definition ErrorCodes.h:31
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)
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:17
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)
constexpr json::Int kMethodNotFound
std::string trimWhitespace(std::string str)
Remove leading and trailing ASCII whitespace.
static std::vector< Port > parsePorts(Config const &config, std::ostream &log)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
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:27
@ GUEST
Definition Role.h:27
@ FORBID
Definition Role.h:27
@ PROXY
Definition Role.h:27
void httpReply(int nStatus, std::string const &strMsg, json::Output const &, beast::Journal j)
@ JtClientWebsocket
Definition Job.h:37
@ JtClientRpc
Definition Job.h:36
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::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()
boost::beast::http::request< boost::beast::http::dynamic_body > HttpRequestType
Definition Handoff.h:12
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)
std::string_view forwardedFor(HttpRequestType const &request)
Definition Role.cpp:251
static bool isStatusRequest(HttpRequestType const &request)
static Handoff statusRequestResponse(HttpRequestType const &request, boost::beast::http::status status)
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)
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
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
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:20
std::shared_ptr< Writer > response
Definition Handoff.h:29
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:81
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:96
std::optional< boost::asio::ip::address > ip
Definition Port.h:94
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:97
std::string sslKey
Definition Port.h:86
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
Definition Port.h:99
std::string name
Definition Port.h:80
std::string sslCiphers
Definition Port.h:89
std::string adminUser
Definition Port.h:84
std::string user
Definition Port.h:82
std::optional< std::uint16_t > port
Definition Port.h:95
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
Definition Port.h:98
std::string sslCert
Definition Port.h:87
std::string password
Definition Port.h:83
std::uint16_t wsQueueLimit
Definition Port.h:92
boost::beast::websocket::permessage_deflate pmdOptions
Definition Port.h:90
std::string adminPassword
Definition Port.h:85
std::string sslChain
Definition Port.h:88
Configuration information for a Server listening port.
Definition Port.h:29
std::string sslCiphers
Definition Port.h:47
int limit
Definition Port.h:53
std::string sslCert
Definition Port.h:45
std::string password
Definition Port.h:41
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:37
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:35
boost::beast::websocket::permessage_deflate pmdOptions
Definition Port.h:48
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
Definition Port.h:38
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:36
std::string adminUser
Definition Port.h:42
std::uint16_t wsQueueLimit
Definition Port.h:56
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
Definition Port.h:39
std::string sslKey
Definition Port.h:44
std::string adminPassword
Definition Port.h:43
boost::asio::ip::address ip
Definition Port.h:33
std::string sslChain
Definition Port.h:46
std::string name
Definition Port.h:32
std::string user
Definition Port.h:40
std::uint16_t port
Definition Port.h:34
static constexpr auto kServer
Definition Constants.h:56
static constexpr auto kPortGrpc
Definition Constants.h:45
boost::asio::ip::tcp::endpoint overlay
std::vector< Port > ports
T substr(T... args)
T transform(T... args)
T what(T... args)