xrpld
Loading...
Searching...
No Matches
BaseHTTPPeer.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/net/IPAddressConversion.h>
5#include <xrpl/beast/net/IPEndpoint.h>
6#include <xrpl/beast/utility/Journal.h>
7#include <xrpl/server/Handoff.h>
8#include <xrpl/server/Port.h>
9#include <xrpl/server/Session.h>
10#include <xrpl/server/Writer.h>
11#include <xrpl/server/detail/Spawn.h>
12#include <xrpl/server/detail/io_list.h>
13
14#include <boost/asio/ip/tcp.hpp>
15#include <boost/asio/spawn.hpp>
16#include <boost/asio/ssl/stream.hpp>
17#include <boost/asio/strand.hpp>
18#include <boost/asio/streambuf.hpp>
19#include <boost/beast/core/stream_traits.hpp>
20#include <boost/beast/http/dynamic_body.hpp>
21#include <boost/beast/http/message.hpp>
22#include <boost/beast/http/parser.hpp>
23#include <boost/beast/http/read.hpp>
24
25#include <atomic>
26#include <chrono>
27#include <cstddef>
28#include <cstring>
29#include <functional>
30#include <memory>
31#include <mutex>
32#include <string>
33#include <utility>
34#include <vector>
35
36namespace xrpl {
37
41template <class Handler, class Impl>
42class BaseHTTPPeer : public IOList::Work, public Session
43{
44protected:
46 using error_code = boost::system::error_code;
47 using endpoint_type = boost::asio::ip::tcp::endpoint;
48 using yield_context = boost::asio::yield_context;
49
50 static constexpr auto kBufferSize = 4 * 1024; // size of read/write buffer
51 static constexpr auto kTimeoutSeconds = 30; // max seconds without completing a message
52 static constexpr auto kTimeoutSecondsLocal = 3; // used for localhost clients
53
54 struct Buffer
55 {
56 Buffer(void const* ptr, std::size_t len) : data(new char[len]), bytes(len)
57 {
58 memcpy(data.get(), ptr, len);
59 }
60
64 };
65
66 Port const& port_;
67 Handler& handler_;
68 boost::asio::executor_work_guard<boost::asio::executor> work_;
69 boost::asio::strand<boost::asio::executor> strand_;
72
75
76 boost::asio::streambuf readBuf_;
81 bool graceful_ = false;
82 bool complete_ = false;
83 boost::system::error_code ec_;
84
88
89 //--------------------------------------------------------------------------
90
91public:
92 template <class ConstBufferSequence>
94 Port const& port,
95 Handler& handler,
96 boost::asio::executor const& executor,
99 ConstBufferSequence const& buffers);
100
101 ~BaseHTTPPeer() override;
102
103 Session&
105 {
106 return *this;
107 }
108
109 void
110 close() override;
111
112protected:
113 Impl&
115 {
116 return *static_cast<Impl*>(this);
117 }
118
119 void
120 fail(error_code ec, char const* what);
121
122 void
124
125 void
127
128 void
130
131 void
133
134 void
135 onWrite(error_code const& ec, std::size_t bytesTransferred);
136
137 void
138 doWriter(std::shared_ptr<Writer> const& writer, bool keepAlive, yield_context doYield);
139
140 virtual void
142
143 virtual void
144 doClose() = 0;
145
146 // Session
147
149 journal() override
150 {
151 return journal_;
152 }
153
154 Port const&
155 port() override
156 {
157 return port_;
158 }
159
165
167 request() override
168 {
169 return message_;
170 }
171
172 void
173 write(void const* buffer, std::size_t bytes) override;
174
175 void
176 write(std::shared_ptr<Writer> const& writer, bool keepAlive) override;
177
179 detach() override;
180
181 void
182 complete() override;
183
184 void
185 close(bool graceful) override;
186};
187
188//------------------------------------------------------------------------------
189
190template <class Handler, class Impl>
191template <class ConstBufferSequence>
193 Port const& port,
194 Handler& handler,
195 boost::asio::executor const& executor,
198 ConstBufferSequence const& buffers)
199 : port_(port)
200 , handler_(handler)
201 , work_(boost::asio::make_work_guard(executor))
202 , strand_(boost::asio::make_strand(executor))
205{
206 readBuf_.commit(
207 boost::asio::buffer_copy(readBuf_.prepare(boost::asio::buffer_size(buffers)), buffers));
208 static std::atomic<int> kSid;
209 nid_ = ++kSid;
210 id_ = std::string("#") + std::to_string(nid_) + " ";
211 JLOG(journal_.trace()) << id_ << "accept: " << remoteAddress_.address();
212}
213
214template <class Handler, class Impl>
216{
217 handler_.onClose(session(), ec_);
218 JLOG(journal_.trace()) << id_ << "destroyed: " << requestCount_
219 << ((requestCount_ == 1) ? " request" : " requests");
220}
221
222template <class Handler, class Impl>
223void
225{
226 if (!strand_.running_in_this_thread())
227 {
228 return post(strand_, [self = impl().shared_from_this()] { self->close(); });
229 }
230 boost::beast::get_lowest_layer(impl().stream_).close();
231}
232
233//------------------------------------------------------------------------------
234
235template <class Handler, class Impl>
236void
238{
239 if (!ec_ && ec != boost::asio::error::operation_aborted)
240 {
241 ec_ = ec;
242 JLOG(journal_.trace()) << id_ << std::string(what) << ": " << ec.message();
243 boost::beast::get_lowest_layer(impl().stream_).close();
244 }
245}
246
247template <class Handler, class Impl>
248void
250{
251 boost::beast::get_lowest_layer(impl().stream_)
252 .expires_after(
254 remoteAddress_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds));
255}
256
257// Convenience for discarding the error code
258template <class Handler, class Impl>
259void
261{
262 boost::beast::get_lowest_layer(impl().stream_).expires_never();
263}
264
265// Called when session times out
266template <class Handler, class Impl>
267void
269{
270 auto ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
271 fail(ec, "timer");
272}
273
274//------------------------------------------------------------------------------
275
276template <class Handler, class Impl>
277void
279{
280 complete_ = false;
281 error_code ec;
282 startTimer();
283 boost::beast::http::async_read(impl().stream_, readBuf_, message_, doYield[ec]);
284 cancelTimer();
285 if (ec == boost::beast::http::error::end_of_stream)
286 return doClose();
287 if (ec == boost::beast::error::timeout)
288 return onTimer();
289 if (ec)
290 return fail(ec, "http::read");
291 doRequest();
292}
293
294// Send everything in the write queue.
295// The write queue must not be empty upon entry.
296template <class Handler, class Impl>
297void
299{
300 cancelTimer();
301 if (ec == boost::beast::error::timeout)
302 return onTimer();
303 if (ec)
304 return fail(ec, "write");
305 bytesOut_ += bytesTransferred;
306 {
307 std::scoped_lock const lock(mutex_);
308 wq2_.clear();
309 wq2_.reserve(wq_.size());
311 }
312 if (!wq2_.empty())
313 {
315 v.reserve(wq2_.size());
316 for (auto const& b : wq2_)
317 v.emplace_back(b.data.get(), b.bytes);
318 startTimer();
319 return boost::asio::async_write(
320 impl().stream_,
321 v,
322 bind_executor(
323 strand_,
324 [self = impl().shared_from_this()](
325 error_code const& ec, std::size_t bytesTransferred) {
326 self->onWrite(ec, bytesTransferred);
327 }));
328 }
329 if (!complete_)
330 return;
331 if (graceful_)
332 return doClose();
333 util::spawn(strand_, [self = impl().shared_from_this()](yield_context doYield) {
334 self->doRead(doYield);
335 });
336}
337
338template <class Handler, class Impl>
339void
341 std::shared_ptr<Writer> const& writer,
342 bool keepAlive,
343 yield_context doYield)
344{
345 std::function<void(void)> resume;
346 {
347 auto const p = impl().shared_from_this();
348 resume = std::function<void(void)>([this, p, writer, keepAlive]() {
349 util::spawn(strand_, [p, writer, keepAlive](yield_context doYield) {
350 p->doWriter(writer, keepAlive, doYield);
351 });
352 });
353 }
354
355 for (;;)
356 {
357 if (!writer->prepare(kBufferSize, resume))
358 return;
359 error_code ec;
360 auto const bytesTransferred = boost::asio::async_write(
361 impl().stream_, writer->data(), boost::asio::transfer_at_least(1), doYield[ec]);
362 if (ec)
363 return fail(ec, "writer");
364 writer->consume(bytesTransferred);
365 if (writer->complete())
366 break;
367 }
368
369 if (!keepAlive)
370 return doClose();
371
372 util::spawn(strand_, [self = impl().shared_from_this()](yield_context doYield) {
373 self->doRead(doYield);
374 });
375}
376
377//------------------------------------------------------------------------------
378
379// Send a copy of the data.
380template <class Handler, class Impl>
381void
383{
384 if (bytes == 0)
385 return;
386 if ([&] {
387 std::scoped_lock const lock(mutex_);
388 wq_.emplace_back(buf, bytes);
389 return wq_.size() == 1 && wq2_.empty();
390 }())
391 {
392 if (!strand_.running_in_this_thread())
393 {
394 return post(
395 strand_, [self = impl().shared_from_this()] { self->onWrite(error_code{}, 0); });
396 }
397 return onWrite(error_code{}, 0);
398 }
399}
400
401template <class Handler, class Impl>
402void
404{
406 strand_, [self = impl().shared_from_this(), writer, keepAlive](yield_context doYield) {
407 self->doWriter(writer, keepAlive, doYield);
408 });
409}
410
411// DEPRECATED
412// Make the Session asynchronous
413template <class Handler, class Impl>
416{
417 return impl().shared_from_this();
418}
419
420// DEPRECATED
421// Called to indicate the response has been written(but not sent)
422template <class Handler, class Impl>
423void
425{
426 if (!strand_.running_in_this_thread())
427 {
428 return post(strand_, [self = impl().shared_from_this()] { self->complete(); });
429 }
430
431 message_ = {};
432 complete_ = true;
433
434 {
435 std::scoped_lock const lock(mutex_);
436 if (!wq_.empty() && !wq2_.empty())
437 return;
438 }
439
440 // keep-alive
441 util::spawn(strand_, [self = impl().shared_from_this()](yield_context doYield) {
442 self->doRead(doYield);
443 });
444}
445
446// DEPRECATED
447// Called from the Handler to close the session.
448template <class Handler, class Impl>
449void
451{
452 if (!strand_.running_in_this_thread())
453 {
454 return post(
455 strand_, [self = impl().shared_from_this(), graceful] { self->close(graceful); });
456 }
457
458 complete_ = true;
459 if (graceful)
460 {
461 graceful_ = true;
462 {
463 std::scoped_lock const lock(mutex_);
464 if (!wq_.empty() || !wq2_.empty())
465 return;
466 }
467 return doClose();
468 }
469
470 boost::beast::get_lowest_layer(impl().stream_).close();
471}
472
473} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
A version-independent IP address and port combination.
Definition IPEndpoint.h:24
void close() override
std::size_t bytesIn_
BaseHTTPPeer(Port const &port, Handler &handler, boost::asio::executor const &executor, beast::Journal journal, endpoint_type remoteAddress, ConstBufferSequence const &buffers)
endpoint_type remoteAddress_
http_request_type message_
Session & session()
void write(void const *buffer, std::size_t bytes) override
boost::asio::ip::tcp::endpoint endpoint_type
void onWrite(error_code const &ec, std::size_t bytesTransferred)
boost::system::error_code ec_
std::size_t bytesOut_
boost::system::error_code error_code
beast::Journal const journal_
void fail(error_code ec, char const *what)
virtual void doClose()=0
void doWriter(std::shared_ptr< Writer > const &writer, bool keepAlive, yield_context doYield)
boost::asio::streambuf readBuf_
std::shared_ptr< Session > detach() override
Detach the session.
Port const & port_
boost::asio::strand< boost::asio::executor > strand_
static constexpr auto kTimeoutSecondsLocal
boost::asio::executor_work_guard< boost::asio::executor > work_
virtual void doRequest()=0
void complete() override
Indicate that the response is complete.
std::vector< Buffer > wq2_
~BaseHTTPPeer() override
std::chrono::system_clock clock_type
static constexpr auto kBufferSize
std::vector< Buffer > wq_
void doRead(yield_context doYield)
static constexpr auto kTimeoutSeconds
http_request_type & request() override
Returns the current HTTP request.
boost::asio::yield_context yield_context
Session()=default
T emplace_back(T... args)
STL namespace.
void spawn(Ctx &&ctx, F &&func)
Spawns a coroutine using boost::asio::spawn.
Definition Spawn.h:67
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
T reserve(T... args)
static ip::Endpoint fromAsio(boost::asio::ip::address const &address)
Buffer(void const *ptr, std::size_t len)
std::unique_ptr< char[]> data
Configuration information for a Server listening port.
Definition Port.h:29
T swap(T... args)
T to_string(T... args)