identt
HttpServerBase.hpp
Go to the documentation of this file.
1 
33 #ifndef _IDENTT_HTTP_SERVER_BASE_HPP_
34 #define _IDENTT_HTTP_SERVER_BASE_HPP_
35 
36 #include "AsioCompat.hpp"
37 #include "Mutex.hpp"
38 #include "Utility.hpp"
39 #include <functional>
40 #include <iostream>
41 #include <limits>
42 #include <list>
43 #include <map>
44 #include <sstream>
45 #include <unordered_set>
46 #include <regex>
47 
48 namespace identt {
49 namespace http {
50 template <class socket_type> class HttpServer;
51 
52 template <class socket_type>
54 protected:
55  class Connection;
56  class Session;
57 
58 public:
60  class Response : public std::enable_shared_from_this<Response>, public std::ostream {
61  friend class HttpServerBase<socket_type>;
62  friend class HttpServer<socket_type>;
63 
64  std::unique_ptr<asio::streambuf> streambuf = std::unique_ptr<asio::streambuf>(new asio::streambuf());
65 
66  std::shared_ptr<Session> session;
67  long timeout_content;
68 
69  Mutex send_queue_mutex;
70  std::list<std::pair<std::shared_ptr<asio::streambuf>, std::function<void(const error_code &)>>> send_queue GUARDED_BY(send_queue_mutex);
71 
72  Response(std::shared_ptr<Session> session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content)
73  {
74  rdbuf(streambuf.get());
75  }
76 
77  template <typename size_type>
78  void write_header(const CaseInsensitiveMultimap &header, size_type size)
79  {
80  bool content_length_written = false;
81  bool chunked_transfer_encoding = false;
82  for(auto &field : header) {
83  if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
84  content_length_written = true;
85  else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
86  chunked_transfer_encoding = true;
87 
88  *this << field.first << ": " << field.second << "\r\n";
89  }
90  if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
91  *this << "Content-Length: " << size << "\r\n\r\n";
92  else
93  *this << "\r\n";
94  }
95 
96  void send_from_queue() REQUIRES(send_queue_mutex)
97  {
98  auto self = this->shared_from_this();
99  asio::async_write(*self->session->connection->socket, *send_queue.begin()->first, [self](const error_code &ec, std::size_t /*bytes_transferred*/) {
100  auto lock = self->session->connection->handler_runner->continue_lock();
101  if(!lock)
102  return;
103  {
104  LockGuard lock(self->send_queue_mutex);
105  if(!ec) {
106  auto it = self->send_queue.begin();
107  auto callback = std::move(it->second);
108  self->send_queue.erase(it);
109  if(self->send_queue.size() > 0)
110  self->send_from_queue();
111 
112  lock.unlock();
113  if(callback)
114  callback(ec);
115  }
116  else {
117  // All handlers in the queue is called with ec:
118  std::vector<std::function<void(const error_code &)>> callbacks;
119  for(auto &pair : self->send_queue) {
120  if(pair.second)
121  callbacks.emplace_back(std::move(pair.second));
122  }
123  self->send_queue.clear();
124 
125  lock.unlock();
126  for(auto &callback : callbacks)
127  callback(ec);
128  }
129  }
130  });
131  }
132 
133  void send_on_delete(const std::function<void(const error_code &)> &callback = nullptr) noexcept
134  {
135  session->connection->set_timeout(timeout_content);
136  auto self = this->shared_from_this(); // Keep Response instance alive through the following async_write
137  asio::async_write(*session->connection->socket, *streambuf, [self, callback](const error_code &ec, std::size_t /*bytes_transferred*/) {
138  self->session->connection->cancel_timeout();
139  auto lock = self->session->connection->handler_runner->continue_lock();
140  if(!lock)
141  return;
142  if(callback)
143  callback(ec);
144  });
145  }
146 
147  public:
148  std::size_t size() noexcept
149  {
150  return streambuf->size();
151  }
152 
156  void send(const std::function<void(const error_code &)> &callback = nullptr) noexcept
157  {
158  session->connection->set_timeout(timeout_content);
159 
160  std::shared_ptr<asio::streambuf> streambuf = std::move(this->streambuf);
161  this->streambuf = std::unique_ptr<asio::streambuf>(new asio::streambuf());
162  rdbuf(this->streambuf.get());
163 
164  LockGuard lock(send_queue_mutex);
165  send_queue.emplace_back(streambuf, callback);
166  if(send_queue.size() == 1)
167  send_from_queue();
168  }
169 
171  void write(const char_type *ptr, std::streamsize n)
172  {
173  std::ostream::write(ptr, n);
174  }
175 
177  void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap())
178  {
179  *this << "HTTP/1.1 " << identt::http::status_code(status_code) << "\r\n";
180  write_header(header, 0);
181  }
182 
184  void write(StatusCode status_code, string_view content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap())
185  {
186  *this << "HTTP/1.1 " << identt::http::status_code(status_code) << "\r\n";
187  write_header(header, content.size());
188  if(!content.empty())
189  *this << content;
190  }
191 
193  void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap())
194  {
195  *this << "HTTP/1.1 " << identt::http::status_code(status_code) << "\r\n";
196  content.seekg(0, std::ios::end);
197  auto size = content.tellg();
198  content.seekg(0, std::ios::beg);
199  write_header(header, size);
200  if(size)
201  *this << content.rdbuf();
202  }
203 
205  void write(string_view content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap())
206  {
207  write(StatusCode::success_ok, content, header);
208  }
209 
211  void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap())
212  {
213  write(StatusCode::success_ok, content, header);
214  }
215 
217  void write(const CaseInsensitiveMultimap &header)
218  {
219  write(StatusCode::success_ok, std::string(), header);
220  }
221 
227  };
228 
229  class Content : public std::istream {
230  friend class HttpServerBase<socket_type>;
231 
232  public:
233  std::size_t size() noexcept
234  {
235  return streambuf.size();
236  }
238  std::string string() noexcept
239  {
240  try {
241  std::string str;
242  auto size = streambuf.size();
243  str.resize(size);
244  read(&str[0], static_cast<std::streamsize>(size));
245  return str;
246  }
247  catch(...) {
248  return std::string();
249  }
250  }
251 
252  private:
253  asio::streambuf &streambuf;
254  Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {}
255  };
256 
257  class Request {
258  friend class HttpServerBase<socket_type>;
259  friend class HttpServer<socket_type>;
260  friend class Session;
261 
262  asio::streambuf streambuf;
263  std::weak_ptr<Connection> connection;
264  std::string optimization = std::to_string(0); // TODO: figure out what goes wrong in gcc optimization without this line
265 
266  Request(std::size_t max_request_streambuf_size, const std::shared_ptr<Connection> &connection_) noexcept : streambuf(max_request_streambuf_size), connection(connection_), content(streambuf) {}
267 
268  public:
269  std::string method, path, query_string, http_version;
270 
271  Content content;
272 
273  CaseInsensitiveMultimap header;
274 
276  std::smatch path_match;
277 
279  std::chrono::system_clock::time_point header_read_time;
280 
281  asio::ip::tcp::endpoint remote_endpoint() const noexcept
282  {
283  try {
284  if(auto connection = this->connection.lock())
285  return connection->socket->lowest_layer().remote_endpoint();
286  }
287  catch(...) {
288  }
289  return asio::ip::tcp::endpoint();
290  }
291 
293  DEPRECATED std::string remote_endpoint_address() const noexcept
294  {
295  try {
296  if(auto connection = this->connection.lock())
297  return connection->socket->lowest_layer().remote_endpoint().address().to_string();
298  }
299  catch(...) {
300  }
301  return std::string();
302  }
303 
305  DEPRECATED unsigned short remote_endpoint_port() const noexcept
306  {
307  try {
308  if(auto connection = this->connection.lock())
309  return connection->socket->lowest_layer().remote_endpoint().port();
310  }
311  catch(...) {
312  }
313  return 0;
314  }
315 
317  CaseInsensitiveMultimap parse_query_string() const noexcept
318  {
319  return identt::http::QueryString::parse(query_string);
320  }
321  };
322 
323 public:
324  using RespPtr = std::shared_ptr<Response>;
325  using ReqPtr = std::shared_ptr<Request>;
326 
327 protected:
328  class Connection : public std::enable_shared_from_this<Connection> {
329  public:
330  template <typename... Args>
331  Connection(std::shared_ptr<ScopeRunner> handler_runner_, Args &&... args) noexcept : handler_runner(std::move(handler_runner_)), socket(new socket_type(std::forward<Args>(args)...)) {}
332 
333  std::shared_ptr<ScopeRunner> handler_runner;
334 
335  std::unique_ptr<socket_type> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
336 
337  std::unique_ptr<asio::steady_timer> timer;
338 
339  void close() noexcept
340  {
341  error_code ec;
342  socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
343  socket->lowest_layer().cancel(ec);
344  }
345 
346  void set_timeout(long seconds) noexcept
347  {
348  if(seconds == 0) {
349  timer = nullptr;
350  return;
351  }
352 
353  timer = std::unique_ptr<asio::steady_timer>(new asio::steady_timer(get_socket_executor(*socket), std::chrono::seconds(seconds)));
354  std::weak_ptr<Connection> self_weak(this->shared_from_this()); // To avoid keeping Connection instance alive longer than needed
355  timer->async_wait([self_weak](const error_code &ec) {
356  if(!ec) {
357  if(auto self = self_weak.lock())
358  self->close();
359  }
360  });
361  }
362 
363  void cancel_timeout() noexcept
364  {
365  if(timer) {
366  try {
367  timer->cancel();
368  }
369  catch(...) {
370  }
371  }
372  }
373  };
374 
375  class Session {
376  public:
377  Session(std::size_t max_request_streambuf_size, std::shared_ptr<Connection> connection_) noexcept : connection(std::move(connection_)), request(new Request(max_request_streambuf_size, connection)) {}
378 
379  std::shared_ptr<Connection> connection;
380  std::shared_ptr<Request> request;
381  };
382 
383 public:
384  class Config {
385  friend class HttpServerBase<socket_type>;
386 
387  Config(unsigned short port) noexcept : port(port) {}
388 
389  public:
391  unsigned short port;
394  long timeout_request = 5;
396  long timeout_content = 300;
399  std::size_t max_request_streambuf_size = std::numeric_limits<std::size_t>::max();
402  std::string address;
404  bool reuse_address = true;
406  bool fast_open = false;
407  };
409  Config config;
410 
411 private:
412  class regex_orderable : public std::regex {
413  public:
414  std::string str;
415 
416  regex_orderable(const char *regex_cstr) : std::regex(regex_cstr), str(regex_cstr) {}
417  regex_orderable(std::string regex_str_) : std::regex(regex_str_), str(std::move(regex_str_)) {}
418  bool operator<(const regex_orderable &rhs) const noexcept
419  {
420  return str < rhs.str;
421  }
422  };
423 
424 public:
427  std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename HttpServerBase<socket_type>::Response>, std::shared_ptr<typename HttpServerBase<socket_type>::Request>)>>> resource;
428 
430  std::map<std::string, std::function<void(std::shared_ptr<typename HttpServerBase<socket_type>::Response>, std::shared_ptr<typename HttpServerBase<socket_type>::Request>)>> default_resource;
431 
433  std::function<void(std::shared_ptr<typename HttpServerBase<socket_type>::Request>, const error_code &)> on_error;
434 
436  std::function<void(std::unique_ptr<socket_type> &, std::shared_ptr<typename HttpServerBase<socket_type>::Request>)> on_upgrade;
437 
439  std::shared_ptr<::identt::http::io_whatever> io_whatever;
440 
444  unsigned short bind()
445  {
446  std::lock_guard<std::mutex> lock(start_stop_mutex);
447 
448  asio::ip::tcp::endpoint endpoint;
449  if(config.address.size() > 0)
450  endpoint = asio::ip::tcp::endpoint(make_address(config.address), config.port);
451  else
452  endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v6(), config.port);
453 
454  if(!acceptor)
455  acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_whatever));
456  acceptor->open(endpoint.protocol());
457  acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
458  if(config.fast_open) {
459 #if defined(__linux__) && defined(TCP_FASTOPEN)
460  const int qlen = 5; // This seems to be the value that is used in other examples.
461  error_code ec;
462  acceptor->set_option(asio::detail::socket_option::integer<IPPROTO_TCP, TCP_FASTOPEN>(qlen), ec);
463 #endif // End Linux
464  }
465  acceptor->bind(endpoint);
466 
467  after_bind();
468 
469  return acceptor->local_endpoint().port();
470  }
471 
475  {
476  acceptor->listen();
477  accept();
478  }
479 
481  void start()
482  {
483  bind();
484  accept_and_run();
485  }
486 
488  void stop() noexcept
489  {
490  std::lock_guard<std::mutex> lock(start_stop_mutex);
491 
492  if(acceptor) {
493  error_code ec;
494  acceptor->close(ec);
495 
496  {
497  LockGuard lock(connections->mutex);
498  for(auto &connection : connections->set)
499  connection->close();
500  connections->set.clear();
501  }
502 
503  }
504  }
505 
506  virtual ~HttpServerBase() noexcept
507  {
508  handler_runner->stop();
509  stop();
510  }
511 
512 protected:
513  std::mutex start_stop_mutex;
514 
515  std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
516 
517  struct Connections {
518  Mutex mutex;
519  std::unordered_set<Connection *> set GUARDED_BY(mutex);
520  };
521  std::shared_ptr<Connections> connections;
522 
523  std::shared_ptr<ScopeRunner> handler_runner;
524 
525  HttpServerBase(unsigned short port) noexcept : config(port), connections(new Connections()), handler_runner(new ScopeRunner()) {}
526 
527  virtual void after_bind() {}
528  virtual void accept() = 0;
529 
530  template <typename... Args>
531  std::shared_ptr<Connection> create_connection(Args &&... args) noexcept
532  {
533  auto connections = this->connections;
534  auto connection = std::shared_ptr<Connection>(new Connection(handler_runner, std::forward<Args>(args)...), [connections](Connection *connection) {
535  {
536  LockGuard lock(connections->mutex);
537  auto it = connections->set.find(connection);
538  if(it != connections->set.end())
539  connections->set.erase(it);
540  }
541  delete connection;
542  });
543  {
544  LockGuard lock(connections->mutex);
545  connections->set.emplace(connection.get());
546  }
547  return connection;
548  }
549 
550  void read(const std::shared_ptr<Session> &session)
551  {
552  session->connection->set_timeout(config.timeout_request);
553  asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) {
554  session->connection->cancel_timeout();
555  auto lock = session->connection->handler_runner->continue_lock();
556  if(!lock)
557  return;
558  session->request->header_read_time = std::chrono::system_clock::now();
559  if(session->request->streambuf.size() == session->request->streambuf.max_size()) {
560  auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
561  response->write(StatusCode::client_error_payload_too_large);
562  if(this->on_error)
563  this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
564  return;
565  }
566 
567  if(!ec) {
568  // request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
569  // "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
570  // The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
571  // streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
572  std::size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred;
573 
574  if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path,
575  session->request->query_string, session->request->http_version, session->request->header)) {
576  if(this->on_error)
577  this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
578  return;
579  }
580 
581  // If content, read that as well
582  auto header_it = session->request->header.find("Content-Length");
583  if(header_it != session->request->header.end()) {
584  unsigned long long content_length = 0;
585  try {
586  content_length = stoull(header_it->second);
587  }
588  catch(const std::exception &) {
589  if(this->on_error)
590  this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
591  return;
592  }
593  if(content_length > num_additional_bytes) {
594  session->connection->set_timeout(config.timeout_content);
595  asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) {
596  session->connection->cancel_timeout();
597  auto lock = session->connection->handler_runner->continue_lock();
598  if(!lock)
599  return;
600  if(session->request->streambuf.size() == session->request->streambuf.max_size()) {
601  auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
602  response->write(StatusCode::client_error_payload_too_large);
603  if(this->on_error)
604  this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
605  return;
606  }
607 
608  if(!ec)
609  this->find_resource(session);
610  else if(this->on_error)
611  this->on_error(session->request, ec);
612  });
613  }
614  else
615  this->find_resource(session);
616  }
617  else if((header_it = session->request->header.find("Transfer-Encoding")) != session->request->header.end() && header_it->second == "chunked") {
618  auto chunks_streambuf = std::make_shared<asio::streambuf>(this->config.max_request_streambuf_size);
619 
620  // Copy leftover bytes
621  std::ostream ostream(chunks_streambuf.get());
622  auto size = session->request->streambuf.size();
623  std::unique_ptr<char[]> buffer(new char[size]);
624  session->request->content.read(buffer.get(), static_cast<std::streamsize>(size));
625  ostream.write(buffer.get(), static_cast<std::streamsize>(size));
626 
627  this->read_chunked_transfer_encoded(session, chunks_streambuf);
628  }
629  else
630  this->find_resource(session);
631  }
632  else if(this->on_error)
633  this->on_error(session->request, ec);
634  });
635  }
636 
637  void read_chunked_transfer_encoded(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &chunks_streambuf)
638  {
639  session->connection->set_timeout(config.timeout_content);
640  asio::async_read_until(*session->connection->socket, *chunks_streambuf, "\r\n", [this, session, chunks_streambuf](const error_code &ec, size_t bytes_transferred) {
641  session->connection->cancel_timeout();
642  auto lock = session->connection->handler_runner->continue_lock();
643  if(!lock)
644  return;
645  if(chunks_streambuf->size() == chunks_streambuf->max_size()) {
646  auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
647  response->write(StatusCode::client_error_payload_too_large);
648  if(this->on_error)
649  this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
650  return;
651  }
652 
653  if(!ec) {
654  std::istream istream(chunks_streambuf.get());
655  std::string line;
656  getline(istream, line);
657  bytes_transferred -= line.size() + 1;
658  line.pop_back();
659  unsigned long length = 0;
660  try {
661  length = stoul(line, 0, 16);
662  }
663  catch(...) {
664  if(this->on_error)
665  this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
666  return;
667  }
668 
669  auto num_additional_bytes = chunks_streambuf->size() - bytes_transferred;
670 
671  if((2 + length) > num_additional_bytes) {
672  session->connection->set_timeout(config.timeout_content);
673  asio::async_read(*session->connection->socket, *chunks_streambuf, asio::transfer_exactly(2 + length - num_additional_bytes), [this, session, chunks_streambuf, length](const error_code &ec, size_t /*bytes_transferred*/) {
674  session->connection->cancel_timeout();
675  auto lock = session->connection->handler_runner->continue_lock();
676  if(!lock)
677  return;
678  if(chunks_streambuf->size() == chunks_streambuf->max_size()) {
679  auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
680  response->write(StatusCode::client_error_payload_too_large);
681  if(this->on_error)
682  this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
683  return;
684  }
685 
686  if(!ec)
687  this->read_chunked_transfer_encoded_chunk(session, chunks_streambuf, length);
688  else if(this->on_error)
689  this->on_error(session->request, ec);
690  });
691  }
692  else
693  this->read_chunked_transfer_encoded_chunk(session, chunks_streambuf, length);
694  }
695  else if(this->on_error)
696  this->on_error(session->request, ec);
697  });
698  }
699 
700  void read_chunked_transfer_encoded_chunk(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &chunks_streambuf, unsigned long length)
701  {
702  std::istream istream(chunks_streambuf.get());
703  if(length > 0) {
704  std::ostream ostream(&session->request->streambuf);
705  std::unique_ptr<char[]> buffer(new char[length]);
706  istream.read(buffer.get(), static_cast<std::streamsize>(length));
707  ostream.write(buffer.get(), static_cast<std::streamsize>(length));
708  if(session->request->streambuf.size() == session->request->streambuf.max_size()) {
709  auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
710  response->write(StatusCode::client_error_payload_too_large);
711  if(this->on_error)
712  this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
713  return;
714  }
715  }
716 
717  // Remove "\r\n"
718  istream.get();
719  istream.get();
720 
721  if(length > 0)
722  read_chunked_transfer_encoded(session, chunks_streambuf);
723  else
724  this->find_resource(session);
725  }
726 
727  void find_resource(const std::shared_ptr<Session> &session)
728  {
729  // Upgrade connection
730  if(on_upgrade) {
731  auto it = session->request->header.find("Upgrade");
732  if(it != session->request->header.end()) {
733  // remove connection from connections
734  {
735  LockGuard lock(connections->mutex);
736  auto it = connections->set.find(session->connection.get());
737  if(it != connections->set.end())
738  connections->set.erase(it);
739  }
740 
741  on_upgrade(session->connection->socket, session->request);
742  return;
743  }
744  }
745  // Find path- and method-match, and call write
746  for(auto &regex_method : resource) {
747  auto it = regex_method.second.find(session->request->method);
748  if(it != regex_method.second.end()) {
749  std::smatch sm_res;
750  if(std::regex_match(session->request->path, sm_res, regex_method.first)) {
751  session->request->path_match = std::move(sm_res);
752  write(session, it->second);
753  return;
754  }
755  }
756  }
757  auto it = default_resource.find(session->request->method);
758  if(it != default_resource.end())
759  write(session, it->second);
760  }
761 
762  void write(const std::shared_ptr<Session> &session,
763  std::function<void(std::shared_ptr<typename HttpServerBase<socket_type>::Response>, std::shared_ptr<typename HttpServerBase<socket_type>::Request>)> &resource_function)
764  {
765  session->connection->set_timeout(config.timeout_content);
766  auto response = std::shared_ptr<Response>(new Response(session, config.timeout_content), [this](Response *response_ptr) {
767  auto response = std::shared_ptr<Response>(response_ptr);
768  response->send_on_delete([this, response](const error_code &ec) {
769  if(!ec) {
770  if(response->close_connection_after_response)
771  return;
772 
773  auto range = response->session->request->header.equal_range("Connection");
774  for(auto it = range.first; it != range.second; it++) {
775  if(case_insensitive_equal(it->second, "close"))
776  return;
777  else if(case_insensitive_equal(it->second, "keep-alive")) {
778  auto new_session = std::make_shared<Session>(this->config.max_request_streambuf_size, response->session->connection);
779  this->read(new_session);
780  return;
781  }
782  }
783  if(response->session->request->http_version >= "1.1") {
784  auto new_session = std::make_shared<Session>(this->config.max_request_streambuf_size, response->session->connection);
785  this->read(new_session);
786  return;
787  }
788  }
789  else if(this->on_error)
790  this->on_error(response->session->request, ec);
791  });
792  });
793 
794  try {
795  resource_function(response, session->request);
796  }
797  catch(const std::exception &) {
798  if(on_error)
799  on_error(session->request, make_error_code::make_error_code(errc::operation_canceled));
800  return;
801  }
802  }
803 };
804 
805 } // namespace http
806 } // namespace identt
807 
808 #endif // _IDENTT_HTTP_SERVER_BASE_HPP_
809 
long timeout_content
Timeout on content handling. Defaults to 300 seconds.
Definition: HttpServerBase.hpp:396
Definition: HttpServerBase.hpp:53
void stop() noexcept
Stop accepting new requests, and close current connections.
Definition: HttpServerBase.hpp:488
void write(StatusCode status_code, string_view content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap())
Convenience function for writing status line, header fields, and content.
Definition: HttpServerBase.hpp:184
std::shared_ptr<::identt::http::io_whatever > io_whatever
If you want to reuse an already created asio::io_service, store its pointer here before calling start...
Definition: HttpServerBase.hpp:439
Scoped mutex guard class that is annotated for Clang Thread Safety Analysis.
Definition: Mutex.hpp:121
void write(const CaseInsensitiveMultimap &header)
Convenience function for writing success status line, and header fields.
Definition: HttpServerBase.hpp:217
Definition: HttpServerBase.hpp:384
bool close_connection_after_response
If set to true, force server to close the connection after the response have been sent...
Definition: HttpServerBase.hpp:226
void write(const char_type *ptr, std::streamsize n)
Write directly to stream buffer using std::ostream::write.
Definition: HttpServerBase.hpp:171
Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
Definition: Utility.hpp:535
std::function< void(std::shared_ptr< typename HttpServerBase< socket_type >::Request >, const error_code &)> on_error
Called when an error occurs.
Definition: HttpServerBase.hpp:433
std::size_t max_request_streambuf_size
Maximum size of request stream buffer.
Definition: HttpServerBase.hpp:399
identt
Definition: HttpServerBase.hpp:375
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept
Returns query keys with percent-decoded values.
Definition: Utility.hpp:168
std::string string() noexcept
Convenience function to return content as std::string. The stream buffer is consumed.
Definition: HttpServerBase.hpp:238
Response class where the content of the response is sent to client when the object is about to be des...
Definition: HttpServerBase.hpp:60
Definition: HttpServerBase.hpp:257
void accept_and_run()
If you know the server port in advance, use start() instead.
Definition: HttpServerBase.hpp:474
std::chrono::system_clock::time_point header_read_time
The time point when the request header was fully read.
Definition: HttpServerBase.hpp:279
unsigned short bind()
If you know the server port in advance, use start() instead.
Definition: HttpServerBase.hpp:444
Definition: HttpServerBase.hpp:328
Definition: CryptoBase.hpp:49
Config config
Set before calling start().
Definition: HttpServerBase.hpp:409
std::string address
IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
Definition: HttpServerBase.hpp:402
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap())
Convenience function for writing status line, header fields, and content.
Definition: HttpServerBase.hpp:193
Definition: HttpServerBase.hpp:517
std::function< void(std::unique_ptr< socket_type > &, std::shared_ptr< typename HttpServerBase< socket_type >::Request >)> on_upgrade
Called on upgrade requests.
Definition: HttpServerBase.hpp:436
void send(const std::function< void(const error_code &)> &callback=nullptr) noexcept
Send the content of the response stream to client.
Definition: HttpServerBase.hpp:156
std::map< std::string, std::function< void(std::shared_ptr< typename HttpServerBase< socket_type >::Response >, std::shared_ptr< typename HttpServerBase< socket_type >::Request >)> > default_resource
If the request path does not match a resource regex, this function is called.
Definition: HttpServerBase.hpp:430
void write(std::istream &content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap())
Convenience function for writing success status line, header fields, and content. ...
Definition: HttpServerBase.hpp:211
void start()
Start the server by calling bind() and accept_and_run()
Definition: HttpServerBase.hpp:481
Definition: HttpServerBase.hpp:229
std::smatch path_match
The result of the resource regular expression match of the request path.
Definition: HttpServerBase.hpp:276
Definition: HttpServer.hpp:42
unsigned short port
Port number to use. Defaults to 80 for HTTP and 443 for HTTPS. Set to 0 get an assigned port...
Definition: HttpServerBase.hpp:391
std::map< regex_orderable, std::map< std::string, std::function< void(std::shared_ptr< typename HttpServerBase< socket_type >::Response >, std::shared_ptr< typename HttpServerBase< socket_type >::Request >)> > > resource
Use this container to add resources for specific request paths depending on the given regex and metho...
Definition: HttpServerBase.hpp:427
void write(string_view content, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap())
Convenience function for writing success status line, header fields, and content. ...
Definition: HttpServerBase.hpp:205
void write(StatusCode status_code=StatusCode::success_ok, const CaseInsensitiveMultimap &header=CaseInsensitiveMultimap())
Convenience function for writing status line, potential header fields, and empty content.
Definition: HttpServerBase.hpp:177
CaseInsensitiveMultimap parse_query_string() const noexcept
Returns query keys with percent-decoded values.
Definition: HttpServerBase.hpp:317