yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
httplib_websocket.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <regex>
5
6#include "util/macro.h" // For RETURN_IF_ERROR
7
8#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9#define CPPHTTPLIB_OPENSSL_SUPPORT
10#endif
11#include "httplib.h"
12
13namespace yaze {
14namespace net {
15
19
25
26absl::Status HttpLibWebSocket::ParseWebSocketUrl(const std::string& ws_url,
27 std::string& http_url) {
28 // Convert ws:// to http:// and wss:// to https://
29 std::regex ws_regex(R"(^(wss?)://(.+)$)");
30 std::smatch matches;
31
32 if (!std::regex_match(ws_url, matches, ws_regex)) {
33 return absl::InvalidArgumentError("Invalid WebSocket URL: " + ws_url);
34 }
35
36 std::string scheme = matches[1].str();
37 std::string rest = matches[2].str();
38
39 if (scheme == "ws") {
40 http_url = "http://" + rest;
41 } else if (scheme == "wss") {
42 http_url = "https://" + rest;
43 } else {
44 return absl::InvalidArgumentError("Invalid WebSocket scheme: " + scheme);
45 }
46
47 url_ = ws_url;
48 return absl::OkStatus();
49}
50
51absl::Status HttpLibWebSocket::Connect(const std::string& url) {
53 return absl::FailedPreconditionError(
54 "WebSocket already connected or connecting");
55 }
56
58
59 // Convert WebSocket URL to HTTP URL
61
62 // Parse HTTP URL to extract host and port
63 std::regex url_regex(R"(^(https?)://([^:/\s]+)(?::(\d+))?(/.*)?)$)");
64 std::smatch matches;
65
66 if (!std::regex_match(http_endpoint_, matches, url_regex)) {
68 return absl::InvalidArgumentError("Invalid HTTP URL: " + http_endpoint_);
69 }
70
71 std::string scheme = matches[1].str();
72 std::string host = matches[2].str();
73 int port = matches[3].matched ? std::stoi(matches[3].str())
74 : (scheme == "https" ? 443 : 80);
75
76 // Create HTTP client
77 if (scheme == "https") {
78#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
79 client_ = std::make_shared<httplib::Client>(host, port);
80 client_->enable_server_certificate_verification(false); // For development
81#else
83 return absl::UnimplementedError(
84 "WSS not supported: OpenSSL support not compiled in");
85#endif
86 } else {
87 client_ = std::make_shared<httplib::Client>(host, port);
88 }
89
90 if (!client_) {
92 return absl::InternalError("Failed to create HTTP client");
93 }
94
95 // Set reasonable timeouts
96 client_->set_connection_timeout(10);
97 client_->set_read_timeout(30);
98
99 // Note: This is a simplified implementation. A real WebSocket implementation
100 // would perform the WebSocket handshake here. For now, we'll use HTTP
101 // long-polling as a fallback.
102
103 // Generate session ID for this connection
104 session_id_ = "session_" + std::to_string(
105 std::chrono::system_clock::now().time_since_epoch().count());
106
108
109 // Call open callback if set
110 if (open_callback_) {
112 }
113
114 // Start receive loop in background thread
115 stop_receive_ = false;
116 receive_thread_ = std::thread([this]() { ReceiveLoop(); });
117
118 return absl::OkStatus();
119}
120
121absl::Status HttpLibWebSocket::Send(const std::string& message) {
123 return absl::FailedPreconditionError("WebSocket not connected");
124 }
125
126 if (!client_) {
127 return absl::InternalError("HTTP client not initialized");
128 }
129
130 // Note: This is a simplified implementation using HTTP POST
131 // A real WebSocket would send frames over the persistent connection
132
133 httplib::Headers headers = {
134 {"Content-Type", "text/plain"},
135 {"X-Session-Id", session_id_}
136 };
137
138 auto res = client_->Post("/send", headers, message, "text/plain");
139
140 if (!res) {
141 return absl::UnavailableError("Failed to send message");
142 }
143
144 if (res->status != 200) {
145 return absl::InternalError("Server returned status " +
146 std::to_string(res->status));
147 }
148
149 return absl::OkStatus();
150}
151
152absl::Status HttpLibWebSocket::SendBinary(const uint8_t* data, size_t length) {
154 return absl::FailedPreconditionError("WebSocket not connected");
155 }
156
157 if (!client_) {
158 return absl::InternalError("HTTP client not initialized");
159 }
160
161 // Convert binary data to string for HTTP transport
162 std::string body(reinterpret_cast<const char*>(data), length);
163
164 httplib::Headers headers = {
165 {"Content-Type", "application/octet-stream"},
166 {"X-Session-Id", session_id_}
167 };
168
169 auto res = client_->Post("/send-binary", headers, body,
170 "application/octet-stream");
171
172 if (!res) {
173 return absl::UnavailableError("Failed to send binary data");
174 }
175
176 if (res->status != 200) {
177 return absl::InternalError("Server returned status " +
178 std::to_string(res->status));
179 }
180
181 return absl::OkStatus();
182}
183
184absl::Status HttpLibWebSocket::Close(int code, const std::string& reason) {
187 return absl::OkStatus();
188 }
189
191
192 // Stop receive loop
194
195 if (client_) {
196 // Send close notification to server
197 httplib::Headers headers = {
198 {"X-Session-Id", session_id_},
199 {"X-Close-Code", std::to_string(code)},
200 {"X-Close-Reason", reason}
201 };
202
203 client_->Post("/close", headers, "", "text/plain");
204 client_.reset();
205 }
206
208
209 // Call close callback if set
210 if (close_callback_) {
211 close_callback_(code, reason);
212 }
213
215
216 return absl::OkStatus();
217}
218
221 if (!client_) {
222 break;
223 }
224
225 // Long-polling: make a request that blocks until there's a message
226 httplib::Headers headers = {
227 {"X-Session-Id", session_id_}
228 };
229
230 auto res = client_->Get("/poll", headers);
231
232 if (stop_receive_) {
233 break;
234 }
235
236 if (!res) {
237 // Connection error
238 if (error_callback_) {
239 error_callback_("Connection lost");
240 }
241 break;
242 }
243
244 if (res->status == 200 && !res->body.empty()) {
245 // Received a message
246 if (message_callback_) {
247 message_callback_(res->body);
248 }
249 } else if (res->status == 204) {
250 // No content - continue polling
251 continue;
252 } else if (res->status >= 400) {
253 // Error from server
254 if (error_callback_) {
255 error_callback_("Server error: " + std::to_string(res->status));
256 }
257 break;
258 }
259
260 // Small delay to prevent tight loop
261 std::this_thread::sleep_for(std::chrono::milliseconds(10));
262 }
263}
264
266 stop_receive_ = true;
267 if (receive_thread_.joinable()) {
268 receive_thread_.join();
269 }
270}
271
272} // namespace net
273} // namespace yaze
std::atomic< bool > stop_receive_
absl::Status Connect(const std::string &url) override
Connect to a WebSocket server.
absl::Status Send(const std::string &message) override
Send a text message.
absl::Status ParseWebSocketUrl(const std::string &ws_url, std::string &http_url)
Parse WebSocket URL into HTTP components.
absl::Status SendBinary(const uint8_t *data, size_t length) override
Send a binary message.
void StopReceiveLoop()
Stop the receive loop.
absl::Status Close(int code=1000, const std::string &reason="") override
Close the WebSocket connection.
std::shared_ptr< httplib::Client > client_
void ReceiveLoop()
Background thread for receiving messages (polling)
MessageCallback message_callback_
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22