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