staticlib_pion  2.0.2
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
websocket.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2018, alex at staticlibs.net
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * File: websocket.hpp
19  * Author: alex
20  *
21  * Created on June 16, 2018, 5:57 PM
22  */
23 
24 #ifndef STATICLIB_PION_WEBSOCKET_HPP
25 #define STATICLIB_PION_WEBSOCKET_HPP
26 
27 #include <cstring>
28 #include <memory>
29 #include <string>
30 #include <unordered_map>
31 #include <vector>
32 
33 #include "asio.hpp"
34 
35 #include "staticlib/support.hpp"
36 #include "staticlib/utils.hpp"
37 #include "staticlib/websocket.hpp"
38 
42 
43 namespace staticlib {
44 namespace pion {
45 
46 namespace websocket_detail {
47 
52 class msg_data_src {
56  std::vector<sl::websocket::frame>& frames_ref;
60  sl::websocket::masked_payload_source src_single;
64  sl::io::multi_source<std::vector<sl::websocket::masked_payload_source>> src_multi;
65 
66 public:
72  msg_data_src(std::vector<sl::websocket::frame>& frames):
73  frames_ref(frames),
74  src_single(
75  [&frames] {
76  if (1 == frames.size()) {
77  return frames[0].payload_unmasked();
78  } else {
79  return sl::websocket::masked_payload_source({nullptr, 0}, 0);
80  }
81  } ()),
83  [&frames] {
84  auto vec = std::vector<sl::websocket::masked_payload_source>();
85  if (frames.size() > 1) {
86  for (auto& fr : frames) {
87  vec.emplace_back(fr.payload_unmasked());
88  }
89  }
90  return sl::io::make_multi_source(std::move(vec));
91  } ()) { }
92 
96  msg_data_src(const msg_data_src&) = delete;
97 
101  msg_data_src& operator=(const msg_data_src&) = delete;
102 
109  frames_ref(other.frames_ref),
110  src_single(std::move(other.src_single)),
111  src_multi(std::move(other.src_multi)) { }
112 
116  msg_data_src& operator=(msg_data_src&&) = delete;
117 
125  std::streamsize read(sl::io::span<char> span) {
126  if (1 == frames_ref.size()) {
127  return src_single.read(span);
128  } else if (frames_ref.size() > 1) {
129  return src_multi.read(span);
130  } else {
131  return std::char_traits<char>::eof();
132  }
133  }
134 };
135 
136 } // namespace
137 
141 class websocket {
142 
146  enum class close_status : uint32_t {
147  normal = 0xe8030288,
148  going_away = 0xe9030288,
149  protocol_error = 0xea030288,
150  overflow = 0xf1030288,
151  error = 0xf3030288
152  };
153 
154  // IO
155  std::unique_ptr<http_request> request;
156  std::shared_ptr<tcp_connection> connection;
157 
158  // handlers
159  std::function<void(std::unique_ptr<websocket>)> on_open_fun;
160  std::function<void(std::unique_ptr<websocket>)> on_message_fun;
161  std::function<void(std::unique_ptr<websocket>)> on_close_fun;
162 
163  // overflow limits
164  const size_t receive_buffer_max_size;
165  const size_t frames_max_count;
166 
167  // output state
168  std::vector<asio::const_buffer> payload_buffers;
169  std::vector<std::unique_ptr<char[]>> payload_cache;
170  size_t payload_length = 0;
171 
172  // input state
173  std::vector<char> receive_buffer;
174  std::vector<std::unique_ptr<char[]>> frames_cache;
175  std::vector<sl::websocket::frame> frames;
176 
177 public:
189  websocket(std::unique_ptr<http_request> req, std::shared_ptr<tcp_connection> conn,
190  std::function<void(std::unique_ptr<websocket>)> open_handler,
191  std::function<void(std::unique_ptr<websocket>)> message_handler,
192  std::function<void(std::unique_ptr<websocket>)> close_handler,
193  size_t max_receive_cache_size_bytes = (1024*1024),
194  size_t max_cached_frames_count = 1024) :
195  request(std::move(req)),
196  connection(std::move(conn)),
197  on_open_fun(std::move(open_handler)),
198  on_message_fun(std::move(message_handler)),
199  on_close_fun(std::move(close_handler)),
200  receive_buffer_max_size(max_receive_cache_size_bytes),
201  frames_max_count(max_cached_frames_count) {
202  connection->set_lifecycle(tcp_connection::lifecycle::close);
203  }
204 
208  ~websocket() STATICLIB_NOEXCEPT {
209  try {
210  connection->finish();
211  } catch(...) {
212  // no-op
213  }
214  }
215 
219  websocket(const websocket&) = delete;
220 
224  websocket& operator=(const websocket&) = delete;
225 
231  const std::string& get_id() {
232  return request->get_header("Sec-WebSocket-Key");
233  }
234 
241  return *request;
242  }
243 
250  return *connection;
251  }
252 
259  sl::websocket::frame_type message_type() {
260  if(frames.size() > 0) {
261  return frames[0].type();
262  }
263  return sl::websocket::frame_type::invalid;
264  }
265 
275  return websocket_detail::msg_data_src(frames);
276  }
277 
283  void write(sl::io::span<const char> data) {
284  if (data.size() > 0) {
285  payload_buffers.push_back(add_to_payload_cache(data));
286  payload_length += data.size();
287  }
288  }
289 
297  void write_nocopy(sl::io::span<const char> data) {
298  if (data.size() > 0) {
299  payload_buffers.push_back(asio::buffer(data.data(), data.size()));
300  payload_length += data.size();
301  }
302  }
303 
307  static bool is_websocket_upgrade(const http_request& req) {
308  return req.has_header_value("Upgrade", "websocket") &&
309  req.has_header_value("Sec-WebSocket-Version", "13") &&
310  req.has_header_value("Connection", "Upgrade") &&
311  !req.get_header("Host").empty() &&
312  24 == req.get_header("Sec-WebSocket-Key").length();
313  }
314 
321  static void start(std::unique_ptr<websocket> self) {
322  self->prepare_handshake();
323  send_internal(std::move(self),
324  [](std::unique_ptr<websocket> self) {
325  on_open(std::move(self));
326  });
327  }
328 
334  static void receive(std::unique_ptr<websocket> self) {
335  self->clear_frames_cache();
336  process_receive_buffer(std::move(self));
337  }
338 
346  static void send(std::unique_ptr<websocket> self,
347  sl::websocket::frame_type msg_type = sl::websocket::frame_type::text) {
348  send_message(std::move(self), msg_type, websocket::receive);
349  }
350 
359  template<typename Handler>
360  static void send_message(std::unique_ptr<websocket> self, sl::websocket::frame_type msg_type,
361  Handler handler) {
362  self->prepare_header(msg_type);
363  // safety precaution for delayed responses
364  post_with_strand(std::move(self),
365  [handler](std::unique_ptr<websocket> self) {
366  send_internal(std::move(self), std::move(handler));
367  });
368  }
369 
384  static void broadcast(std::vector<std::shared_ptr<tcp_connection>>& connections,
385  sl::io::span<const char> msg,
386  sl::websocket::frame_type msg_type = sl::websocket::frame_type::text) {
387  // prepare message
388  auto holder = std::make_shared<std::vector<char>>();
389  auto hbuf = std::array<char, 10>();
390  auto header = sl::websocket::frame::make_header(hbuf, msg_type, msg.size());
391  holder->resize(header.size() + msg.size());
392  std::memcpy(holder->data(), header.data(), header.size());
393  std::memcpy(holder->data() + header.size(), msg.data(), msg.size());
394 
395  // broadcast message
396  for (auto& el : connections) {
397  send_broadcast(std::move(el), holder);
398  }
399  }
400 
407  static void close(std::unique_ptr<websocket> self, const close_status& status = close_status::normal) {
408  on_close(std::move(self), status);
409  }
410 
411 private:
412 
419  asio::const_buffer add_to_payload_cache(sl::io::span<const char> buf) {
420  payload_cache.emplace_back(new char[buf.size()]);
421  char* dest = payload_cache.back().get();
422  std::memcpy(dest, buf.data(), buf.size());
423  return asio::buffer(dest, buf.size());
424  }
425 
426  void add_to_receive_buffer(sl::io::span<const char> buf) {
427  auto size = receive_buffer.size();
428  receive_buffer.resize(size + buf.size());
429  std::memcpy(receive_buffer.data() + size, buf.data(), buf.size());
430  }
431 
432  void remove_frame_from_receive_buffer(sl::websocket::frame& frame) {
433  auto receive_tail = receive_buffer.size() - frame.size();
434  if (0 == receive_tail) {
435  receive_buffer.clear();
436  } else {
437  std::memmove(receive_buffer.data(), receive_buffer.data() + frame.size(), receive_tail);
438  receive_buffer.resize(receive_tail);
439  }
440  }
441 
442  void add_to_frames_cache(sl::websocket::frame& frame) {
443  frames_cache.emplace_back(new char[frame.size()]);
444  char* dest = frames_cache.back().get();
445  std::memcpy(dest, frame.data(), frame.size());
446  frames.emplace_back(sl::websocket::frame({dest, frame.size()}));
447  remove_frame_from_receive_buffer(frame);
448  }
449 
450  void clear_payload() {
451  payload_buffers.clear();
452  payload_buffers.push_back(asio::buffer(sl::utils::empty_string().data(), 0));
453  payload_cache.clear();
454  payload_length = 0;
455  }
456 
457  void clear_frames_cache() {
458  // last received frame always lives in cache
459  if (frames.size() > 0) {
460  remove_frame_from_receive_buffer(frames.back());
461  }
462  frames_cache.clear();
463  frames.clear();
464  }
465 
466  void prepare_header(sl::websocket::frame_type msg_type) {
467  auto buf = std::array<char, 10>();
468  auto header = sl::websocket::frame::make_header(buf, msg_type, payload_length);
469  payload_cache.emplace_back(new char[header.size()]);
470  char* dest = payload_cache.back().get();
471  std::memcpy(dest, header.data(), header.size());
472  payload_buffers[0] = asio::buffer(dest, header.size());
473  }
474 
475  void prepare_handshake() {
476  write(sl::websocket::handshake::make_response_line());
477  auto& key = request->get_header("Sec-WebSocket-Key");
478  auto headers = sl::websocket::handshake::make_response_headers(key);
479  for (auto& ha : headers) {
480  write(ha.first);
481  write_nocopy(": ");
482  write(ha.second);
483  write_nocopy("\r\n");
484  }
485  write_nocopy("\r\n");
486  }
487 
488  bool receive_buffer_overflow(size_t addition) {
489  return receive_buffer.size() + addition > receive_buffer_max_size;
490  }
491 
492  bool frames_cache_overflow() {
493  return frames.size() >= frames_max_count;
494  }
495 
496  template<typename Handler>
497  static void post_with_strand(std::unique_ptr<websocket> self, Handler handler) {
498  auto self_ptr = self.get();
499  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
500  auto post_handler =
501  [self_shared, handler]() {
502  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
503  if (nullptr != self.get()) {
504  handler(std::move(self));
505  } else {
506  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket", "Lost context detected in 'post'");
507  }
508  };
509  self_ptr->connection->get_strand().post(std::move(post_handler));
510  }
511 
512  template<typename Handler>
513  static void send_internal(std::unique_ptr<websocket> self, Handler handler) {
514  auto self_ptr = self.get();
515  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
516  auto send_handler =
517  [self_shared, handler](const asio::error_code& ec, size_t) {
518  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
519  if (nullptr != self.get()) {
520  self->clear_payload();
521  if (!ec) {
522  handler(std::move(self));
523  } else {
524  STATICLIB_PION_LOG_DEBUG("staticlib.pion.websocket", "Write error," <<
525  " code: [" << ec << "]" <<
526  " message: [" << ec.message() << "]" <<
527  " id: [" << self->get_id() << "]" <<
528  " path: [" << self->request->get_resource() << "]");
529  on_close(std::move(self), close_status::error);
530  }
531  } else {
532  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket", "Lost context detected in 'async_write'");
533  }
534  };
535  auto send_handler_stranded = self_ptr->connection->get_strand().wrap(send_handler);
536  self_ptr->connection->async_write(self_ptr->payload_buffers, std::move(send_handler_stranded));
537  }
538 
539  static void send_close(std::unique_ptr<websocket> self, const close_status& status) {
540  auto self_ptr = self.get();
541  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
542  auto send_handler =
543  [self_shared](const asio::error_code&, size_t) {
544  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
545  if (nullptr != self.get()) {
546  auto self_ptr = self.get();
547  self_ptr->on_close_fun(std::move(self));
548  // self is destroyed on close fun exit
549  } else {
550  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket", "Lost context detected in 'async_write'");
551  }
552  };
553  auto send_handler_stranded = self_ptr->connection->get_strand().wrap(send_handler);
554  auto msg = asio::buffer(std::addressof(status), sizeof(status));
555  self_ptr->connection->async_write(std::move(msg), std::move(send_handler_stranded));
556  }
557 
558  static void receive_internal(std::unique_ptr<websocket> self) {
559  auto self_ptr = self.get();
560  auto self_shared = sl::support::make_shared_with_release_deleter(self.release());
561  auto read_handler =
562  [self_shared](const std::error_code& ec, size_t bytes_read) {
563  auto self = sl::support::make_unique_from_shared_with_release_deleter(self_shared);
564  if (nullptr != self.get()) {
565  if (!ec) {
566  const char* dptr = self->connection->get_read_buffer().data();
567  auto buf = sl::io::make_span(dptr, bytes_read);
568  consume(std::move(self), buf);
569  } else {
570  STATICLIB_PION_LOG_DEBUG("staticlib.pion.websocket", "Read error," <<
571  " code: [" << ec << "]" <<
572  " message: [" << ec.message() << "]" <<
573  " id: [" << self->get_id() << "]" <<
574  " path: [" << self->request->get_resource() << "]");
575  on_close(std::move(self), close_status::error);
576  }
577  } else {
578  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket", "Lost context detected in 'async_read_some'");
579  }
580  };
581  auto read_handler_stranded = self_ptr->connection->get_strand().wrap(std::move(read_handler));
582  self_ptr->connection->async_read_some(std::move(read_handler_stranded));
583  }
584 
585  static void send_broadcast(std::shared_ptr<tcp_connection> conn,
586  std::shared_ptr<std::vector<char>> data) {
587  auto handler =
588  [conn, data]() {
589  conn->async_write(asio::buffer(data->data(), data->size()),
590  [data](const std::error_code&, size_t){ /* no-op */ });
591  };
592  conn->get_strand().post(std::move(handler));
593  }
594 
595  static void consume(std::unique_ptr<websocket> self, sl::io::span<const char> buf) {
596  // append incoming data to receive buffer
597  if (self->receive_buffer_overflow(buf.size())) {
598  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket", "Receive buffer overflow" <<
599  " id: [" << self->get_id() << "]" <<
600  " path: [" << self->request->get_resource() << "]");
601  on_close(std::move(self), close_status::overflow);
602  return;
603  }
604  self->add_to_receive_buffer(buf);
605  process_receive_buffer(std::move(self));
606  }
607 
608  static void process_receive_buffer(std::unique_ptr<websocket> self) {
609  auto frame = sl::websocket::frame(self->receive_buffer_span());
610  // close connection for invalid frame
611  if (!frame.is_well_formed()) {
612  STATICLIB_PION_LOG_WARN("staticlib.pion.websocket",
613  "Invalid frame received, header: [" << frame.header_hex() << "]" <<
614  " id: [" << self->get_id() << "]" <<
615  " path: [" << self->request->get_resource() << "]");
616  on_close(std::move(self), close_status::protocol_error);
617  return;
618  }
619  // read more data for fragmented frame
620  if (!frame.is_complete()) {
621  receive_internal(std::move(self));
622  return;
623  }
624  // frame is not fragmented on TCP, but may be partial
625  if (!frame.is_final()) {
626  on_continuation(std::move(self), frame);
627  return;
628  }
629  // complete and final frame received
630  process_final_frame(std::move(self), frame);
631  }
632 
633  static void process_final_frame(std::unique_ptr<websocket> self, sl::websocket::frame frame) {
634  self->frames.push_back(frame);
635  // choose handler
636  switch (frame.type()) {
637  case sl::websocket::frame_type::text:
638  case sl::websocket::frame_type::binary:
639  case sl::websocket::frame_type::continuation:
640  on_message(std::move(self));
641  break;
642  case sl::websocket::frame_type::ping:
643  on_ping(std::move(self), frame);
644  break;
645  case sl::websocket::frame_type::close:
646  on_close(std::move(self), close_status::normal);
647  break;
648  default:
649  on_close(std::move(self), close_status::protocol_error);
650  break;
651  };
652  }
653 
654  static void on_open(std::unique_ptr<websocket> self) {
655  STATICLIB_PION_LOG_DEBUG("staticlib.pion.websocket", "WebSocket connection opened," <<
656  " id: [" << self->get_id() << "]" <<
657  " path: [" << self->request->get_resource() << "]");
658  auto self_ptr = self.get();
659  self_ptr->on_open_fun(std::move(self));
660  }
661 
662  static void on_message(std::unique_ptr<websocket> self) {
663  auto self_ptr = self.get();
664  self_ptr->on_message_fun(std::move(self));
665  }
666 
667  static void on_close(std::unique_ptr<websocket> self, const close_status& status) {
668  STATICLIB_PION_LOG_DEBUG("staticlib.pion.websocket", "Closing WebSocket connection," <<
669  " id: [" << self->get_id() << "]" <<
670  " path: [" << self->request->get_resource() << "]");
671  send_close(std::move(self), status);
672  }
673 
674  static void on_ping(std::unique_ptr<websocket> self, sl::websocket::frame frame) {
675  if (frame.payload_length() > 0) {
676  auto src = frame.payload_unmasked();
677  auto buf = std::array<char, (1<<7)>();
678  auto len = src.read({buf.data(), buf.size()});
679  self->write({buf.data(), len});
680  }
681  send_internal(std::move(self),
682  [](std::unique_ptr<websocket> self) {
683  receive(std::move(self));
684  });
685  }
686 
687  static void on_continuation(std::unique_ptr<websocket> self, sl::websocket::frame frame) {
688  if (self->frames_cache_overflow()) {
689  STATICLIB_PION_LOG_DEBUG("staticlib.pion.websocket", "Frame fragments overflow" <<
690  " id: [" << self->get_id() << "]" <<
691  " path: [" << self->request->get_resource() << "]");
692  on_close(std::move(self), close_status::overflow);
693  return;
694  }
695  self->add_to_frames_cache(frame);
696  // safety precaution for stack overflow
697  post_with_strand(std::move(self), websocket::process_receive_buffer);
698  }
699 
700  sl::io::span<const char> receive_buffer_span() {
701  return sl::io::make_span(
702  const_cast<const char*>(receive_buffer.data()),
703  receive_buffer.size());
704  }
705 };
706 
710 using websocket_ptr = std::unique_ptr<websocket>;
714 using websocket_handler_type = std::function<void(std::unique_ptr<websocket>)>;
715 
716 
717 } // namespace
718 }
719 
720 #endif /* STATICLIB_PION_WEBSOCKET_HPP */
721