RESTinio
websocket.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  WebSocket messgage handler definition.
7 */
8 
9 #pragma once
10 
11 #include <functional>
12 
13 #include <restinio/websocket/message.hpp>
14 #include <restinio/websocket/impl/ws_connection_base.hpp>
15 #include <restinio/websocket/impl/ws_connection.hpp>
16 #include <restinio/utils/base64.hpp>
17 #include <restinio/utils/sha1.hpp>
18 
19 namespace restinio
20 {
21 
22 namespace websocket
23 {
24 
25 namespace basic
26 {
27 
28 //
29 // ws_t
30 //
31 
32 //! A WebSocket bind.
33 /*!
34  An abstraction for websocket. User have to keep this handle during all the period
35  that websocket is used. It must be stored in a `shared_ptr<ws_t>` (ws_handle_t)
36  and when the last reference on this handle is lost underlying connection will be closed.
37 */
38 class ws_t
39  : public std::enable_shared_from_this< ws_t >
40 {
41  public:
42  //
43  // activate()
44  //
45 
46  //! Activate websocket: start receiving messages.
47  friend void activate( ws_t & ws )
48  {
49  ws.m_ws_connection_handle->init_read( ws.shared_from_this() );
50  }
51 
52  ws_t( const ws_t & ) = delete;
53  ws_t( ws_t && ) = delete;
54  ws_t & operator = ( const ws_t & ) = delete;
55  ws_t & operator = ( ws_t && ) = delete;
56 
58  impl::ws_connection_handle_t ws_connection_handle,
59  endpoint_t remote_endpoint )
62  {}
63 
64  ~ws_t()
65  {
66  try
67  {
68  shutdown();
69  }
70  catch( ... )
71  {}
72  }
73 
74  //! Get connection id.
75  /*!
76  If connection exists then its id is returned,
77  otherwise retursn zero.
78  */
80  connection_id() const
81  {
82  return m_ws_connection_handle ? m_ws_connection_handle->connection_id() : 0;
83  }
84 
85  //! Shutdown websocket: wait for all outgoing data to be sent,
86  //! and close connection.
87  void
89  {
90  if( m_ws_connection_handle )
91  {
92  auto con = std::move( m_ws_connection_handle );
93  con->shutdown();
94  }
95  }
96 
97  //! Kill websocket: close underlying tcp socket.
98  //! Do not tolerate unsent outgoing data.
99  void
101  {
102  if( m_ws_connection_handle )
103  {
104  auto con = std::move( m_ws_connection_handle );
105  con->kill();
106  }
107  }
108 
109  //! Send_websocket message.
110  void
112  final_frame_flag_t final_flag,
113  opcode_t opcode,
114  writable_item_t payload,
115  write_status_cb_t wscb = write_status_cb_t{} )
116  {
117  if( m_ws_connection_handle )
118  {
119  if( restinio::writable_item_type_t::trivial_write_operation ==
120  payload.write_type() )
121  {
122  writable_items_container_t bufs;
123  bufs.reserve( 2 );
124 
125  // Create header serialize it and append to bufs .
126  impl::message_details_t details{
127  final_flag, opcode, asio_ns::buffer_size( payload.buf() ) };
128 
129  bufs.emplace_back(
130  impl::write_message_details( details ) );
131 
132  bufs.emplace_back( std::move( payload ) );
133 
134  write_group_t wg{ std::move( bufs ) };
135 
136  if( wscb )
137  {
138  wg.after_write_notificator( std::move( wscb ) );
139  }
140 
141  // TODO: set flag.
142  const bool is_close_frame =
143  opcode_t::connection_close_frame == opcode;
144 
145  if( is_close_frame )
146  {
147  auto con = std::move( m_ws_connection_handle );
148  con->write_data(
149  std::move( wg ),
150  is_close_frame );
151  }
152  else
153  {
154  m_ws_connection_handle->write_data(
155  std::move( wg ),
156  is_close_frame );
157  }
158  }
159  else
160  {
161  throw exception_t{ "ws doesn't support sendfile" };
162  }
163  }
164  else
165  {
166  throw exception_t{ "websocket is not available" };
167  }
168  }
169 
170  void
171  send_message( message_t msg, write_status_cb_t wscb = write_status_cb_t{} )
172  {
173  send_message(
174  msg.final_flag(),
175  msg.opcode(),
176  writable_item_t{ std::move( msg.payload() ) },
177  std::move( wscb ) );
178  }
179 
180  //! Get the remote endpoint of the underlying connection.
181  const endpoint_t & remote_endpoint() const noexcept { return m_remote_endpoint; }
182 
183  private:
185 
186  //! Remote endpoint for this ws-connection.
188 };
189 
190 //! Alias for ws_t handle.
191 using ws_handle_t = std::shared_ptr< ws_t >;
192 
193 //
194 // activation_t
195 //
196 
197 //! Flags for websocket activation policies.
198 enum class activation_t
199 {
200  //! Activate immediately after upgrade operation.
201  immediate,
202  //! User will initiate activation later.
203  delayed
204 };
205 
206 //
207 // upgrade()
208 //
209 
210 //! Upgrade http-connection of a current request to a websocket connection.
211 template <
212  typename Traits,
213  typename WS_Message_Handler >
216  //! Upgrade request.
217  request_t & req,
218  //! Activation policy.
220  //! Response header fields.
222  //! Message handler.
224 {
225  // TODO: check if upgrade request?
226 
227  //! Check if mandatory field is available.
229  {
230  throw exception_t{
231  fmt::format( "{} field is mandatory for upgrade response",
233  }
234 
236  {
238  }
239 
242  if( !conn_ptr )
243  {
244  throw exception_t{ "no connection for upgrade: already moved" };
245  }
246  auto & con = dynamic_cast< connection_t & >( *conn_ptr );
247 
249 
251  auto ws_connection =
253  con.connection_id(),
257 
259  {
263 
264  const auto content_length_flag =
266 
271  }
272 
275  false );
276 
277  auto result =
279 
281  {
282  activate( *result );
283  }
284 
285  // Returns strong handle on websocket, thus giving an ownership.
286  return result;
287 }
288 
289 template <
290  typename Traits,
291  typename WS_Message_Handler >
292 auto
294  request_t & req,
295  activation_t activation_flag,
296  std::string sec_websocket_accept_field_value,
297  WS_Message_Handler ws_message_handler )
298 {
299  http_header_fields_t upgrade_response_header_fields;
300  upgrade_response_header_fields.set_field(
301  http_field::sec_websocket_accept,
302  std::move( sec_websocket_accept_field_value ) );
303 
304  return
305  upgrade< Traits, WS_Message_Handler >(
306  req,
307  activation_flag,
308  std::move( upgrade_response_header_fields ),
309  std::move( ws_message_handler ) );
310 }
311 
312 template <
313  typename Traits,
314  typename WS_Message_Handler >
315 auto
317  request_t & req,
318  activation_t activation_flag,
319  std::string sec_websocket_accept_field_value,
320  std::string sec_websocket_protocol_field_value,
321  WS_Message_Handler ws_message_handler )
322 {
323  http_header_fields_t upgrade_response_header_fields;
324  upgrade_response_header_fields.set_field(
325  http_field::sec_websocket_accept,
326  std::move( sec_websocket_accept_field_value ) );
327 
328  upgrade_response_header_fields.set_field(
329  http_field::sec_websocket_protocol,
330  std::move( sec_websocket_protocol_field_value ) );
331 
332  return
333  upgrade< Traits, WS_Message_Handler >(
334  req,
335  activation_flag,
336  std::move( upgrade_response_header_fields ),
337  std::move( ws_message_handler ) );
338 }
339 
340 template <
341  typename Traits,
342  typename WS_Message_Handler >
343 auto
345  request_t & req,
346  activation_t activation_flag,
347  WS_Message_Handler ws_message_handler )
348 {
349  const char * websocket_accept_field_suffix = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
350  const auto ws_key =
351  req.header().get_field( restinio::http_field::sec_websocket_key ) +
352  websocket_accept_field_suffix;
353 
354  auto digest = restinio::utils::sha1::make_digest( ws_key );
355 
356  std::string sec_websocket_accept_field_value = utils::base64::encode(
357  utils::sha1::to_string( digest ) );
358 
359  http_header_fields_t upgrade_response_header_fields;
360  upgrade_response_header_fields.set_field(
361  http_field::sec_websocket_accept,
362  std::move( sec_websocket_accept_field_value ) );
363 
364  return
365  upgrade< Traits, WS_Message_Handler >(
366  req,
367  activation_flag,
368  std::move( upgrade_response_header_fields ),
369  std::move( ws_message_handler ) );
370 }
371 
372 } /* namespace basic */
373 
374 } /* namespace websocket */
375 
376 } /* namespace restinio */
auto upgrade(request_t &req, activation_t activation_flag, std::string sec_websocket_accept_field_value, WS_Message_Handler ws_message_handler)
Definition: websocket.hpp:293
void shutdown()
Shutdown websocket: wait for all outgoing data to be sent, and close connection.
Definition: websocket.hpp:88
ws_t & operator=(const ws_t &)=delete
const endpoint_t & remote_endpoint() const noexcept
Get the remote endpoint of the underlying connection.
Definition: websocket.hpp:181
void send_message(message_t msg, write_status_cb_t wscb=write_status_cb_t{})
Definition: websocket.hpp:171
const endpoint_t m_remote_endpoint
Remote endpoint for this ws-connection.
Definition: websocket.hpp:187
void send_message(final_frame_flag_t final_flag, opcode_t opcode, writable_item_t payload, write_status_cb_t wscb=write_status_cb_t{})
Send_websocket message.
Definition: websocket.hpp:111
User will initiate activation later.
connection_id_t connection_id() const
Get connection id.
Definition: websocket.hpp:80
impl::ws_connection_handle_t m_ws_connection_handle
Definition: websocket.hpp:184
activation_t
Flags for websocket activation policies.
Definition: websocket.hpp:198
void kill()
Kill websocket: close underlying tcp socket. Do not tolerate unsent outgoing data.
Definition: websocket.hpp:100
ws_t & operator=(ws_t &&)=delete
friend void activate(ws_t &ws)
Activate websocket: start receiving messages.
Definition: websocket.hpp:47
Activate immediately after upgrade operation.
auto upgrade(request_t &req, activation_t activation_flag, std::string sec_websocket_accept_field_value, std::string sec_websocket_protocol_field_value, WS_Message_Handler ws_message_handler)
Definition: websocket.hpp:316
ws_handle_t upgrade(request_t &req, activation_t activation_flag, http_header_fields_t upgrade_response_header_fields, WS_Message_Handler ws_message_handler)
Upgrade http-connection of a current request to a websocket connection.
Definition: websocket.hpp:215
auto upgrade(request_t &req, activation_t activation_flag, WS_Message_Handler ws_message_handler)
Definition: websocket.hpp:344
ws_t(impl::ws_connection_handle_t ws_connection_handle, endpoint_t remote_endpoint)
Definition: websocket.hpp:57
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64