/src/uWebSockets/src/HttpResponse.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Authored by Alex Hultman, 2018-2020. |
3 | | * Intellectual property of third-party. |
4 | | |
5 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
6 | | * you may not use this file except in compliance with the License. |
7 | | * You may obtain a copy of the License at |
8 | | |
9 | | * http://www.apache.org/licenses/LICENSE-2.0 |
10 | | |
11 | | * Unless required by applicable law or agreed to in writing, software |
12 | | * distributed under the License is distributed on an "AS IS" BASIS, |
13 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | * See the License for the specific language governing permissions and |
15 | | * limitations under the License. |
16 | | */ |
17 | | |
18 | | #ifndef UWS_HTTPRESPONSE_H |
19 | | #define UWS_HTTPRESPONSE_H |
20 | | |
21 | | /* An HttpResponse is the channel on which you send back a response */ |
22 | | |
23 | | #include "AsyncSocket.h" |
24 | | #include "HttpResponseData.h" |
25 | | #include "HttpContext.h" |
26 | | #include "HttpContextData.h" |
27 | | #include "Utilities.h" |
28 | | |
29 | | #include "WebSocketExtensions.h" |
30 | | #include "WebSocketHandshake.h" |
31 | | #include "WebSocket.h" |
32 | | #include "WebSocketContextData.h" |
33 | | |
34 | | #include "MoveOnlyFunction.h" |
35 | | |
36 | | /* todo: tryWrite is missing currently, only send smaller segments with write */ |
37 | | |
38 | | namespace uWS { |
39 | | |
40 | | /* Some pre-defined status constants to use with writeStatus */ |
41 | | static const char *HTTP_200_OK = "200 OK"; |
42 | | |
43 | | /* The general timeout for HTTP sockets */ |
44 | | static const int HTTP_TIMEOUT_S = 10; |
45 | | |
46 | | template <bool SSL> |
47 | | struct HttpResponse : public AsyncSocket<SSL> { |
48 | | /* Solely used for getHttpResponseData() */ |
49 | | template <bool> friend struct TemplatedApp; |
50 | | typedef AsyncSocket<SSL> Super; |
51 | | private: |
52 | 1.08M | HttpResponseData<SSL> *getHttpResponseData() { |
53 | 1.08M | return (HttpResponseData<SSL> *) Super::getAsyncSocketData(); |
54 | 1.08M | } |
55 | | |
56 | | /* Write an unsigned 32-bit integer in hex */ |
57 | 0 | void writeUnsignedHex(unsigned int value) { |
58 | | /* Buf really only needs to be 8 long but building with |
59 | | * -mavx2, GCC still wants to overstep it so made it 16 */ |
60 | 0 | char buf[16]; |
61 | 0 | int length = utils::u32toaHex(value, buf); |
62 | | |
63 | | /* For now we do this copy */ |
64 | 0 | Super::write(buf, length); |
65 | 0 | } |
66 | | |
67 | | /* Write an unsigned 64-bit integer */ |
68 | 0 | void writeUnsigned64(uint64_t value) { |
69 | 0 | char buf[20]; |
70 | 0 | int length = utils::u64toa(value, buf); |
71 | | |
72 | | /* For now we do this copy */ |
73 | 0 | Super::write(buf, length); |
74 | 0 | } |
75 | | |
76 | | /* Called only once per request */ |
77 | 108k | void writeMark() { |
78 | | /* Date is always written */ |
79 | 108k | writeHeader("Date", std::string_view(((LoopData *) us_loop_ext(us_socket_context_loop(SSL, (us_socket_context(SSL, (us_socket_t *) this)))))->date, 29)); |
80 | | |
81 | | /* You can disable this altogether */ |
82 | 108k | #ifndef UWS_HTTPRESPONSE_NO_WRITEMARK |
83 | 108k | if (!Super::getLoopData()->noMark) { |
84 | | /* We only expose major version */ |
85 | 108k | writeHeader("uWebSockets", "20"); |
86 | 108k | } |
87 | 108k | #endif |
88 | 108k | } |
89 | | |
90 | | /* Returns true on success, indicating that it might be feasible to write more data. |
91 | | * Will start timeout if stream reaches totalSize or write failure. */ |
92 | 108k | bool internalEnd(std::string_view data, uintmax_t totalSize, bool optional, bool allowContentLength = true, bool closeConnection = false) { |
93 | | /* Write status if not already done */ |
94 | 108k | writeStatus(HTTP_200_OK); |
95 | | |
96 | | /* If no total size given then assume this chunk is everything */ |
97 | 108k | if (!totalSize) { |
98 | 108k | totalSize = data.length(); |
99 | 108k | } |
100 | | |
101 | 108k | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
102 | | |
103 | | /* In some cases, such as when refusing huge data we want to close the connection when drained */ |
104 | 108k | if (closeConnection) { |
105 | | |
106 | | /* HTTP 1.1 must send this back unless the client already sent it to us. |
107 | | * It is a connection close when either of the two parties say so but the |
108 | | * one party must tell the other one so. |
109 | | * |
110 | | * This check also serves to limit writing the header only once. */ |
111 | 0 | if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) == 0) { |
112 | 0 | writeHeader("Connection", "close"); |
113 | 0 | } |
114 | |
|
115 | 0 | httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE; |
116 | 0 | } |
117 | | |
118 | 108k | if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) { |
119 | | |
120 | | /* We do not have tryWrite-like functionalities, so ignore optional in this path */ |
121 | | |
122 | | /* Do not allow sending 0 chunk here */ |
123 | 0 | if (data.length()) { |
124 | 0 | Super::write("\r\n", 2); |
125 | 0 | writeUnsignedHex((unsigned int) data.length()); |
126 | 0 | Super::write("\r\n", 2); |
127 | | |
128 | | /* Ignoring optional for now */ |
129 | 0 | Super::write(data.data(), (int) data.length()); |
130 | 0 | } |
131 | | |
132 | | /* Terminating 0 chunk */ |
133 | 0 | Super::write("\r\n0\r\n\r\n", 7); |
134 | |
|
135 | 0 | httpResponseData->markDone(); |
136 | | |
137 | | /* We need to check if we should close this socket here now */ |
138 | 0 | if (!Super::isCorked()) { |
139 | 0 | if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) { |
140 | 0 | if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) { |
141 | 0 | if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) { |
142 | 0 | ((AsyncSocket<SSL> *) this)->shutdown(); |
143 | | /* We need to force close after sending FIN since we want to hinder |
144 | | * clients from keeping to send their huge data */ |
145 | 0 | ((AsyncSocket<SSL> *) this)->close(); |
146 | 0 | return true; |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | | /* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */ |
153 | 0 | Super::timeout(HTTP_TIMEOUT_S); |
154 | 0 | return true; |
155 | 108k | } else { |
156 | | /* Write content-length on first call */ |
157 | 108k | if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) { |
158 | | /* Write mark, this propagates to WebSockets too */ |
159 | 108k | writeMark(); |
160 | | |
161 | | /* WebSocket upgrades does not allow content-length */ |
162 | 108k | if (allowContentLength) { |
163 | | /* Even zero is a valid content-length */ |
164 | 0 | Super::write("Content-Length: ", 16); |
165 | 0 | writeUnsigned64(totalSize); |
166 | 0 | Super::write("\r\n\r\n", 4); |
167 | 108k | } else { |
168 | 108k | Super::write("\r\n", 2); |
169 | 108k | } |
170 | | |
171 | | /* Mark end called */ |
172 | 108k | httpResponseData->state |= HttpResponseData<SSL>::HTTP_END_CALLED; |
173 | 108k | } |
174 | | |
175 | | /* Even if we supply no new data to write, its failed boolean is useful to know |
176 | | * if it failed to drain any prior failed header writes */ |
177 | | |
178 | | /* Write as much as possible without causing backpressure */ |
179 | 108k | size_t written = 0; |
180 | 108k | bool failed = false; |
181 | 108k | while (written < data.length() && !failed) { |
182 | | /* uSockets only deals with int sizes, so pass chunks of max signed int size */ |
183 | 0 | auto writtenFailed = Super::write(data.data() + written, (int) std::min<size_t>(data.length() - written, INT_MAX), optional); |
184 | |
|
185 | 0 | written += (size_t) writtenFailed.first; |
186 | 0 | failed = writtenFailed.second; |
187 | 0 | } |
188 | | |
189 | 108k | httpResponseData->offset += written; |
190 | | |
191 | | /* Success is when we wrote the entire thing without any failures */ |
192 | 108k | bool success = written == data.length() && !failed; |
193 | | |
194 | | /* If we are now at the end, start a timeout. Also start a timeout if we failed. */ |
195 | 108k | if (!success || httpResponseData->offset == totalSize) { |
196 | 108k | Super::timeout(HTTP_TIMEOUT_S); |
197 | 108k | } |
198 | | |
199 | | /* Remove onAborted function if we reach the end */ |
200 | 108k | if (httpResponseData->offset == totalSize) { |
201 | 108k | httpResponseData->markDone(); |
202 | | |
203 | | /* We need to check if we should close this socket here now */ |
204 | 108k | if (!Super::isCorked()) { |
205 | 0 | if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) { |
206 | 0 | if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) { |
207 | 0 | if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) { |
208 | 0 | ((AsyncSocket<SSL> *) this)->shutdown(); |
209 | | /* We need to force close after sending FIN since we want to hinder |
210 | | * clients from keeping to send their huge data */ |
211 | 0 | ((AsyncSocket<SSL> *) this)->close(); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | 0 | } |
216 | 108k | } |
217 | | |
218 | 108k | return success; |
219 | 108k | } |
220 | 108k | } |
221 | | |
222 | | public: |
223 | | /* If we have proxy support; returns the proxed source address as reported by the proxy. */ |
224 | | #ifdef UWS_WITH_PROXY |
225 | | std::string_view getProxiedRemoteAddress() { |
226 | | return getHttpResponseData()->proxyParser.getSourceAddress(); |
227 | | } |
228 | | |
229 | | std::string_view getProxiedRemoteAddressAsText() { |
230 | | return Super::addressAsText(getProxiedRemoteAddress()); |
231 | | } |
232 | | #endif |
233 | | |
234 | | /* Manually upgrade to WebSocket. Typically called in upgrade handler. Immediately calls open handler. |
235 | | * NOTE: Will invalidate 'this' as socket might change location in memory. Throw away after use. */ |
236 | | template <typename UserData> |
237 | | void upgrade(UserData &&userData, std::string_view secWebSocketKey, std::string_view secWebSocketProtocol, |
238 | | std::string_view secWebSocketExtensions, |
239 | 108k | struct us_socket_context_t *webSocketContext) { |
240 | | |
241 | | /* Extract needed parameters from WebSocketContextData */ |
242 | 108k | WebSocketContextData<SSL, UserData> *webSocketContextData = (WebSocketContextData<SSL, UserData> *) us_socket_context_ext(SSL, webSocketContext); |
243 | | |
244 | | /* Note: OpenSSL can be used here to speed this up somewhat */ |
245 | 108k | char secWebSocketAccept[29] = {}; |
246 | 108k | WebSocketHandshake::generate(secWebSocketKey.data(), secWebSocketAccept); |
247 | | |
248 | 108k | writeStatus("101 Switching Protocols") |
249 | 108k | ->writeHeader("Upgrade", "websocket") |
250 | 108k | ->writeHeader("Connection", "Upgrade") |
251 | 108k | ->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept); |
252 | | |
253 | | /* Select first subprotocol if present */ |
254 | 108k | if (secWebSocketProtocol.length()) { |
255 | 520 | writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(','))); |
256 | 520 | } |
257 | | |
258 | | /* Negotiate compression */ |
259 | 108k | bool perMessageDeflate = false; |
260 | 108k | CompressOptions compressOptions = CompressOptions::DISABLED; |
261 | 108k | if (secWebSocketExtensions.length() && webSocketContextData->compression != DISABLED) { |
262 | | |
263 | | /* Make sure to map SHARED_DECOMPRESSOR to windowBits = 0, not 1 */ |
264 | 8.07k | int wantedInflationWindow = 0; |
265 | 8.07k | if ((webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) != CompressOptions::SHARED_DECOMPRESSOR) { |
266 | 8.07k | wantedInflationWindow = (webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) >> 8; |
267 | 8.07k | } |
268 | | |
269 | | /* Map from selected compressor (this automatically maps SHARED_COMPRESSOR to windowBits 0, not 1) */ |
270 | 8.07k | int wantedCompressionWindow = (webSocketContextData->compression & CompressOptions::_COMPRESSOR_MASK) >> 4; |
271 | | |
272 | 8.07k | auto [negCompression, negCompressionWindow, negInflationWindow, negResponse] = |
273 | 8.07k | negotiateCompression(true, wantedCompressionWindow, wantedInflationWindow, |
274 | 8.07k | secWebSocketExtensions); |
275 | | |
276 | 8.07k | if (negCompression) { |
277 | 4.18k | perMessageDeflate = true; |
278 | | |
279 | | /* Map from negotiated windowBits to compressor and decompressor */ |
280 | 4.18k | if (negCompressionWindow == 0) { |
281 | 2.21k | compressOptions = CompressOptions::SHARED_COMPRESSOR; |
282 | 2.21k | } else { |
283 | 1.97k | compressOptions = (CompressOptions) ((uint32_t) (negCompressionWindow << 4) |
284 | 1.97k | | (uint32_t) (negCompressionWindow - 7)); |
285 | | |
286 | | /* If we are dedicated and have the 3kb then correct any 4kb to 3kb, |
287 | | * (they both share the windowBits = 9) */ |
288 | 1.97k | if (webSocketContextData->compression & DEDICATED_COMPRESSOR_3KB) { |
289 | 1.97k | compressOptions = DEDICATED_COMPRESSOR_3KB; |
290 | 1.97k | } |
291 | 1.97k | } |
292 | | |
293 | | /* Here we modify the above compression with negotiated decompressor */ |
294 | 4.18k | if (negInflationWindow == 0) { |
295 | 4.18k | compressOptions = CompressOptions(compressOptions | CompressOptions::SHARED_DECOMPRESSOR); |
296 | 4.18k | } else { |
297 | 0 | compressOptions = CompressOptions(compressOptions | (negInflationWindow << 8)); |
298 | 0 | } |
299 | | |
300 | 4.18k | writeHeader("Sec-WebSocket-Extensions", negResponse); |
301 | 4.18k | } |
302 | 8.07k | } |
303 | | |
304 | 108k | internalEnd({nullptr, 0}, 0, false, false); |
305 | | |
306 | | /* Grab the httpContext from res */ |
307 | 108k | HttpContext<SSL> *httpContext = (HttpContext<SSL> *) us_socket_context(SSL, (struct us_socket_t *) this); |
308 | | |
309 | | /* Move any backpressure out of HttpResponse */ |
310 | 108k | BackPressure backpressure(std::move(((AsyncSocketData<SSL> *) getHttpResponseData())->buffer)); |
311 | | |
312 | | /* Destroy HttpResponseData */ |
313 | 108k | getHttpResponseData()->~HttpResponseData(); |
314 | | |
315 | | /* Before we adopt and potentially change socket, check if we are corked */ |
316 | 108k | bool wasCorked = Super::isCorked(); |
317 | | |
318 | | /* Adopting a socket invalidates it, do not rely on it directly to carry any data */ |
319 | 108k | WebSocket<SSL, true, UserData> *webSocket = (WebSocket<SSL, true, UserData> *) us_socket_context_adopt_socket(SSL, |
320 | 108k | (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(WebSocketData) + sizeof(UserData)); |
321 | | |
322 | | /* For whatever reason we were corked, update cork to the new socket */ |
323 | 108k | if (wasCorked) { |
324 | 108k | webSocket->AsyncSocket<SSL>::corkUnchecked(); |
325 | 108k | } |
326 | | |
327 | | /* Initialize websocket with any moved backpressure intact */ |
328 | 108k | webSocket->init(perMessageDeflate, compressOptions, std::move(backpressure)); |
329 | | |
330 | | /* We should only mark this if inside the parser; if upgrading "async" we cannot set this */ |
331 | 108k | HttpContextData<SSL> *httpContextData = httpContext->getSocketContextData(); |
332 | 108k | if (httpContextData->isParsingHttp) { |
333 | | /* We need to tell the Http parser that we changed socket */ |
334 | 108k | httpContextData->upgradedWebSocket = webSocket; |
335 | 108k | } |
336 | | |
337 | | /* Arm maxLifetime timeout */ |
338 | 108k | us_socket_long_timeout(SSL, (us_socket_t *) webSocket, webSocketContextData->maxLifetime); |
339 | | |
340 | | /* Arm idleTimeout */ |
341 | 108k | us_socket_timeout(SSL, (us_socket_t *) webSocket, webSocketContextData->idleTimeoutComponents.first); |
342 | | |
343 | | /* Move construct the UserData right before calling open handler */ |
344 | 108k | new (webSocket->getUserData()) UserData(std::move(userData)); |
345 | | |
346 | | /* Emit open event and start the timeout */ |
347 | 108k | if (webSocketContextData->openHandler) { |
348 | 108k | webSocketContextData->openHandler(webSocket); |
349 | 108k | } |
350 | 108k | } |
351 | | |
352 | | /* Immediately terminate this Http response */ |
353 | | using Super::close; |
354 | | |
355 | | /* See AsyncSocket */ |
356 | | using Super::getRemoteAddress; |
357 | | using Super::getRemoteAddressAsText; |
358 | | using Super::getNativeHandle; |
359 | | |
360 | | /* Throttle reads and writes */ |
361 | | HttpResponse *pause() { |
362 | | Super::pause(); |
363 | | Super::timeout(0); |
364 | | return this; |
365 | | } |
366 | | |
367 | | HttpResponse *resume() { |
368 | | Super::resume(); |
369 | | Super::timeout(HTTP_TIMEOUT_S); |
370 | | return this; |
371 | | } |
372 | | |
373 | | /* Note: Headers are not checked in regards to timeout. |
374 | | * We only check when you actively push data or end the request */ |
375 | | |
376 | | /* Write 100 Continue, can be done any amount of times */ |
377 | 194 | HttpResponse *writeContinue() { |
378 | 194 | Super::write("HTTP/1.1 100 Continue\r\n\r\n", 25); |
379 | 194 | return this; |
380 | 194 | } |
381 | | |
382 | | /* Write the HTTP status */ |
383 | 762k | HttpResponse *writeStatus(std::string_view status) { |
384 | 762k | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
385 | | |
386 | | /* Do not allow writing more than one status */ |
387 | 762k | if (httpResponseData->state & HttpResponseData<SSL>::HTTP_STATUS_CALLED) { |
388 | 654k | return this; |
389 | 654k | } |
390 | | |
391 | | /* Update status */ |
392 | 108k | httpResponseData->state |= HttpResponseData<SSL>::HTTP_STATUS_CALLED; |
393 | | |
394 | 108k | Super::write("HTTP/1.1 ", 9); |
395 | 108k | Super::write(status.data(), (int) status.length()); |
396 | 108k | Super::write("\r\n", 2); |
397 | 108k | return this; |
398 | 762k | } |
399 | | |
400 | | /* Write an HTTP header with string value */ |
401 | 546k | HttpResponse *writeHeader(std::string_view key, std::string_view value) { |
402 | 546k | writeStatus(HTTP_200_OK); |
403 | | |
404 | 546k | Super::write(key.data(), (int) key.length()); |
405 | 546k | Super::write(": ", 2); |
406 | 546k | Super::write(value.data(), (int) value.length()); |
407 | 546k | Super::write("\r\n", 2); |
408 | 546k | return this; |
409 | 546k | } |
410 | | |
411 | | /* Write an HTTP header with unsigned int value */ |
412 | | HttpResponse *writeHeader(std::string_view key, uint64_t value) { |
413 | | writeStatus(HTTP_200_OK); |
414 | | |
415 | | Super::write(key.data(), (int) key.length()); |
416 | | Super::write(": ", 2); |
417 | | writeUnsigned64(value); |
418 | | Super::write("\r\n", 2); |
419 | | return this; |
420 | | } |
421 | | |
422 | | /* End without a body (no content-length) or end with a spoofed content-length. */ |
423 | | void endWithoutBody(std::optional<size_t> reportedContentLength = std::nullopt, bool closeConnection = false) { |
424 | | if (reportedContentLength.has_value()) { |
425 | | internalEnd({nullptr, 0}, reportedContentLength.value(), false, true, closeConnection); |
426 | | } else { |
427 | | internalEnd({nullptr, 0}, 0, false, false, closeConnection); |
428 | | } |
429 | | } |
430 | | |
431 | | /* End the response with an optional data chunk. Always starts a timeout. */ |
432 | | void end(std::string_view data = {}, bool closeConnection = false) { |
433 | | internalEnd(data, data.length(), false, true, closeConnection); |
434 | | } |
435 | | |
436 | | /* Try and end the response. Returns [true, true] on success. |
437 | | * Starts a timeout in some cases. Returns [ok, hasResponded] */ |
438 | | std::pair<bool, bool> tryEnd(std::string_view data, uintmax_t totalSize = 0, bool closeConnection = false) { |
439 | | return {internalEnd(data, totalSize, true, true, closeConnection), hasResponded()}; |
440 | | } |
441 | | |
442 | | /* Write parts of the response in chunking fashion. Starts timeout if failed. */ |
443 | | bool write(std::string_view data) { |
444 | | writeStatus(HTTP_200_OK); |
445 | | |
446 | | /* Do not allow sending 0 chunks, they mark end of response */ |
447 | | if (!data.length()) { |
448 | | /* If you called us, then according to you it was fine to call us so it's fine to still call us */ |
449 | | return true; |
450 | | } |
451 | | |
452 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
453 | | |
454 | | if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) { |
455 | | /* Write mark on first call to write */ |
456 | | writeMark(); |
457 | | |
458 | | writeHeader("Transfer-Encoding", "chunked"); |
459 | | httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED; |
460 | | } |
461 | | |
462 | | Super::write("\r\n", 2); |
463 | | writeUnsignedHex((unsigned int) data.length()); |
464 | | Super::write("\r\n", 2); |
465 | | |
466 | | auto [written, failed] = Super::write(data.data(), (int) data.length()); |
467 | | if (failed) { |
468 | | Super::timeout(HTTP_TIMEOUT_S); |
469 | | } |
470 | | |
471 | | /* If we did not fail the write, accept more */ |
472 | | return !failed; |
473 | | } |
474 | | |
475 | | /* Get the current byte write offset for this Http response */ |
476 | | uintmax_t getWriteOffset() { |
477 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
478 | | |
479 | | return httpResponseData->offset; |
480 | | } |
481 | | |
482 | | /* If you are messing around with sendfile you might want to override the offset. */ |
483 | | void overrideWriteOffset(uintmax_t offset) { |
484 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
485 | | |
486 | | httpResponseData->offset = offset; |
487 | | } |
488 | | |
489 | | /* Checking if we have fully responded and are ready for another request */ |
490 | 0 | bool hasResponded() { |
491 | 0 | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
492 | |
|
493 | 0 | return !(httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING); |
494 | 0 | } |
495 | | |
496 | | /* Corks the response if possible. Leaves already corked socket be. */ |
497 | | HttpResponse *cork(MoveOnlyFunction<void()> &&handler) { |
498 | | if (!Super::isCorked() && Super::canCork()) { |
499 | | Super::cork(); |
500 | | handler(); |
501 | | |
502 | | /* The only way we could possibly have changed the corked socket during handler call, would be if |
503 | | * the HTTP socket was upgraded to WebSocket and caused a realloc. Because of this we cannot use "this" |
504 | | * from here downwards. The corking is done with corkUnchecked() in upgrade. It steals cork. */ |
505 | | auto *newCorkedSocket = Super::corkedSocket(); |
506 | | |
507 | | /* If nobody is corked, it means most probably that large amounts of data has |
508 | | * been written and the cork buffer has already been sent off and uncorked. |
509 | | * We are done here, if that is the case. */ |
510 | | if (!newCorkedSocket) { |
511 | | return this; |
512 | | } |
513 | | |
514 | | /* Timeout on uncork failure, since most writes will succeed while corked */ |
515 | | auto [written, failed] = static_cast<Super *>(newCorkedSocket)->uncork(); |
516 | | |
517 | | /* If we are no longer an HTTP socket then early return the new "this". |
518 | | * We don't want to even overwrite timeout as it is set in upgrade already. */ |
519 | | if (this != newCorkedSocket) { |
520 | | return static_cast<HttpResponse *>(newCorkedSocket); |
521 | | } |
522 | | |
523 | | if (failed) { |
524 | | /* For now we only have one single timeout so let's use it */ |
525 | | /* This behavior should equal the behavior in HttpContext when uncorking fails */ |
526 | | Super::timeout(HTTP_TIMEOUT_S); |
527 | | } |
528 | | |
529 | | /* If we have no backbuffer and we are connection close and we responded fully then close */ |
530 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
531 | | if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) { |
532 | | if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) { |
533 | | if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) { |
534 | | ((AsyncSocket<SSL> *) this)->shutdown(); |
535 | | /* We need to force close after sending FIN since we want to hinder |
536 | | * clients from keeping to send their huge data */ |
537 | | ((AsyncSocket<SSL> *) this)->close(); |
538 | | } |
539 | | } |
540 | | } |
541 | | } else { |
542 | | /* We are already corked, or can't cork so let's just call the handler */ |
543 | | handler(); |
544 | | } |
545 | | |
546 | | return this; |
547 | | } |
548 | | |
549 | | /* Attach handler for writable HTTP response */ |
550 | | HttpResponse *onWritable(MoveOnlyFunction<bool(uintmax_t)> &&handler) { |
551 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
552 | | |
553 | | httpResponseData->onWritable = std::move(handler); |
554 | | return this; |
555 | | } |
556 | | |
557 | | /* Attach handler for aborted HTTP request */ |
558 | | HttpResponse *onAborted(MoveOnlyFunction<void()> &&handler) { |
559 | | HttpResponseData<SSL> *httpResponseData = getHttpResponseData(); |
560 | | |
561 | | httpResponseData->onAborted = std::move(handler); |
562 | | return this; |
563 | | } |
564 | | |
565 | | /* Attach a read handler for data sent. Will be called with FIN set true if last segment. */ |
566 | | void onData(MoveOnlyFunction<void(std::string_view, bool)> &&handler) { |
567 | | HttpResponseData<SSL> *data = getHttpResponseData(); |
568 | | data->inStream = std::move(handler); |
569 | | |
570 | | /* Always reset this counter here */ |
571 | | data->received_bytes_per_timeout = 0; |
572 | | } |
573 | | }; |
574 | | |
575 | | } |
576 | | |
577 | | #endif // UWS_HTTPRESPONSE_H |