RESTinio
Loading...
Searching...
No Matches
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
19namespace restinio
20{
21
22namespace websocket
23{
24
25namespace 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*/
38class 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
65 {
66 try
67 {
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 */
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 {
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(
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.
191using ws_handle_t = std::shared_ptr< ws_t >;
192
193//
194// activation_t
195//
196
197//! Flags for websocket activation policies.
198enum class activation_t
199{
200 //! Activate immediately after upgrade operation.
202 //! User will initiate activation later.
204};
205
206//
207// upgrade()
208//
209
210//! Upgrade http-connection of a current request to a websocket connection.
211template <
212 typename Traits,
213 typename WS_Message_Handler >
216 //! Upgrade request.
217 generic_request_type_from_traits_t<Traits> & req,
218 //! Activation policy.
219 activation_t activation_flag,
220 //! Response header fields.
221 http_header_fields_t upgrade_response_header_fields,
222 //! Message handler.
223 WS_Message_Handler ws_message_handler )
224{
225 // TODO: check if upgrade request?
226
227 //! Check if mandatory field is available.
228 if( !upgrade_response_header_fields.has_field( http_field::sec_websocket_accept ) )
229 {
230 throw exception_t{
231 fmt::format(
233 "{} field is mandatory for upgrade response" ),
234 field_to_string( http_field::sec_websocket_accept ) ) };
235 }
236
237 if( !upgrade_response_header_fields.has_field( http_field::upgrade ) )
238 {
239 upgrade_response_header_fields.set_field( http_field::upgrade, "websocket" );
240 }
241
242 using connection_t = restinio::impl::connection_t< Traits >;
243 auto conn_ptr = std::move( restinio::impl::access_req_connection( req ) );
244 if( !conn_ptr )
245 {
246 throw exception_t{ "no connection for upgrade: already moved" };
247 }
248 auto & con = dynamic_cast< connection_t & >( *conn_ptr );
249
250 using ws_connection_t = impl::ws_connection_t< Traits, WS_Message_Handler >;
251
252 auto upgrade_internals = con.move_upgrade_internals();
253 auto ws_connection =
254 std::make_shared< ws_connection_t >(
255 con.connection_id(),
256 std::move( upgrade_internals.m_settings ),
257 std::move( upgrade_internals.m_socket ),
258 std::move( upgrade_internals.m_lifetime_monitor ),
259 std::move( ws_message_handler ) );
260
261 writable_items_container_t upgrade_response_bufs;
262 {
263 http_response_header_t upgrade_response_header{ status_switching_protocols() };
264 upgrade_response_header.swap_fields( upgrade_response_header_fields );
265 upgrade_response_header.connection( http_connection_header_t::upgrade );
266
267 const auto content_length_flag =
269
270 upgrade_response_bufs.emplace_back(
271 restinio::impl::create_header_string(
272 upgrade_response_header,
273 content_length_flag ) );
274 }
275
276 ws_connection->write_data(
277 write_group_t{ std::move( upgrade_response_bufs ) },
278 false );
279
280 auto result =
281 std::make_shared< ws_t >( std::move( ws_connection ), req.remote_endpoint() );
282
283 if( activation_t::immediate == activation_flag )
284 {
285 activate( *result );
286 }
287
288 // Returns strong handle on websocket, thus giving an ownership.
289 return result;
290}
291
292template <
293 typename Traits,
294 typename WS_Message_Handler >
295auto
297 generic_request_type_from_traits_t<Traits> & req,
298 activation_t activation_flag,
299 std::string sec_websocket_accept_field_value,
300 WS_Message_Handler ws_message_handler )
301{
302 http_header_fields_t upgrade_response_header_fields;
303 upgrade_response_header_fields.set_field(
304 http_field::sec_websocket_accept,
305 std::move( sec_websocket_accept_field_value ) );
306
307 return
308 upgrade< Traits, WS_Message_Handler >(
309 req,
310 activation_flag,
311 std::move( upgrade_response_header_fields ),
312 std::move( ws_message_handler ) );
313}
314
315template <
316 typename Traits,
317 typename WS_Message_Handler >
318auto
320 generic_request_type_from_traits_t<Traits> & req,
321 activation_t activation_flag,
322 std::string sec_websocket_accept_field_value,
323 std::string sec_websocket_protocol_field_value,
324 WS_Message_Handler ws_message_handler )
325{
326 http_header_fields_t upgrade_response_header_fields;
327 upgrade_response_header_fields.set_field(
328 http_field::sec_websocket_accept,
329 std::move( sec_websocket_accept_field_value ) );
330
331 upgrade_response_header_fields.set_field(
332 http_field::sec_websocket_protocol,
333 std::move( sec_websocket_protocol_field_value ) );
334
335 return
336 upgrade< Traits, WS_Message_Handler >(
337 req,
338 activation_flag,
339 std::move( upgrade_response_header_fields ),
340 std::move( ws_message_handler ) );
341}
342
343template <
344 typename Traits,
345 typename WS_Message_Handler >
346auto
348 generic_request_type_from_traits_t<Traits> & req,
349 activation_t activation_flag,
350 WS_Message_Handler ws_message_handler )
351{
352 const char * websocket_accept_field_suffix = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
353 const auto ws_key =
354 req.header().get_field( restinio::http_field::sec_websocket_key ) +
355 websocket_accept_field_suffix;
356
357 auto digest = restinio::utils::sha1::make_digest( ws_key );
358
359 std::string sec_websocket_accept_field_value = utils::base64::encode(
360 utils::sha1::to_string( digest ) );
361
362 http_header_fields_t upgrade_response_header_fields;
363 upgrade_response_header_fields.set_field(
364 http_field::sec_websocket_accept,
365 std::move( sec_websocket_accept_field_value ) );
366
367 return
368 upgrade< Traits, WS_Message_Handler >(
369 req,
370 activation_flag,
371 std::move( upgrade_response_header_fields ),
372 std::move( ws_message_handler ) );
373}
374
375} /* namespace basic */
376
377} /* namespace websocket */
378
379} /* namespace restinio */
Exception class for all exceptions thrown by RESTinio.
Definition exception.hpp:26
exception_t(const char *err)
Definition exception.hpp:29
void swap_fields(http_header_fields_t &http_header_fields)
bool has_field(http_field_t field_id) const noexcept
Check field by field-id.
request_handler_type_from_traits_t< Traits > request_handler_t
Websocket message class with more detailed protocol information.
Definition ws_parser.hpp:63
final_frame_flag_t final_flag() const noexcept
Get final flag.
Definition message.hpp:168
opcode_t opcode() const noexcept
Definition message.hpp:186
ws_t & operator=(ws_t &&)=delete
void send_message(message_t msg, write_status_cb_t wscb=write_status_cb_t{})
ws_t(impl::ws_connection_handle_t ws_connection_handle, endpoint_t remote_endpoint)
Definition websocket.hpp:57
ws_t & operator=(const ws_t &)=delete
impl::ws_connection_handle_t m_ws_connection_handle
connection_id_t connection_id() const
Get connection id.
Definition websocket.hpp:80
void shutdown()
Shutdown websocket: wait for all outgoing data to be sent, and close connection.
Definition websocket.hpp:88
void kill()
Kill websocket: close underlying tcp socket. Do not tolerate unsent outgoing data.
friend void activate(ws_t &ws)
Activate websocket: start receiving messages.
Definition websocket.hpp:47
const endpoint_t & remote_endpoint() const noexcept
Get the remote endpoint of the underlying connection.
const endpoint_t m_remote_endpoint
Remote endpoint for this ws-connection.
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.
Class for storing the buffers used for streaming body (request/response).
Definition buffers.hpp:524
asio_ns::const_buffer buf() const
Create a buf reference object used by ASIO.
Definition buffers.hpp:622
writable_item_type_t write_type() const noexcept
Get a type of a stored buffer object.
Definition buffers.hpp:610
Group of writable items transported to the context of underlying connection as one solid piece.
Definition buffers.hpp:727
void after_write_notificator(write_status_cb_t notificator) noexcept
Set after write notificator.
Definition buffers.hpp:836
#define RESTINIO_FMT_FORMAT_STRING(s)
std::string to_string(const digest_t &what)
Definition sha1.hpp:398
raw_data_t write_message_details(const message_details_t &message)
Serialize websocket message details into bytes buffer.
std::shared_ptr< ws_connection_base_t > ws_connection_handle_t
Alias for WebSocket connection handle.
auto upgrade(generic_request_type_from_traits_t< Traits > &req, activation_t activation_flag, std::string sec_websocket_accept_field_value, WS_Message_Handler ws_message_handler)
auto upgrade(generic_request_type_from_traits_t< Traits > &req, activation_t activation_flag, WS_Message_Handler ws_message_handler)
activation_t
Flags for websocket activation policies.
@ immediate
Activate immediately after upgrade operation.
@ delayed
User will initiate activation later.
ws_handle_t upgrade(generic_request_type_from_traits_t< Traits > &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.
std::shared_ptr< ws_t > ws_handle_t
Alias for ws_t handle.
final_frame_flag_t
WS frame (message) "final"/"not final" flag.
Definition message.hpp:133
auto upgrade(generic_request_type_from_traits_t< Traits > &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)
asio_ns::ip::tcp::endpoint endpoint_t
An alias for endpoint type from Asio.
http_field_t http_field
Helper alies to omitt _t suffix.
http_connection_header_t
Values for conection header field.
std::function< void(const asio_ns::error_code &ec) > write_status_cb_t
An alias for a callback to be invoked after the write operation of a particular group of "buffers".
Definition buffers.hpp:714
writable_item_type_t
Buffers write operation type.
Definition buffers.hpp:443
@ trivial_write_operation
Item is a buffer and must be written trivially.
Definition buffers.hpp:445
const char * field_to_string(http_field_t f) noexcept
Helper sunction to get method string name.
http_status_line_t status_switching_protocols()
std::uint64_t connection_id_t
Type for ID of connection.
http_response_header_t(http_status_line_t status_line)