Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
websocket_socket.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
6
9
11{
12 // ========================================================================
13 // websocket_socket implementation
14 // ========================================================================
15
16 websocket_socket::websocket_socket(std::shared_ptr<tcp_socket> socket,
17 bool is_client)
18 : tcp_socket_(std::move(socket))
19 , protocol_(is_client)
20 {
21 // Set up WebSocket protocol callbacks
23 [this](const ws_message& msg) { handle_protocol_message(msg); });
24
25 protocol_.set_ping_callback([this](const std::vector<uint8_t>& payload)
26 {
27 // RFC 6455: Automatically respond to Ping with Pong
28 auto pong_frame = protocol_.create_pong(
29 std::vector<uint8_t>(payload));
30 tcp_socket_->async_send(
31 std::move(pong_frame),
32 [](std::error_code, std::size_t) {});
33
34 handle_protocol_ping(payload);
35 });
36
37 protocol_.set_pong_callback([this](const std::vector<uint8_t>& payload)
38 { handle_protocol_pong(payload); });
39
40 protocol_.set_close_callback([this](ws_close_code code, const std::string& reason)
41 { handle_protocol_close(code, reason); });
42
43 // Set up TCP socket callbacks to feed data into protocol layer
44 // Use zero-copy span callback to avoid per-read vector allocation
45 tcp_socket_->set_receive_callback_view(
46 [this](std::span<const uint8_t> data) { on_tcp_receive(data); });
47
48 tcp_socket_->set_error_callback(
49 [this](std::error_code ec) { on_tcp_error(ec); });
50 }
51
53 {
54 // Cleanup resources if needed
55 }
56
57 auto websocket_socket::async_handshake(const std::string& host,
58 const std::string& path, uint16_t port,
59 std::function<void(std::error_code)> handler)
60 -> void
61 {
62 // Generate client handshake request
63 auto handshake_request =
65
66 // Store the expected key for validation
67 auto key_start = handshake_request.find("Sec-WebSocket-Key: ");
68 std::string client_key;
69 if (key_start != std::string::npos)
70 {
71 key_start += 19; // Length of "Sec-WebSocket-Key: "
72 auto key_end = handshake_request.find("\r\n", key_start);
73 client_key = handshake_request.substr(key_start, key_end - key_start);
74 }
75
76 // Send handshake request
77 std::vector<uint8_t> request_data(handshake_request.begin(),
78 handshake_request.end());
79
80 tcp_socket_->async_send(
81 std::move(request_data),
82 [this, client_key, handler](std::error_code ec, std::size_t)
83 {
84 if (ec)
85 {
86 handler(ec);
87 return;
88 }
89
90 // Set up temporary callback to receive handshake response
91 // Use span callback for zero-copy during handshake
92 tcp_socket_->set_receive_callback_view(
93 [this, client_key, handler](std::span<const uint8_t> data)
94 {
95 // Convert response to string
96 std::string response(data.begin(), data.end());
97
98 // Validate server response
100 response, client_key);
101
102 if (!result.success)
103 {
104 handler(std::make_error_code(std::errc::protocol_error));
105 return;
106 }
107
108 // Handshake successful, update state
109 state_ = ws_state::open;
110
111 // Restore normal receive callback
112 tcp_socket_->set_receive_callback_view(
113 [this](std::span<const uint8_t> data)
114 { on_tcp_receive(data); });
115
116 handler(std::error_code{});
117 });
118
119 // Start reading response
120 tcp_socket_->start_read();
121 });
122 }
123
124 auto websocket_socket::async_accept(std::function<void(std::error_code)> handler)
125 -> void
126 {
127 // Set up temporary callback to receive handshake request
128 // Use span callback for zero-copy during handshake
129 tcp_socket_->set_receive_callback_view(
130 [this, handler](std::span<const uint8_t> data)
131 {
132 // Convert request to string
133 std::string request(data.begin(), data.end());
134
135 // Parse client request
136 auto result = websocket_handshake::parse_client_request(request);
137
138 if (!result.success)
139 {
140 handler(std::make_error_code(std::errc::protocol_error));
141 return;
142 }
143
144 // Extract client key (headers are stored lowercase by parse_headers)
145 auto it = result.headers.find("sec-websocket-key");
146 if (it == result.headers.end())
147 {
148 handler(std::make_error_code(std::errc::protocol_error));
149 return;
150 }
151
152 std::string client_key = it->second;
153
154 // Generate server response
155 auto response = websocket_handshake::create_server_response(client_key);
156 std::vector<uint8_t> response_data(response.begin(), response.end());
157
158 // Send response
159 tcp_socket_->async_send(
160 std::move(response_data),
161 [this, handler](std::error_code ec, std::size_t)
162 {
163 if (ec)
164 {
165 handler(ec);
166 return;
167 }
168
169 // Handshake successful, update state
170 state_ = ws_state::open;
171
172 // Restore normal receive callback
173 tcp_socket_->set_receive_callback_view(
174 [this](std::span<const uint8_t> data)
175 { on_tcp_receive(data); });
176
177 handler(std::error_code{});
178 });
179 });
180
181 // Start reading request
182 tcp_socket_->start_read();
183 }
184
185 auto websocket_socket::start_read() -> void { tcp_socket_->start_read(); }
186
188 std::string&& message,
189 std::function<void(std::error_code, std::size_t)> handler) -> VoidResult
190 {
191 if (state_ != ws_state::open)
192 {
194 "WebSocket connection not open");
195 }
196
197 // Encode text message into WebSocket frame
198 auto frame_data = protocol_.create_text_message(std::move(message));
199
200 if (frame_data.empty())
201 {
203 "Invalid UTF-8 in text message");
204 }
205
206 // Send via underlying tcp_socket
207 tcp_socket_->async_send(std::move(frame_data), std::move(handler));
208
209 return ok();
210 }
211
213 std::vector<uint8_t>&& data,
214 std::function<void(std::error_code, std::size_t)> handler) -> VoidResult
215 {
216 if (state_ != ws_state::open)
217 {
219 "WebSocket connection not open");
220 }
221
222 // Encode binary message into WebSocket frame
223 auto frame_data = protocol_.create_binary_message(std::move(data));
224
225 // Send via underlying tcp_socket
226 tcp_socket_->async_send(std::move(frame_data), std::move(handler));
227
228 return ok();
229 }
230
232 std::vector<uint8_t> payload, std::function<void(std::error_code)> handler)
233 -> void
234 {
235 // Encode ping frame
236 auto frame_data = protocol_.create_ping(std::move(payload));
237
238 // Send via underlying tcp_socket
239 tcp_socket_->async_send(std::move(frame_data),
240 [handler](std::error_code ec, std::size_t)
241 {
242 if (handler)
243 {
244 handler(ec);
245 }
246 });
247 }
248
249 auto websocket_socket::async_close(ws_close_code code, const std::string& reason,
250 std::function<void(std::error_code)> handler)
251 -> void
252 {
253 // Update state to closing
254 state_ = ws_state::closing;
255
256 // Encode close frame
257 auto frame_data = protocol_.create_close(code, std::string(reason));
258
259 // Send via underlying tcp_socket
260 tcp_socket_->async_send(std::move(frame_data),
261 [this, handler](std::error_code ec, std::size_t)
262 {
263 if (ec)
264 {
265 state_ = ws_state::closed;
266 if (handler)
267 {
268 handler(ec);
269 }
270 return;
271 }
272
273 // Close handshake initiated successfully
274 if (handler)
275 {
276 handler(std::error_code{});
277 }
278 });
279 }
280
281 auto websocket_socket::state() const -> ws_state { return state_.load(); }
282
283 auto websocket_socket::is_open() const -> bool
284 {
285 return state_.load() == ws_state::open;
286 }
287
289 std::function<void(const ws_message&)> callback) -> void
290 {
291 std::lock_guard<std::mutex> lock(callback_mutex_);
292 message_callback_ = std::move(callback);
293 }
294
296 std::function<void(const std::vector<uint8_t>&)> callback) -> void
297 {
298 std::lock_guard<std::mutex> lock(callback_mutex_);
299 ping_callback_ = std::move(callback);
300 }
301
303 std::function<void(const std::vector<uint8_t>&)> callback) -> void
304 {
305 std::lock_guard<std::mutex> lock(callback_mutex_);
306 pong_callback_ = std::move(callback);
307 }
308
310 std::function<void(ws_close_code, const std::string&)> callback) -> void
311 {
312 std::lock_guard<std::mutex> lock(callback_mutex_);
313 close_callback_ = std::move(callback);
314 }
315
317 std::function<void(std::error_code)> callback) -> void
318 {
319 std::lock_guard<std::mutex> lock(callback_mutex_);
320 error_callback_ = std::move(callback);
321 }
322
323 auto websocket_socket::on_tcp_receive(std::span<const uint8_t> data) -> void
324 {
325 // Feed raw TCP data into WebSocket protocol decoder (zero-copy view)
326 protocol_.process_data(data);
327 }
328
329 auto websocket_socket::on_tcp_error(std::error_code ec) -> void
330 {
331 // Update state to closed
332 state_ = ws_state::closed;
333
334 // Propagate error to application
335 std::lock_guard<std::mutex> lock(callback_mutex_);
336 if (error_callback_)
337 {
338 error_callback_(ec);
339 }
340 }
341
343 {
344 std::lock_guard<std::mutex> lock(callback_mutex_);
345 if (message_callback_)
346 {
347 message_callback_(msg);
348 }
349 }
350
351 auto websocket_socket::handle_protocol_ping(const std::vector<uint8_t>& payload)
352 -> void
353 {
354 std::lock_guard<std::mutex> lock(callback_mutex_);
355 if (ping_callback_)
356 {
357 ping_callback_(payload);
358 }
359 }
360
361 auto websocket_socket::handle_protocol_pong(const std::vector<uint8_t>& payload)
362 -> void
363 {
364 std::lock_guard<std::mutex> lock(callback_mutex_);
365 if (pong_callback_)
366 {
367 pong_callback_(payload);
368 }
369 }
370
372 const std::string& reason) -> void
373 {
374 // Update state to closed
375 state_ = ws_state::closed;
376
377 std::lock_guard<std::mutex> lock(callback_mutex_);
378 if (close_callback_)
379 {
380 close_callback_(code, reason);
381 }
382 }
383
384} // namespace kcenon::network::internal
static auto create_server_response(const std::string &client_key) -> std::string
Creates a WebSocket handshake response (server-side).
static auto create_client_handshake(std::string_view host, std::string_view path, uint16_t port, const std::map< std::string, std::string > &extra_headers={}) -> std::string
Creates a WebSocket handshake request (client-side).
static auto validate_server_response(const std::string &response, const std::string &expected_key) -> ws_handshake_result
Validates a WebSocket handshake response (client-side).
static auto parse_client_request(const std::string &request) -> ws_handshake_result
Parses a WebSocket handshake request (server-side).
auto set_message_callback(std::function< void(const ws_message &)> callback) -> void
Sets the callback for data messages.
auto create_pong(std::vector< uint8_t > &&payload={}) -> std::vector< uint8_t >
Creates a pong control frame.
auto set_close_callback(std::function< void(ws_close_code, const std::string &)> callback) -> void
Sets the callback for close frames.
auto set_ping_callback(std::function< void(const std::vector< uint8_t > &)> callback) -> void
Sets the callback for ping frames.
auto set_pong_callback(std::function< void(const std::vector< uint8_t > &)> callback) -> void
Sets the callback for pong frames.
auto is_open() const -> bool
Checks if the connection is open and ready.
auto state() const -> ws_state
Gets the current connection state.
auto handle_protocol_ping(const std::vector< uint8_t > &payload) -> void
Handles a ping frame from the protocol layer.
std::shared_ptr< tcp_socket > tcp_socket_
auto set_error_callback(std::function< void(std::error_code)> callback) -> void
Sets the callback for errors.
auto async_send_binary(std::vector< uint8_t > &&data, std::function< void(std::error_code, std::size_t)> handler) -> VoidResult
Sends a binary message.
auto async_handshake(const std::string &host, const std::string &path, uint16_t port, std::function< void(std::error_code)> handler) -> void
Performs WebSocket client handshake (RFC 6455 Section 4.1).
auto handle_protocol_message(const ws_message &msg) -> void
Handles a decoded message from the protocol layer.
auto on_tcp_receive(std::span< const uint8_t > data) -> void
Called by tcp_socket when data is received.
auto set_close_callback(std::function< void(ws_close_code, const std::string &)> callback) -> void
Sets the callback for received close frames.
websocket_socket(std::shared_ptr< tcp_socket > socket, bool is_client)
Constructs a websocket_socket wrapping an existing tcp_socket.
auto start_read() -> void
Starts reading WebSocket frames from the underlying socket.
auto async_accept(std::function< void(std::error_code)> handler) -> void
Accepts WebSocket server handshake (RFC 6455 Section 4.2).
auto handle_protocol_close(ws_close_code code, const std::string &reason) -> void
Handles a close frame from the protocol layer.
auto async_send_text(std::string &&message, std::function< void(std::error_code, std::size_t)> handler) -> VoidResult
Sends a text message (UTF-8 encoded).
auto async_send_ping(std::vector< uint8_t > payload, std::function< void(std::error_code)> handler) -> void
Sends a ping control frame.
auto set_message_callback(std::function< void(const ws_message &)> callback) -> void
Sets the callback for received messages.
auto async_close(ws_close_code code, const std::string &reason, std::function< void(std::error_code)> handler) -> void
Initiates the WebSocket closing handshake.
auto on_tcp_error(std::error_code ec) -> void
Called by tcp_socket when an error occurs.
auto set_ping_callback(std::function< void(const std::vector< uint8_t > &)> callback) -> void
Sets the callback for received ping frames.
auto set_pong_callback(std::function< void(const std::vector< uint8_t > &)> callback) -> void
Sets the callback for received pong frames.
auto handle_protocol_pong(const std::vector< uint8_t > &payload) -> void
Handles a pong frame from the protocol layer.
ws_state
WebSocket connection state (RFC 6455 Section 7).
@ closing
Close handshake initiated.
@ open
Connection established and ready.
ws_close_code
WebSocket close status codes (RFC 6455 Section 7.4).
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
VoidResult ok()
Represents a complete WebSocket message.