Crow  0.3
A C++ microframework for the web
http_connection.h
1 #pragma once
2 #include <boost/asio.hpp>
3 #include <boost/algorithm/string/predicate.hpp>
4 #include <boost/lexical_cast.hpp>
5 #include <boost/array.hpp>
6 #include <atomic>
7 #include <chrono>
8 #include <vector>
9 
10 #include "crow/http_parser_merged.h"
11 #include "crow/common.h"
12 #include "crow/parser.h"
13 #include "crow/http_response.h"
14 #include "crow/logging.h"
15 #include "crow/settings.h"
16 #include "crow/task_timer.h"
17 #include "crow/middleware_context.h"
18 #include "crow/socket_adaptors.h"
19 #include "crow/compression.h"
20 
21 namespace crow
22 {
23  using namespace boost;
24  using tcp = asio::ip::tcp;
25 
26  namespace detail
27  {
28  template<typename MW>
30  {
31  template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::before_handle>
32  struct get
33  {};
34  };
35 
36  template<typename MW>
38  {
39  template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::before_handle>
40  struct get
41  {};
42  };
43 
44  template<typename MW>
46  {
47  template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::after_handle>
48  struct get
49  {};
50  };
51 
52  template<typename MW>
54  {
55  template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::after_handle>
56  struct get
57  {};
58  };
59 
60  template<typename T>
62  {
63  template<typename C>
64  static std::true_type f(typename check_before_handle_arity_3_const<T>::template get<C>*);
65 
66  template<typename C>
67  static std::true_type f(typename check_before_handle_arity_3<T>::template get<C>*);
68 
69  template<typename C>
70  static std::false_type f(...);
71 
72  public:
73  static const bool value = decltype(f<T>(nullptr))::value;
74  };
75 
76  template<typename T>
78  {
79  template<typename C>
80  static std::true_type f(typename check_after_handle_arity_3_const<T>::template get<C>*);
81 
82  template<typename C>
83  static std::true_type f(typename check_after_handle_arity_3<T>::template get<C>*);
84 
85  template<typename C>
86  static std::false_type f(...);
87 
88  public:
89  static const bool value = decltype(f<T>(nullptr))::value;
90  };
91 
92  template<typename MW, typename Context, typename ParentContext>
93  typename std::enable_if<!is_before_handle_arity_3_impl<MW>::value>::type
94  before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
95  {
96  mw.before_handle(req, res, ctx.template get<MW>(), ctx);
97  }
98 
99  template<typename MW, typename Context, typename ParentContext>
100  typename std::enable_if<is_before_handle_arity_3_impl<MW>::value>::type
101  before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
102  {
103  mw.before_handle(req, res, ctx.template get<MW>());
104  }
105 
106  template<typename MW, typename Context, typename ParentContext>
107  typename std::enable_if<!is_after_handle_arity_3_impl<MW>::value>::type
108  after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
109  {
110  mw.after_handle(req, res, ctx.template get<MW>(), ctx);
111  }
112 
113  template<typename MW, typename Context, typename ParentContext>
114  typename std::enable_if<is_after_handle_arity_3_impl<MW>::value>::type
115  after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
116  {
117  mw.after_handle(req, res, ctx.template get<MW>());
118  }
119 
120  template<int N, typename Context, typename Container, typename CurrentMW, typename... Middlewares>
121  bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
122  {
123  using parent_context_t = typename Context::template partial<N - 1>;
124  before_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
125 
126  if (res.is_completed())
127  {
128  after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
129  return true;
130  }
131 
132  if (middleware_call_helper<N + 1, Context, Container, Middlewares...>(middlewares, req, res, ctx))
133  {
134  after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
135  return true;
136  }
137 
138  return false;
139  }
140 
141  template<int N, typename Context, typename Container>
142  bool middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
143  {
144  return false;
145  }
146 
147  template<int N, typename Context, typename Container>
148  typename std::enable_if<(N < 0)>::type
149  after_handlers_call_helper(Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/)
150  {
151  }
152 
153  template<int N, typename Context, typename Container>
154  typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
155  {
156  using parent_context_t = typename Context::template partial<N - 1>;
157  using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
158  after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
159  }
160 
161  template<int N, typename Context, typename Container>
162  typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
163  {
164  using parent_context_t = typename Context::template partial<N - 1>;
165  using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
166  after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
167  after_handlers_call_helper<N - 1, Context, Container>(middlewares, ctx, req, res);
168  }
169  } // namespace detail
170 
171 #ifdef CROW_ENABLE_DEBUG
172  static std::atomic<int> connectionCount;
173 #endif
174 
175  /// An HTTP connection.
176  template<typename Adaptor, typename Handler, typename... Middlewares>
178  {
179  friend struct crow::response;
180 
181  public:
182  Connection(
183  boost::asio::io_service& io_service,
184  Handler* handler,
185  const std::string& server_name,
186  std::tuple<Middlewares...>* middlewares,
187  std::function<std::string()>& get_cached_date_str_f,
188  detail::task_timer& task_timer,
189  typename Adaptor::context* adaptor_ctx_):
190  adaptor_(io_service, adaptor_ctx_),
191  handler_(handler),
192  parser_(this),
193  server_name_(server_name),
194  middlewares_(middlewares),
195  get_cached_date_str(get_cached_date_str_f),
196  task_timer_(task_timer),
197  res_stream_threshold_(handler->stream_threshold())
198  {
199 #ifdef CROW_ENABLE_DEBUG
200  connectionCount++;
201  CROW_LOG_DEBUG << "Connection open, total " << connectionCount << ", " << this;
202 #endif
203  }
204 
205  ~Connection()
206  {
207  res.complete_request_handler_ = nullptr;
208  cancel_deadline_timer();
209 #ifdef CROW_ENABLE_DEBUG
210  connectionCount--;
211  CROW_LOG_DEBUG << "Connection closed, total " << connectionCount << ", " << this;
212 #endif
213  }
214 
215  /// The TCP socket on top of which the connection is established.
216  decltype(std::declval<Adaptor>().raw_socket())& socket()
217  {
218  return adaptor_.raw_socket();
219  }
220 
221  void start()
222  {
223  adaptor_.start([this](const boost::system::error_code& ec) {
224  if (!ec)
225  {
226  start_deadline();
227 
228  do_read();
229  }
230  else
231  {
232  CROW_LOG_ERROR << "Could not start adaptor: " << ec.message();
233  check_destroy();
234  }
235  });
236  }
237 
238  void handle_header()
239  {
240  // HTTP 1.1 Expect: 100-continue
241  if (parser_.check_version(1, 1) && parser_.headers.count("expect") && get_header_value(parser_.headers, "expect") == "100-continue")
242  {
243  buffers_.clear();
244  static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n";
245  buffers_.emplace_back(expect_100_continue.data(), expect_100_continue.size());
246  do_write();
247  }
248  }
249 
250  void handle()
251  {
252  cancel_deadline_timer();
253  bool is_invalid_request = false;
254  add_keep_alive_ = false;
255 
256  req_ = std::move(parser_.to_request());
257  request& req = req_;
258 
259  req.remote_ip_address = adaptor_.remote_endpoint().address().to_string();
260 
261  if (parser_.check_version(1, 0))
262  {
263  // HTTP/1.0
264  if (req.headers.count("connection"))
265  {
266  if (boost::iequals(req.get_header_value("connection"), "Keep-Alive"))
267  add_keep_alive_ = true;
268  }
269  else
270  close_connection_ = true;
271  }
272  else if (parser_.check_version(1, 1))
273  {
274  // HTTP/1.1
275  if (req.headers.count("connection"))
276  {
277  if (req.get_header_value("connection") == "close")
278  close_connection_ = true;
279  else if (boost::iequals(req.get_header_value("connection"), "Keep-Alive"))
280  add_keep_alive_ = true;
281  }
282  if (!req.headers.count("host"))
283  {
284  is_invalid_request = true;
285  res = response(400);
286  }
287  if (parser_.is_upgrade())
288  {
289  if (req.get_header_value("upgrade") == "h2c")
290  {
291  // TODO HTTP/2
292  // currently, ignore upgrade header
293  }
294  else
295  {
296  close_connection_ = true;
297  handler_->handle_upgrade(req, res, std::move(adaptor_));
298  return;
299  }
300  }
301  }
302 
303  CROW_LOG_INFO << "Request: " << boost::lexical_cast<std::string>(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << parser_.http_major << "." << parser_.http_minor << ' '
304  << method_name(req.method) << " " << req.url;
305 
306 
307  need_to_call_after_handlers_ = false;
308  if (!is_invalid_request)
309  {
310  res.complete_request_handler_ = [] {};
311  res.is_alive_helper_ = [this]() -> bool {
312  return adaptor_.is_open();
313  };
314 
315  ctx_ = detail::context<Middlewares...>();
316  req.middleware_context = static_cast<void*>(&ctx_);
317  req.io_service = &adaptor_.get_io_service();
318  detail::middleware_call_helper<0, decltype(ctx_), decltype(*middlewares_), Middlewares...>(*middlewares_, req, res, ctx_);
319 
320  if (!res.completed_)
321  {
322  res.complete_request_handler_ = [this] {
323  this->complete_request();
324  };
325  need_to_call_after_handlers_ = true;
326  handler_->handle(req, res);
327  if (add_keep_alive_)
328  res.set_header("connection", "Keep-Alive");
329  }
330  else
331  {
332  complete_request();
333  }
334  }
335  else
336  {
337  complete_request();
338  }
339  }
340 
341  /// Call the after handle middleware and send the write the response to the connection.
343  {
344  CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_;
345 
346  if (need_to_call_after_handlers_)
347  {
348  need_to_call_after_handlers_ = false;
349 
350  // call all after_handler of middlewares
351  detail::after_handlers_call_helper<
352  (static_cast<int>(sizeof...(Middlewares)) - 1),
353  decltype(ctx_),
354  decltype(*middlewares_)>(*middlewares_, ctx_, req_, res);
355  }
356 #ifdef CROW_ENABLE_COMPRESSION
357  if (handler_->compression_used())
358  {
359  std::string accept_encoding = req_.get_header_value("Accept-Encoding");
360  if (!accept_encoding.empty() && res.compressed)
361  {
362  switch (handler_->compression_algorithm())
363  {
364  case compression::DEFLATE:
365  if (accept_encoding.find("deflate") != std::string::npos)
366  {
367  res.body = compression::compress_string(res.body, compression::algorithm::DEFLATE);
368  res.set_header("Content-Encoding", "deflate");
369  }
370  break;
371  case compression::GZIP:
372  if (accept_encoding.find("gzip") != std::string::npos)
373  {
374  res.body = compression::compress_string(res.body, compression::algorithm::GZIP);
375  res.set_header("Content-Encoding", "gzip");
376  }
377  break;
378  default:
379  break;
380  }
381  }
382  }
383 #endif
384  //if there is a redirection with a partial URL, treat the URL as a route.
385  std::string location = res.get_header_value("Location");
386  if (!location.empty() && location.find("://", 0) == std::string::npos)
387  {
388 #ifdef CROW_ENABLE_SSL
389  if (handler_->ssl_used())
390  location.insert(0, "https://" + req_.get_header_value("Host"));
391  else
392 #endif
393  location.insert(0, "http://" + req_.get_header_value("Host"));
394  res.set_header("location", location);
395  }
396 
397  prepare_buffers();
398 
399  if (res.is_static_type())
400  {
401  do_write_static();
402  }
403  else
404  {
405  do_write_general();
406  }
407  }
408 
409  private:
410  void prepare_buffers()
411  {
412  //auto self = this->shared_from_this();
413  res.complete_request_handler_ = nullptr;
414 
415  if (!adaptor_.is_open())
416  {
417  //CROW_LOG_DEBUG << this << " delete (socket is closed) " << is_reading << ' ' << is_writing;
418  //delete this;
419  return;
420  }
421 
422  // Keep in sync with common.h/status
423  static std::unordered_map<int, std::string> statusCodes = {
424  {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
425  {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
426 
427  {status::OK, "HTTP/1.1 200 OK\r\n"},
428  {status::CREATED, "HTTP/1.1 201 Created\r\n"},
429  {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
430  {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
431  {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
432  {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
433  {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
434 
435  {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
436  {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
437  {status::FOUND, "HTTP/1.1 302 Found\r\n"},
438  {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
439  {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
440  {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
441  {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
442 
443  {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
444  {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
445  {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
446  {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
447  {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
448  {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
449  {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
450  {status::GONE, "HTTP/1.1 410 Gone\r\n"},
451  {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
452  {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
453  {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
454  {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
455  {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
456  {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
457  {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
458 
459  {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
460  {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
461  {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
462  {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
463  {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
464  };
465 
466  static std::string seperator = ": ";
467  static std::string crlf = "\r\n";
468 
469  buffers_.clear();
470  buffers_.reserve(4 * (res.headers.size() + 5) + 3);
471 
472  if (!statusCodes.count(res.code))
473  res.code = 500;
474  {
475  auto& status = statusCodes.find(res.code)->second;
476  buffers_.emplace_back(status.data(), status.size());
477  }
478 
479  if (res.code >= 400 && res.body.empty())
480  res.body = statusCodes[res.code].substr(9);
481 
482  for (auto& kv : res.headers)
483  {
484  buffers_.emplace_back(kv.first.data(), kv.first.size());
485  buffers_.emplace_back(seperator.data(), seperator.size());
486  buffers_.emplace_back(kv.second.data(), kv.second.size());
487  buffers_.emplace_back(crlf.data(), crlf.size());
488  }
489 
490  if (!res.manual_length_header && !res.headers.count("content-length"))
491  {
492  content_length_ = std::to_string(res.body.size());
493  static std::string content_length_tag = "Content-Length: ";
494  buffers_.emplace_back(content_length_tag.data(), content_length_tag.size());
495  buffers_.emplace_back(content_length_.data(), content_length_.size());
496  buffers_.emplace_back(crlf.data(), crlf.size());
497  }
498  if (!res.headers.count("server"))
499  {
500  static std::string server_tag = "Server: ";
501  buffers_.emplace_back(server_tag.data(), server_tag.size());
502  buffers_.emplace_back(server_name_.data(), server_name_.size());
503  buffers_.emplace_back(crlf.data(), crlf.size());
504  }
505  if (!res.headers.count("date"))
506  {
507  static std::string date_tag = "Date: ";
508  date_str_ = get_cached_date_str();
509  buffers_.emplace_back(date_tag.data(), date_tag.size());
510  buffers_.emplace_back(date_str_.data(), date_str_.size());
511  buffers_.emplace_back(crlf.data(), crlf.size());
512  }
513  if (add_keep_alive_)
514  {
515  static std::string keep_alive_tag = "Connection: Keep-Alive";
516  buffers_.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
517  buffers_.emplace_back(crlf.data(), crlf.size());
518  }
519 
520  buffers_.emplace_back(crlf.data(), crlf.size());
521  }
522 
523  void do_write_static()
524  {
525  is_writing = true;
526  boost::asio::write(adaptor_.socket(), buffers_);
527  res.do_stream_file(adaptor_);
528 
529  res.end();
530  res.clear();
531  buffers_.clear();
532  }
533 
534  void do_write_general()
535  {
536  if (res.body.length() < res_stream_threshold_)
537  {
538  res_body_copy_.swap(res.body);
539  buffers_.emplace_back(res_body_copy_.data(), res_body_copy_.size());
540 
541  do_write();
542 
543  if (need_to_start_read_after_complete_)
544  {
545  need_to_start_read_after_complete_ = false;
546  start_deadline();
547  do_read();
548  }
549  }
550  else
551  {
552  is_writing = true;
553  boost::asio::write(adaptor_.socket(), buffers_);
554  res.do_stream_body(adaptor_);
555 
556  res.end();
557  res.clear();
558  buffers_.clear();
559  }
560  }
561 
562  void do_read()
563  {
564  //auto self = this->shared_from_this();
565  is_reading = true;
566  adaptor_.socket().async_read_some(
567  boost::asio::buffer(buffer_),
568  [this](const boost::system::error_code& ec, std::size_t bytes_transferred) {
569  bool error_while_reading = true;
570  if (!ec)
571  {
572  bool ret = parser_.feed(buffer_.data(), bytes_transferred);
573  if (ret && adaptor_.is_open())
574  {
575  error_while_reading = false;
576  }
577  }
578 
579  if (error_while_reading)
580  {
581  cancel_deadline_timer();
582  parser_.done();
583  adaptor_.shutdown_read();
584  adaptor_.close();
585  is_reading = false;
586  CROW_LOG_DEBUG << this << " from read(1)";
587  check_destroy();
588  }
589  else if (close_connection_)
590  {
591  cancel_deadline_timer();
592  parser_.done();
593  is_reading = false;
594  check_destroy();
595  // adaptor will close after write
596  }
597  else if (!need_to_call_after_handlers_)
598  {
599  start_deadline();
600  do_read();
601  }
602  else
603  {
604  // res will be completed later by user
605  need_to_start_read_after_complete_ = true;
606  }
607  });
608  }
609 
610  void do_write()
611  {
612  //auto self = this->shared_from_this();
613  is_writing = true;
614  boost::asio::async_write(
615  adaptor_.socket(), buffers_,
616  [&](const boost::system::error_code& ec, std::size_t /*bytes_transferred*/) {
617  is_writing = false;
618  res.clear();
619  res_body_copy_.clear();
620  if (!ec)
621  {
622  if (close_connection_)
623  {
624  adaptor_.shutdown_write();
625  adaptor_.close();
626  CROW_LOG_DEBUG << this << " from write(1)";
627  check_destroy();
628  }
629  }
630  else
631  {
632  CROW_LOG_DEBUG << this << " from write(2)";
633  check_destroy();
634  }
635  });
636  }
637 
638  void check_destroy()
639  {
640  CROW_LOG_DEBUG << this << " is_reading " << is_reading << " is_writing " << is_writing;
641  if (!is_reading && !is_writing)
642  {
643  CROW_LOG_DEBUG << this << " delete (idle) ";
644  delete this;
645  }
646  }
647 
648  void cancel_deadline_timer()
649  {
650  CROW_LOG_DEBUG << this << " timer cancelled: " << &task_timer_ << ' ' << task_id_;
651  task_timer_.cancel(task_id_);
652  }
653 
654  void start_deadline(/*int timeout = 5*/)
655  {
656  cancel_deadline_timer();
657 
658  task_id_ = task_timer_.schedule([this] {
659  if (!adaptor_.is_open())
660  {
661  return;
662  }
663  adaptor_.shutdown_readwrite();
664  adaptor_.close();
665  });
666  CROW_LOG_DEBUG << this << " timer added: " << &task_timer_ << ' ' << task_id_;
667  }
668 
669  private:
670  Adaptor adaptor_;
671  Handler* handler_;
672 
673  boost::array<char, 4096> buffer_;
674 
675  HTTPParser<Connection> parser_;
676  request req_;
677  response res;
678 
679  bool close_connection_ = false;
680 
681  const std::string& server_name_;
682  std::vector<boost::asio::const_buffer> buffers_;
683 
684  std::string content_length_;
685  std::string date_str_;
686  std::string res_body_copy_;
687 
688  detail::task_timer::identifier_type task_id_;
689 
690  bool is_reading{};
691  bool is_writing{};
692  bool need_to_call_after_handlers_{};
693  bool need_to_start_read_after_complete_{};
694  bool add_keep_alive_{};
695 
696  std::tuple<Middlewares...>* middlewares_;
697  detail::context<Middlewares...> ctx_;
698 
699  std::function<std::string()>& get_cached_date_str;
700  detail::task_timer& task_timer_;
701 
702  size_t res_stream_threshold_;
703  };
704 
705 } // namespace crow
crow::detail::is_before_handle_arity_3_impl
Definition: http_connection.h:61
crow::detail::check_before_handle_arity_3
Definition: http_connection.h:37
crow::detail::check_after_handle_arity_3_const
Definition: http_connection.h:45
crow::detail::check_after_handle_arity_3_const::get
Definition: http_connection.h:48
crow::detail::check_before_handle_arity_3_const::get
Definition: http_connection.h:32
crow::detail::is_after_handle_arity_3_impl
Definition: http_connection.h:77
crow::detail::check_after_handle_arity_3::get
Definition: http_connection.h:56
crow::detail::check_before_handle_arity_3::get
Definition: http_connection.h:40
crow::request
An HTTP request.
Definition: http_request.h:26
crow::Connection
An HTTP connection.
Definition: http_connection.h:177
crow::Connection::complete_request
void complete_request()
Call the after handle middleware and send the write the response to the connection.
Definition: http_connection.h:342
crow::response
HTTP response.
Definition: http_response.h:23
crow::detail::check_after_handle_arity_3
Definition: http_connection.h:53
crow::Connection::socket
decltype(std::declval< Adaptor >().raw_socket()) & socket()
The TCP socket on top of which the connection is established.
Definition: http_connection.h:216
crow::detail::task_timer
A class for scheduling functions to be called after a specific amount of ticks. A tick is equal to 1 ...
Definition: task_timer.h:17
crow::detail::check_before_handle_arity_3_const
Definition: http_connection.h:29