rippled
Loading...
Searching...
No Matches
BaseHTTPPeer.h
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#ifndef RIPPLE_SERVER_BASEHTTPPEER_H_INCLUDED
21#define RIPPLE_SERVER_BASEHTTPPEER_H_INCLUDED
22
23#include <xrpl/basics/Log.h>
24#include <xrpl/beast/net/IPAddressConversion.h>
25#include <xrpl/beast/utility/instrumentation.h>
26#include <xrpl/server/Session.h>
27#include <xrpl/server/detail/Spawn.h>
28#include <xrpl/server/detail/io_list.h>
29
30#include <boost/asio/ip/tcp.hpp>
31#include <boost/asio/spawn.hpp>
32#include <boost/asio/ssl/stream.hpp>
33#include <boost/asio/strand.hpp>
34#include <boost/asio/streambuf.hpp>
35#include <boost/beast/core/stream_traits.hpp>
36#include <boost/beast/http/dynamic_body.hpp>
37#include <boost/beast/http/message.hpp>
38#include <boost/beast/http/parser.hpp>
39#include <boost/beast/http/read.hpp>
40
41#include <atomic>
42#include <chrono>
43#include <functional>
44#include <memory>
45#include <mutex>
46#include <vector>
47
48namespace ripple {
49
51template <class Handler, class Impl>
52class BaseHTTPPeer : public io_list::work, public Session
53{
54protected:
56 using error_code = boost::system::error_code;
57 using endpoint_type = boost::asio::ip::tcp::endpoint;
58 using yield_context = boost::asio::yield_context;
59
60 enum {
61 // Size of our read/write buffer
62 bufferSize = 4 * 1024,
63
64 // Max seconds without completing a message
66 timeoutSecondsLocal = 3 // used for localhost clients
67 };
68
69 struct buffer
70 {
71 buffer(void const* ptr, std::size_t len)
72 : data(new char[len]), bytes(len), used(0)
73 {
74 memcpy(data.get(), ptr, len);
75 }
76
80 };
81
82 Port const& port_;
83 Handler& handler_;
84 boost::asio::executor_work_guard<boost::asio::executor> work_;
85 boost::asio::strand<boost::asio::executor> strand_;
88
91
92 boost::asio::streambuf read_buf_;
97 bool graceful_ = false;
98 bool complete_ = false;
99 boost::system::error_code ec_;
100
104
105 //--------------------------------------------------------------------------
106
107public:
108 template <class ConstBufferSequence>
110 Port const& port,
111 Handler& handler,
112 boost::asio::executor const& executor,
114 endpoint_type remote_address,
115 ConstBufferSequence const& buffers);
116
117 virtual ~BaseHTTPPeer();
118
119 Session&
121 {
122 return *this;
123 }
124
125 void
126 close() override;
127
128protected:
129 Impl&
131 {
132 return *static_cast<Impl*>(this);
133 }
134
135 void
136 fail(error_code ec, char const* what);
137
138 void
140
141 void
143
144 void
146
147 void
149
150 void
151 on_write(error_code const& ec, std::size_t bytes_transferred);
152
153 void
155 std::shared_ptr<Writer> const& writer,
156 bool keep_alive,
157 yield_context do_yield);
158
159 virtual void
161
162 virtual void
163 do_close() = 0;
164
165 // Session
166
168 journal() override
169 {
170 return journal_;
171 }
172
173 Port const&
174 port() override
175 {
176 return port_;
177 }
178
184
186 request() override
187 {
188 return message_;
189 }
190
191 void
192 write(void const* buffer, std::size_t bytes) override;
193
194 void
195 write(std::shared_ptr<Writer> const& writer, bool keep_alive) override;
196
198 detach() override;
199
200 void
201 complete() override;
202
203 void
204 close(bool graceful) override;
205};
206
207//------------------------------------------------------------------------------
208
209template <class Handler, class Impl>
210template <class ConstBufferSequence>
212 Port const& port,
213 Handler& handler,
214 boost::asio::executor const& executor,
215 beast::Journal journal,
216 endpoint_type remote_address,
217 ConstBufferSequence const& buffers)
218 : port_(port)
219 , handler_(handler)
220 , work_(boost::asio::make_work_guard(executor))
221 , strand_(boost::asio::make_strand(executor))
222 , remote_address_(remote_address)
223 , journal_(journal)
224{
225 read_buf_.commit(boost::asio::buffer_copy(
226 read_buf_.prepare(boost::asio::buffer_size(buffers)), buffers));
227 static std::atomic<int> sid;
228 nid_ = ++sid;
229 id_ = std::string("#") + std::to_string(nid_) + " ";
230 JLOG(journal_.trace()) << id_ << "accept: " << remote_address_.address();
231}
232
233template <class Handler, class Impl>
235{
236 handler_.onClose(session(), ec_);
237 JLOG(journal_.trace()) << id_ << "destroyed: " << request_count_
238 << ((request_count_ == 1) ? " request"
239 : " requests");
240}
241
242template <class Handler, class Impl>
243void
245{
246 if (!strand_.running_in_this_thread())
247 return post(
248 strand_,
249 std::bind(
250 (void(BaseHTTPPeer::*)(void)) & BaseHTTPPeer::close,
251 impl().shared_from_this()));
252 boost::beast::get_lowest_layer(impl().stream_).close();
253}
254
255//------------------------------------------------------------------------------
256
257template <class Handler, class Impl>
258void
260{
261 if (!ec_ && ec != boost::asio::error::operation_aborted)
262 {
263 ec_ = ec;
264 JLOG(journal_.trace())
265 << id_ << std::string(what) << ": " << ec.message();
266 boost::beast::get_lowest_layer(impl().stream_).close();
267 }
268}
269
270template <class Handler, class Impl>
271void
273{
274 boost::beast::get_lowest_layer(impl().stream_)
275 .expires_after(std::chrono::seconds(
276 remote_address_.address().is_loopback() ? timeoutSecondsLocal
277 : timeoutSeconds));
278}
279
280// Convenience for discarding the error code
281template <class Handler, class Impl>
282void
284{
285 boost::beast::get_lowest_layer(impl().stream_).expires_never();
286}
287
288// Called when session times out
289template <class Handler, class Impl>
290void
292{
293 auto ec =
294 boost::system::errc::make_error_code(boost::system::errc::timed_out);
295 fail(ec, "timer");
296}
297
298//------------------------------------------------------------------------------
299
300template <class Handler, class Impl>
301void
303{
304 complete_ = false;
305 error_code ec;
306 start_timer();
307 boost::beast::http::async_read(
308 impl().stream_, read_buf_, message_, do_yield[ec]);
309 cancel_timer();
310 if (ec == boost::beast::http::error::end_of_stream)
311 return do_close();
312 if (ec == boost::beast::error::timeout)
313 return on_timer();
314 if (ec)
315 return fail(ec, "http::read");
316 do_request();
317}
318
319// Send everything in the write queue.
320// The write queue must not be empty upon entry.
321template <class Handler, class Impl>
322void
324 error_code const& ec,
325 std::size_t bytes_transferred)
326{
327 cancel_timer();
328 if (ec == boost::beast::error::timeout)
329 return on_timer();
330 if (ec)
331 return fail(ec, "write");
332 bytes_out_ += bytes_transferred;
333 {
334 std::lock_guard lock(mutex_);
335 wq2_.clear();
336 wq2_.reserve(wq_.size());
337 std::swap(wq2_, wq_);
338 }
339 if (!wq2_.empty())
340 {
342 v.reserve(wq2_.size());
343 for (auto const& b : wq2_)
344 v.emplace_back(b.data.get(), b.bytes);
345 start_timer();
346 return boost::asio::async_write(
347 impl().stream_,
348 v,
349 bind_executor(
350 strand_,
351 std::bind(
353 impl().shared_from_this(),
354 std::placeholders::_1,
355 std::placeholders::_2)));
356 }
357 if (!complete_)
358 return;
359 if (graceful_)
360 return do_close();
362 strand_,
363 std::bind(
365 impl().shared_from_this(),
366 std::placeholders::_1));
367}
368
369template <class Handler, class Impl>
370void
372 std::shared_ptr<Writer> const& writer,
373 bool keep_alive,
374 yield_context do_yield)
375{
376 std::function<void(void)> resume;
377 {
378 auto const p = impl().shared_from_this();
379 resume = std::function<void(void)>([this, p, writer, keep_alive]() {
381 strand_,
382 std::bind(
384 p,
385 writer,
386 keep_alive,
387 std::placeholders::_1));
388 });
389 }
390
391 for (;;)
392 {
393 if (!writer->prepare(bufferSize, resume))
394 return;
395 error_code ec;
396 auto const bytes_transferred = boost::asio::async_write(
397 impl().stream_,
398 writer->data(),
399 boost::asio::transfer_at_least(1),
400 do_yield[ec]);
401 if (ec)
402 return fail(ec, "writer");
403 writer->consume(bytes_transferred);
404 if (writer->complete())
405 break;
406 }
407
408 if (!keep_alive)
409 return do_close();
410
412 strand_,
413 std::bind(
415 impl().shared_from_this(),
416 std::placeholders::_1));
417}
418
419//------------------------------------------------------------------------------
420
421// Send a copy of the data.
422template <class Handler, class Impl>
423void
425{
426 if (bytes == 0)
427 return;
428 if ([&] {
429 std::lock_guard lock(mutex_);
430 wq_.emplace_back(buf, bytes);
431 return wq_.size() == 1 && wq2_.size() == 0;
432 }())
433 {
434 if (!strand_.running_in_this_thread())
435 return post(
436 strand_,
437 std::bind(
439 impl().shared_from_this(),
440 error_code{},
441 0));
442 else
443 return on_write(error_code{}, 0);
444 }
445}
446
447template <class Handler, class Impl>
448void
450 std::shared_ptr<Writer> const& writer,
451 bool keep_alive)
452{
454 strand_,
455 std::bind(
457 impl().shared_from_this(),
458 writer,
459 keep_alive,
460 std::placeholders::_1));
461}
462
463// DEPRECATED
464// Make the Session asynchronous
465template <class Handler, class Impl>
468{
469 return impl().shared_from_this();
470}
471
472// DEPRECATED
473// Called to indicate the response has been written(but not sent)
474template <class Handler, class Impl>
475void
477{
478 if (!strand_.running_in_this_thread())
479 return post(
480 strand_,
481 std::bind(
483 impl().shared_from_this()));
484
485 message_ = {};
486 complete_ = true;
487
488 {
489 std::lock_guard lock(mutex_);
490 if (!wq_.empty() && !wq2_.empty())
491 return;
492 }
493
494 // keep-alive
496 strand_,
497 std::bind(
499 impl().shared_from_this(),
500 std::placeholders::_1));
501}
502
503// DEPRECATED
504// Called from the Handler to close the session.
505template <class Handler, class Impl>
506void
508{
509 if (!strand_.running_in_this_thread())
510 return post(
511 strand_,
512 std::bind(
513 (void(BaseHTTPPeer::*)(bool)) &
515 impl().shared_from_this(),
516 graceful));
517
518 complete_ = true;
519 if (graceful)
520 {
521 graceful_ = true;
522 {
523 std::lock_guard lock(mutex_);
524 if (!wq_.empty() || !wq2_.empty())
525 return;
526 }
527 return do_close();
528 }
529
530 boost::beast::get_lowest_layer(impl().stream_).close();
531}
532
533} // namespace ripple
534
535#endif
T bind(T... args)
A version-independent IP address and port combination.
Definition IPEndpoint.h:38
A generic endpoint for log messages.
Definition Journal.h:60
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Represents an active connection.
BaseHTTPPeer(Port const &port, Handler &handler, boost::asio::executor const &executor, beast::Journal journal, endpoint_type remote_address, ConstBufferSequence const &buffers)
boost::asio::yield_context yield_context
void write(void const *buffer, std::size_t bytes) override
boost::system::error_code error_code
Port const & port() override
Returns the Port settings for this connection.
http_request_type message_
std::vector< buffer > wq2_
boost::asio::strand< boost::asio::executor > strand_
void on_write(error_code const &ec, std::size_t bytes_transferred)
void write(std::shared_ptr< Writer > const &writer, bool keep_alive) override
virtual void do_close()=0
beast::Journal const journal_
endpoint_type remote_address_
boost::asio::ip::tcp::endpoint endpoint_type
virtual void do_request()=0
boost::asio::streambuf read_buf_
std::vector< buffer > wq_
boost::system::error_code ec_
beast::IP::Endpoint remoteAddress() override
Returns the remote address of the connection.
void close(bool graceful) override
Close the session.
std::shared_ptr< Session > detach() override
Detach the session.
http_request_type & request() override
Returns the current HTTP request.
void complete() override
Indicate that the response is complete.
void do_writer(std::shared_ptr< Writer > const &writer, bool keep_alive, yield_context do_yield)
void fail(error_code ec, char const *what)
void do_read(yield_context do_yield)
void close() override
boost::asio::executor_work_guard< boost::asio::executor > work_
beast::Journal journal() override
Returns the Journal to use for logging.
Persistent state information for a connection session.
Definition Session.h:43
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:87
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:33
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:50
T swap(T... args)
T to_string(T... args)