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