Line data Source code
1 : #include "response.hpp"
2 : #include "request.hpp"
3 : #include "mime_types.hpp"
4 : #include "../../util/logger.hpp"
5 : #include "../../util/base64.hpp"
6 : #include "../../util/sha1.hpp"
7 : #include "../../asio/sockets/websocket.hpp"
8 : #include <boost/algorithm/string.hpp>
9 : #include "../common/http_data.hpp"
10 : #include "../data/out_chunk.hpp"
11 : #include <fstream>
12 : #include <sstream>
13 :
14 : namespace thinger::http {
15 :
16 : // Redirect implementation
17 67 : void response::redirect(const std::string& url, http::http_response::status redirect_type) {
18 67 : prepare_response();
19 67 : response_->set_status(redirect_type);
20 67 : response_->add_header(header::location, url);
21 67 : send_prepared_response();
22 67 : }
23 :
24 : // File sending implementation
25 12 : void response::send_file(const std::filesystem::path& path, bool force_download) {
26 16 : if (!ensure_not_responded()) return;
27 :
28 : // For now, we'll implement basic file sending here
29 : // TODO: Integrate with simple_file_handler when it's updated to work with response
30 :
31 12 : if (!std::filesystem::exists(path)) {
32 2 : error(http_response::status::not_found, "File not found");
33 2 : return;
34 : }
35 :
36 10 : if (!std::filesystem::is_regular_file(path)) {
37 2 : error(http_response::status::forbidden, "Not a regular file");
38 2 : return;
39 : }
40 :
41 : // Read file content
42 8 : std::ifstream file(path, std::ios::binary);
43 8 : if (!file) {
44 0 : error(http_response::status::internal_server_error, "Failed to open file");
45 0 : return;
46 : }
47 :
48 : // Get file size
49 8 : file.seekg(0, std::ios::end);
50 8 : auto file_size = file.tellg();
51 8 : file.seekg(0, std::ios::beg);
52 :
53 : // Read file content
54 8 : std::string content(file_size, '\0');
55 8 : file.read(&content[0], file_size);
56 :
57 : // Determine content type using mime_types
58 8 : std::string content_type = mime_types::extension_to_type(path.extension().string());
59 :
60 : // Create response
61 8 : prepare_response();
62 8 : response_->set_status(http_response::status::ok);
63 8 : response_->set_content(content, content_type);
64 :
65 : // Add Content-Disposition header if force_download is true
66 8 : if (force_download) {
67 6 : response_->add_header("Content-Disposition", "attachment; filename=\"" + path.filename().string() + "\"");
68 : }
69 :
70 8 : send_prepared_response();
71 8 : }
72 :
73 : // WebSocket upgrade implementation
74 38 : void response::upgrade_websocket(std::function<void(std::shared_ptr<websocket_connection>)> handler,
75 : const std::set<std::string>& supported_protocols) {
76 38 : if (!ensure_not_responded()) return;
77 :
78 38 : auto conn = connection_.lock();
79 38 : auto str = stream_.lock();
80 38 : if (!conn || !str) {
81 0 : error(http_response::status::internal_server_error, "Connection lost");
82 0 : return;
83 : }
84 :
85 : // Check if this is a WebSocket upgrade request
86 38 : if (!boost::iequals(http_request_->get_header(http::header::upgrade), "websocket")) {
87 0 : error(http_response::status::upgrade_required, "This service requires use of WebSockets");
88 0 : return;
89 : }
90 :
91 : // Check WebSocket protocol if specified
92 38 : auto protocol = http_request_->get_header("Sec-WebSocket-Protocol");
93 38 : if (!protocol.empty()) {
94 8 : LOG_DEBUG("Received WebSocket protocol: {}", protocol);
95 8 : if (!supported_protocols.contains(protocol)) {
96 0 : error(http_response::status::bad_request, "Unsupported WebSocket protocol");
97 0 : return;
98 : }
99 30 : } else if (!supported_protocols.empty()) {
100 0 : error(http_response::status::bad_request, "This method requires specifying a WebSocket protocol");
101 0 : return;
102 : }
103 :
104 : // Get WebSocket key
105 38 : auto ws_key = http_request_->get_header("Sec-WebSocket-Key");
106 38 : if (ws_key.empty()) {
107 0 : error(http_response::status::bad_request, "Missing Sec-WebSocket-Key header");
108 0 : return;
109 : }
110 :
111 : // Calculate WebSocket accept key
112 38 : std::string accept_key = ws_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
113 38 : std::string hash = ::thinger::util::sha1::hash(accept_key);
114 38 : accept_key = ::thinger::util::base64::encode(hash);
115 :
116 : // Create upgrade response
117 38 : prepare_response();
118 38 : response_->set_status(http_response::status::switching_protocols);
119 76 : response_->add_header(header::upgrade, "websocket");
120 76 : response_->add_header(header::connection, "Upgrade");
121 114 : response_->add_header("Sec-WebSocket-Accept", accept_key);
122 :
123 38 : if (!protocol.empty()) {
124 24 : response_->add_header("Sec-WebSocket-Protocol", protocol);
125 : }
126 :
127 : // Register callback to be executed after response is sent
128 38 : str->on_completed([handler = std::move(handler), conn, str]() {
129 : // Release the socket from HTTP connection
130 38 : auto socket = conn->release_socket();
131 38 : if (!socket) {
132 0 : LOG_ERROR("Failed to release socket for WebSocket upgrade");
133 0 : return;
134 : }
135 :
136 : // Create WebSocket from the socket
137 38 : auto websocket = std::make_shared<asio::websocket>(socket, true, true); // binary=true, server=true
138 38 : auto ws_connection = std::make_shared<websocket_connection>(websocket);
139 :
140 : // Call user handler
141 38 : handler(ws_connection);
142 :
143 : // Start the WebSocket connection
144 38 : ws_connection->start();
145 38 : });
146 :
147 : // Send the upgrade response
148 38 : conn->handle_stream(str, response_);
149 :
150 38 : responded_ = true;
151 38 : }
152 :
153 : // Server-Sent Events implementation
154 0 : void response::start_sse(std::function<void(std::shared_ptr<sse_connection>)> handler) {
155 0 : if (!ensure_not_responded()) return;
156 :
157 0 : auto conn = connection_.lock();
158 0 : auto str = stream_.lock();
159 0 : if (!conn || !str) {
160 0 : error(http_response::status::internal_server_error, "Connection lost");
161 0 : return;
162 : }
163 :
164 : // Create SSE response headers
165 0 : prepare_response();
166 0 : response_->set_status(http_response::status::ok);
167 0 : response_->set_content_type("text/event-stream");
168 0 : response_->add_header("Cache-Control", "no-cache");
169 0 : response_->add_header("Connection", "keep-alive");
170 0 : response_->add_header("X-Accel-Buffering", "no"); // Disable nginx buffering
171 :
172 : // Register callback to be executed after headers are sent
173 0 : str->on_completed([handler = std::move(handler), conn]() {
174 : // Release the socket from HTTP connection
175 0 : auto socket = conn->release_socket();
176 0 : if (!socket) {
177 0 : LOG_ERROR("Failed to release socket for SSE");
178 0 : return;
179 : }
180 :
181 : // Create SSE connection
182 0 : auto sse_conn = std::make_shared<sse_connection>(socket);
183 :
184 : // Start the SSE connection
185 0 : sse_conn->start();
186 :
187 : // Call user handler
188 0 : handler(sse_conn);
189 0 : });
190 :
191 : // Send the SSE headers
192 0 : conn->handle_stream(str, response_);
193 :
194 0 : responded_ = true;
195 0 : }
196 :
197 : // Chunked response support
198 8 : bool response::start_chunked(const std::string& content_type, http::http_response::status status) {
199 8 : if (!ensure_not_responded()) return false;
200 :
201 8 : auto conn = connection_.lock();
202 8 : auto str = stream_.lock();
203 8 : if (!conn || !str) {
204 0 : LOG_ERROR("Connection lost while starting chunked response");
205 0 : return false;
206 : }
207 :
208 : // Create chunked response headers
209 8 : prepare_response();
210 8 : response_->set_status(status);
211 8 : response_->set_content_type(content_type);
212 32 : response_->add_header("Transfer-Encoding", "chunked");
213 32 : response_->add_header("X-Content-Type-Options", "nosniff");
214 8 : response_->set_last_frame(false);
215 :
216 : // Send headers (stream stays open for subsequent chunks)
217 8 : conn->handle_stream(str, response_);
218 :
219 8 : responded_ = true;
220 8 : return true;
221 8 : }
222 :
223 10 : bool response::write_chunk(const std::string& data) {
224 10 : if (!responded_) {
225 0 : LOG_ERROR("Must call start_chunked() before writing chunks");
226 0 : return false;
227 : }
228 :
229 10 : auto conn = connection_.lock();
230 10 : auto str = stream_.lock();
231 10 : if (!conn || !str) {
232 0 : LOG_ERROR("Connection lost while writing chunk");
233 0 : return false;
234 : }
235 :
236 10 : auto chunk = std::make_shared<http_data>(std::make_shared<data::out_chunk>(data));
237 10 : chunk->set_last_frame(false);
238 10 : conn->handle_stream(str, chunk);
239 10 : return true;
240 10 : }
241 :
242 8 : bool response::end_chunked() {
243 8 : if (!responded_) {
244 0 : LOG_ERROR("Must call start_chunked() before ending chunks");
245 0 : return false;
246 : }
247 :
248 8 : auto conn = connection_.lock();
249 8 : auto str = stream_.lock();
250 8 : if (!conn || !str) {
251 0 : return false;
252 : }
253 :
254 : // Send final zero-length chunk to terminate the response
255 8 : auto chunk = std::make_shared<http_data>(std::make_shared<data::out_chunk>());
256 8 : chunk->set_last_frame(true);
257 8 : conn->handle_stream(str, chunk);
258 8 : return true;
259 8 : }
260 :
261 : } // namespace thinger::http
|