rippled
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/utility/instrumentation.h>
6#include <xrpl/server/Session.h>
7#include <xrpl/server/detail/Spawn.h>
8#include <xrpl/server/detail/io_list.h>
9
10#include <boost/asio/ip/tcp.hpp>
11#include <boost/asio/spawn.hpp>
12#include <boost/asio/ssl/stream.hpp>
13#include <boost/asio/strand.hpp>
14#include <boost/asio/streambuf.hpp>
15#include <boost/beast/core/stream_traits.hpp>
16#include <boost/beast/http/dynamic_body.hpp>
17#include <boost/beast/http/message.hpp>
18#include <boost/beast/http/parser.hpp>
19#include <boost/beast/http/read.hpp>
20
21#include <atomic>
22#include <chrono>
23#include <functional>
24#include <memory>
25#include <mutex>
26#include <vector>
27
28namespace xrpl {
29
31template <class Handler, class Impl>
32class BaseHTTPPeer : public io_list::work, public Session
33{
34protected:
36 using error_code = boost::system::error_code;
37 using endpoint_type = boost::asio::ip::tcp::endpoint;
38 using yield_context = boost::asio::yield_context;
39
40 enum {
41 // Size of our read/write buffer
42 bufferSize = 4 * 1024,
43
44 // Max seconds without completing a message
46 timeoutSecondsLocal = 3 // used for localhost clients
47 };
48
49 struct buffer
50 {
51 buffer(void const* ptr, std::size_t len) : data(new char[len]), bytes(len), used(0)
52 {
53 memcpy(data.get(), ptr, len);
54 }
55
59 };
60
61 Port const& port_;
62 Handler& handler_;
63 boost::asio::executor_work_guard<boost::asio::executor> work_;
64 boost::asio::strand<boost::asio::executor> strand_;
67
70
71 boost::asio::streambuf read_buf_;
76 bool graceful_ = false;
77 bool complete_ = false;
78 boost::system::error_code ec_;
79
83
84 //--------------------------------------------------------------------------
85
86public:
87 template <class ConstBufferSequence>
89 Port const& port,
90 Handler& handler,
91 boost::asio::executor const& executor,
93 endpoint_type remote_address,
94 ConstBufferSequence const& buffers);
95
96 virtual ~BaseHTTPPeer();
97
98 Session&
100 {
101 return *this;
102 }
103
104 void
105 close() override;
106
107protected:
108 Impl&
110 {
111 return *static_cast<Impl*>(this);
112 }
113
114 void
115 fail(error_code ec, char const* what);
116
117 void
119
120 void
122
123 void
125
126 void
128
129 void
130 on_write(error_code const& ec, std::size_t bytes_transferred);
131
132 void
133 do_writer(std::shared_ptr<Writer> const& writer, bool keep_alive, yield_context do_yield);
134
135 virtual void
137
138 virtual void
139 do_close() = 0;
140
141 // Session
142
144 journal() override
145 {
146 return journal_;
147 }
148
149 Port const&
150 port() override
151 {
152 return port_;
153 }
154
160
162 request() override
163 {
164 return message_;
165 }
166
167 void
168 write(void const* buffer, std::size_t bytes) override;
169
170 void
171 write(std::shared_ptr<Writer> const& writer, bool keep_alive) override;
172
174 detach() override;
175
176 void
177 complete() override;
178
179 void
180 close(bool graceful) override;
181};
182
183//------------------------------------------------------------------------------
184
185template <class Handler, class Impl>
186template <class ConstBufferSequence>
188 Port const& port,
189 Handler& handler,
190 boost::asio::executor const& executor,
191 beast::Journal journal,
192 endpoint_type remote_address,
193 ConstBufferSequence const& buffers)
194 : port_(port)
195 , handler_(handler)
196 , work_(boost::asio::make_work_guard(executor))
197 , strand_(boost::asio::make_strand(executor))
198 , remote_address_(remote_address)
199 , journal_(journal)
200{
201 read_buf_.commit(boost::asio::buffer_copy(read_buf_.prepare(boost::asio::buffer_size(buffers)), buffers));
202 static std::atomic<int> sid;
203 nid_ = ++sid;
204 id_ = std::string("#") + std::to_string(nid_) + " ";
205 JLOG(journal_.trace()) << id_ << "accept: " << remote_address_.address();
206}
207
208template <class Handler, class Impl>
210{
211 handler_.onClose(session(), ec_);
212 JLOG(journal_.trace()) << id_ << "destroyed: " << request_count_
213 << ((request_count_ == 1) ? " request" : " requests");
214}
215
216template <class Handler, class Impl>
217void
219{
220 if (!strand_.running_in_this_thread())
221 return post(strand_, std::bind((void(BaseHTTPPeer::*)(void)) & BaseHTTPPeer::close, impl().shared_from_this()));
222 boost::beast::get_lowest_layer(impl().stream_).close();
223}
224
225//------------------------------------------------------------------------------
226
227template <class Handler, class Impl>
228void
230{
231 if (!ec_ && ec != boost::asio::error::operation_aborted)
232 {
233 ec_ = ec;
234 JLOG(journal_.trace()) << id_ << std::string(what) << ": " << ec.message();
235 boost::beast::get_lowest_layer(impl().stream_).close();
236 }
237}
238
239template <class Handler, class Impl>
240void
242{
243 boost::beast::get_lowest_layer(impl().stream_)
244 .expires_after(
245 std::chrono::seconds(remote_address_.address().is_loopback() ? timeoutSecondsLocal : timeoutSeconds));
246}
247
248// Convenience for discarding the error code
249template <class Handler, class Impl>
250void
252{
253 boost::beast::get_lowest_layer(impl().stream_).expires_never();
254}
255
256// Called when session times out
257template <class Handler, class Impl>
258void
260{
261 auto ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
262 fail(ec, "timer");
263}
264
265//------------------------------------------------------------------------------
266
267template <class Handler, class Impl>
268void
270{
271 complete_ = false;
272 error_code ec;
273 start_timer();
274 boost::beast::http::async_read(impl().stream_, read_buf_, message_, do_yield[ec]);
275 cancel_timer();
276 if (ec == boost::beast::http::error::end_of_stream)
277 return do_close();
278 if (ec == boost::beast::error::timeout)
279 return on_timer();
280 if (ec)
281 return fail(ec, "http::read");
282 do_request();
283}
284
285// Send everything in the write queue.
286// The write queue must not be empty upon entry.
287template <class Handler, class Impl>
288void
290{
291 cancel_timer();
292 if (ec == boost::beast::error::timeout)
293 return on_timer();
294 if (ec)
295 return fail(ec, "write");
296 bytes_out_ += bytes_transferred;
297 {
298 std::lock_guard lock(mutex_);
299 wq2_.clear();
300 wq2_.reserve(wq_.size());
301 std::swap(wq2_, wq_);
302 }
303 if (!wq2_.empty())
304 {
306 v.reserve(wq2_.size());
307 for (auto const& b : wq2_)
308 v.emplace_back(b.data.get(), b.bytes);
309 start_timer();
310 return boost::asio::async_write(
311 impl().stream_,
312 v,
313 bind_executor(
314 strand_,
315 std::bind(
316 &BaseHTTPPeer::on_write, impl().shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
317 }
318 if (!complete_)
319 return;
320 if (graceful_)
321 return do_close();
323 strand_, std::bind(&BaseHTTPPeer<Handler, Impl>::do_read, impl().shared_from_this(), std::placeholders::_1));
324}
325
326template <class Handler, class Impl>
327void
329{
330 std::function<void(void)> resume;
331 {
332 auto const p = impl().shared_from_this();
333 resume = std::function<void(void)>([this, p, writer, keep_alive]() {
335 strand_,
336 std::bind(&BaseHTTPPeer<Handler, Impl>::do_writer, p, writer, keep_alive, std::placeholders::_1));
337 });
338 }
339
340 for (;;)
341 {
342 if (!writer->prepare(bufferSize, resume))
343 return;
344 error_code ec;
345 auto const bytes_transferred =
346 boost::asio::async_write(impl().stream_, writer->data(), boost::asio::transfer_at_least(1), do_yield[ec]);
347 if (ec)
348 return fail(ec, "writer");
349 writer->consume(bytes_transferred);
350 if (writer->complete())
351 break;
352 }
353
354 if (!keep_alive)
355 return do_close();
356
358 strand_, std::bind(&BaseHTTPPeer<Handler, Impl>::do_read, impl().shared_from_this(), std::placeholders::_1));
359}
360
361//------------------------------------------------------------------------------
362
363// Send a copy of the data.
364template <class Handler, class Impl>
365void
367{
368 if (bytes == 0)
369 return;
370 if ([&] {
371 std::lock_guard lock(mutex_);
372 wq_.emplace_back(buf, bytes);
373 return wq_.size() == 1 && wq2_.size() == 0;
374 }())
375 {
376 if (!strand_.running_in_this_thread())
377 return post(strand_, std::bind(&BaseHTTPPeer::on_write, impl().shared_from_this(), error_code{}, 0));
378 else
379 return on_write(error_code{}, 0);
380 }
381}
382
383template <class Handler, class Impl>
384void
386{
388 strand_,
389 std::bind(
391 impl().shared_from_this(),
392 writer,
393 keep_alive,
394 std::placeholders::_1));
395}
396
397// DEPRECATED
398// Make the Session asynchronous
399template <class Handler, class Impl>
402{
403 return impl().shared_from_this();
404}
405
406// DEPRECATED
407// Called to indicate the response has been written(but not sent)
408template <class Handler, class Impl>
409void
411{
412 if (!strand_.running_in_this_thread())
413 return post(strand_, std::bind(&BaseHTTPPeer<Handler, Impl>::complete, impl().shared_from_this()));
414
415 message_ = {};
416 complete_ = true;
417
418 {
419 std::lock_guard lock(mutex_);
420 if (!wq_.empty() && !wq2_.empty())
421 return;
422 }
423
424 // keep-alive
426 strand_, std::bind(&BaseHTTPPeer<Handler, Impl>::do_read, impl().shared_from_this(), std::placeholders::_1));
427}
428
429// DEPRECATED
430// Called from the Handler to close the session.
431template <class Handler, class Impl>
432void
434{
435 if (!strand_.running_in_this_thread())
436 return post(
437 strand_,
438 std::bind(
440 impl().shared_from_this(),
441 graceful));
442
443 complete_ = true;
444 if (graceful)
445 {
446 graceful_ = true;
447 {
448 std::lock_guard lock(mutex_);
449 if (!wq_.empty() || !wq2_.empty())
450 return;
451 }
452 return do_close();
453 }
454
455 boost::beast::get_lowest_layer(impl().stream_).close();
456}
457
458} // namespace xrpl
T bind(T... args)
A version-independent IP address and port combination.
Definition IPEndpoint.h:18
A generic endpoint for log messages.
Definition Journal.h:40
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
Represents an active connection.
void close() override
boost::asio::streambuf read_buf_
http_request_type message_
Session & session()
void write(void const *buffer, std::size_t bytes) override
virtual ~BaseHTTPPeer()
beast::IP::Endpoint remoteAddress() override
Returns the remote address of the connection.
std::vector< buffer > wq_
boost::asio::ip::tcp::endpoint endpoint_type
void do_read(yield_context do_yield)
boost::system::error_code ec_
boost::system::error_code error_code
beast::Journal const journal_
void fail(error_code ec, char const *what)
endpoint_type remote_address_
virtual void do_request()=0
std::size_t bytes_out_
std::vector< buffer > wq2_
std::shared_ptr< Session > detach() override
Detach the session.
Port const & port_
void on_write(error_code const &ec, std::size_t bytes_transferred)
beast::Journal journal() override
Returns the Journal to use for logging.
boost::asio::strand< boost::asio::executor > strand_
BaseHTTPPeer(Port const &port, Handler &handler, boost::asio::executor const &executor, beast::Journal journal, endpoint_type remote_address, ConstBufferSequence const &buffers)
boost::asio::executor_work_guard< boost::asio::executor > work_
void complete() override
Indicate that the response is complete.
void write(std::shared_ptr< Writer > const &writer, bool keep_alive) override
void close(bool graceful) override
Close the session.
Port const & port() override
Returns the Port settings for this connection.
void do_writer(std::shared_ptr< Writer > const &writer, bool keep_alive, yield_context do_yield)
http_request_type & request() override
Returns the current HTTP request.
boost::asio::yield_context yield_context
virtual void do_close()=0
std::size_t bytes_in_
Persistent state information for a connection session.
Definition Session.h:23
T emplace_back(T... args)
T get(T... args)
void spawn(Ctx &&ctx, F &&func)
Spawns a coroutine using boost::asio::spawn
Definition Spawn.h:65
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 from_asio(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:30
T swap(T... args)
T to_string(T... args)