Coverage Report

Created: 2023-06-06 06:17

/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