Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
ws_connection_adapter.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
6
7#include <algorithm>
8#include <cstring>
9#include <regex>
10
12
13ws_connection_adapter::ws_connection_adapter(std::string_view connection_id)
14 : connection_id_(connection_id)
15 , client_(std::make_shared<core::messaging_ws_client>(connection_id))
16{
18}
19
24
25auto ws_connection_adapter::send(std::span<const std::byte> data) -> VoidResult
26{
27 if (!client_ || !client_->is_connected()) {
28 return error_void(
29 static_cast<int>(std::errc::not_connected),
30 "WebSocket is not connected"
31 );
32 }
33
34 std::vector<uint8_t> buffer(data.size());
35 std::memcpy(buffer.data(), data.data(), data.size());
36
37 return client_->send_binary(std::move(buffer));
38}
39
40auto ws_connection_adapter::send(std::vector<uint8_t>&& data) -> VoidResult
41{
42 if (!client_ || !client_->is_connected()) {
43 return error_void(
44 static_cast<int>(std::errc::not_connected),
45 "WebSocket is not connected"
46 );
47 }
48
49 return client_->send_binary(std::move(data));
50}
51
52auto ws_connection_adapter::is_connected() const noexcept -> bool
53{
54 return client_ && client_->is_connected();
55}
56
57auto ws_connection_adapter::id() const noexcept -> std::string_view
58{
59 return connection_id_;
60}
61
63{
64 std::lock_guard lock(endpoint_mutex_);
65 return remote_endpoint_;
66}
67
69{
70 std::lock_guard lock(endpoint_mutex_);
71 return local_endpoint_;
72}
73
75{
76 if (!client_) {
77 return error_void(
78 static_cast<int>(std::errc::invalid_argument),
79 "Client is not initialized"
80 );
81 }
82
83 if (client_->is_connected() || client_->is_running()) {
84 return error_void(
85 static_cast<int>(std::errc::already_connected),
86 "Already connected or connecting"
87 );
88 }
89
90 {
91 std::lock_guard lock(endpoint_mutex_);
92 remote_endpoint_ = endpoint;
93 }
94
95 is_connecting_ = true;
96
97 // Use endpoint's port, default to 80 if not specified
98 uint16_t port = endpoint.port;
99 if (port == 0) {
100 port = 80;
101 }
102
103 auto result = client_->start_client(endpoint.host, port, ws_path_);
104
105 if (!result.is_ok()) {
106 is_connecting_ = false;
107 }
108
109 return result;
110}
111
112auto ws_connection_adapter::connect(std::string_view url) -> VoidResult
113{
114 if (!client_) {
115 return error_void(
116 static_cast<int>(std::errc::invalid_argument),
117 "Client is not initialized"
118 );
119 }
120
121 if (client_->is_connected() || client_->is_running()) {
122 return error_void(
123 static_cast<int>(std::errc::already_connected),
124 "Already connected or connecting"
125 );
126 }
127
128 std::string host;
129 uint16_t port = 0;
130 std::string path;
131 bool secure = false;
132
133 if (!parse_websocket_url(url, host, port, path, secure)) {
134 return error_void(
135 static_cast<int>(std::errc::invalid_argument),
136 "Invalid WebSocket URL format. Expected: ws://host:port/path or wss://host:port/path"
137 );
138 }
139
140 {
141 std::lock_guard lock(endpoint_mutex_);
142 remote_endpoint_ = endpoint_info{host, port};
143 }
144
145 is_connecting_ = true;
146
147 auto result = client_->start_client(host, port, path);
148
149 if (!result.is_ok()) {
150 is_connecting_ = false;
151 }
152
153 return result;
154}
155
156auto ws_connection_adapter::close() noexcept -> void
157{
158 if (client_ && client_->is_running()) {
159 (void)client_->stop();
160 }
161 is_connecting_ = false;
162}
163
165{
166 {
167 std::lock_guard lock(callbacks_mutex_);
168 callbacks_ = std::move(callbacks);
169 }
170
171 setup_internal_callbacks();
172}
173
175{
176 options_ = options;
177}
178
179auto ws_connection_adapter::set_timeout(std::chrono::milliseconds timeout) -> void
180{
181 options_.connect_timeout = timeout;
182}
183
184auto ws_connection_adapter::is_connecting() const noexcept -> bool
185{
186 return is_connecting_.load();
187}
188
190{
191 if (client_) {
192 client_->wait_for_stop();
193 }
194}
195
196auto ws_connection_adapter::set_path(std::string_view path) -> void
197{
198 ws_path_ = std::string(path);
199}
200
202{
203 if (!client_) {
204 return;
205 }
206
207 // Bridge binary message callback to on_data
208 client_->set_binary_callback([this](const std::vector<uint8_t>& data) {
209 std::lock_guard lock(callbacks_mutex_);
210 if (callbacks_.on_data) {
211 std::span<const std::byte> byte_span(
212 reinterpret_cast<const std::byte*>(data.data()),
213 data.size()
214 );
215 callbacks_.on_data(byte_span);
216 }
217 });
218
219 // Also bridge text messages as binary data for unified interface
220 client_->set_text_callback([this](const std::string& text) {
221 std::lock_guard lock(callbacks_mutex_);
222 if (callbacks_.on_data) {
223 std::span<const std::byte> byte_span(
224 reinterpret_cast<const std::byte*>(text.data()),
225 text.size()
226 );
227 callbacks_.on_data(byte_span);
228 }
229 });
230
231 // Bridge connected callback
232 client_->set_connected_callback([this]() {
233 is_connecting_ = false;
234
235 std::lock_guard lock(callbacks_mutex_);
236 if (callbacks_.on_connected) {
237 callbacks_.on_connected();
238 }
239 });
240
241 // Bridge disconnected callback
242 client_->set_disconnected_callback([this](uint16_t /*code*/, std::string_view /*reason*/) {
243 is_connecting_ = false;
244
245 std::lock_guard lock(callbacks_mutex_);
246 if (callbacks_.on_disconnected) {
247 callbacks_.on_disconnected();
248 }
249 });
250
251 // Bridge error callback
252 client_->set_error_callback([this](std::error_code ec) {
253 is_connecting_ = false;
254
255 std::lock_guard lock(callbacks_mutex_);
256 if (callbacks_.on_error) {
257 callbacks_.on_error(ec);
258 }
259 });
260}
261
263 std::string& host,
264 uint16_t& port,
265 std::string& path,
266 bool& secure) -> bool
267{
268 std::string url_str(url);
269
270 // Check for WebSocket schemes
271 secure = false;
272 std::string remaining;
273
274 if (url_str.substr(0, 6) == "wss://") {
275 secure = true;
276 remaining = url_str.substr(6);
277 } else if (url_str.substr(0, 5) == "ws://") {
278 secure = false;
279 remaining = url_str.substr(5);
280 } else {
281 // No scheme, assume ws://
282 remaining = url_str;
283 }
284
285 // Find path start
286 auto path_pos = remaining.find('/');
287 std::string host_port;
288 if (path_pos != std::string::npos) {
289 host_port = remaining.substr(0, path_pos);
290 path = remaining.substr(path_pos);
291 } else {
292 host_port = remaining;
293 path = "/";
294 }
295
296 // Parse host and port
297 auto colon_pos = host_port.rfind(':');
298
299 // Check if this is IPv6 (contains '[')
300 bool is_ipv6 = host_port.find('[') != std::string::npos;
301
302 if (is_ipv6) {
303 auto bracket_end = host_port.find(']');
304 if (bracket_end == std::string::npos) {
305 return false; // Invalid IPv6 format
306 }
307
308 host = host_port.substr(0, bracket_end + 1);
309
310 if (bracket_end + 1 < host_port.size() && host_port[bracket_end + 1] == ':') {
311 std::string port_str = host_port.substr(bracket_end + 2);
312 try {
313 port = static_cast<uint16_t>(std::stoi(port_str));
314 } catch (...) {
315 return false;
316 }
317 } else {
318 port = secure ? 443 : 80;
319 }
320 } else if (colon_pos != std::string::npos) {
321 host = host_port.substr(0, colon_pos);
322 std::string port_str = host_port.substr(colon_pos + 1);
323 try {
324 port = static_cast<uint16_t>(std::stoi(port_str));
325 } catch (...) {
326 return false;
327 }
328 } else {
329 host = host_port;
330 port = secure ? 443 : 80;
331 }
332
333 return !host.empty();
334}
335
336} // namespace kcenon::network::unified::adapters
static auto parse_websocket_url(std::string_view url, std::string &host, uint16_t &port, std::string &path, bool &secure) -> bool
Parses a WebSocket URL into components.
auto set_timeout(std::chrono::milliseconds timeout) -> void override
Sets the connection timeout.
auto send(std::span< const std::byte > data) -> VoidResult override
Sends raw data to the remote endpoint.
auto set_callbacks(connection_callbacks callbacks) -> void override
Sets all connection callbacks at once.
auto connect(const endpoint_info &endpoint) -> VoidResult override
Connects to a remote endpoint using host/port.
auto setup_internal_callbacks() -> void
Sets up internal callbacks to bridge to unified callbacks.
auto set_path(std::string_view path) -> void
Sets the WebSocket path for endpoint-based connections.
auto wait_for_stop() -> void override
Blocks until the component has stopped.
auto close() noexcept -> void override
Closes the connection gracefully.
auto set_options(connection_options options) -> void override
Sets connection options.
auto id() const noexcept -> std::string_view override
Gets the unique identifier for this transport/connection.
auto remote_endpoint() const noexcept -> endpoint_info override
Gets the remote endpoint information.
ws_connection_adapter(std::string_view connection_id)
Constructs an adapter with a unique connection ID.
std::shared_ptr< core::messaging_ws_client > client_
auto local_endpoint() const noexcept -> endpoint_info override
Gets the local endpoint information.
auto is_connecting() const noexcept -> bool override
Checks if the connection is in the process of connecting.
~ws_connection_adapter() override
Destructor ensures proper cleanup.
auto is_connected() const noexcept -> bool override
Checks if the transport is currently connected.
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
Callback functions for connection events.
Definition types.h:154
Configuration options for connections.
Definition types.h:198
Network endpoint information (host/port or URL)
Definition types.h:56