Coverage Report

Created: 2023-06-06 06:17

/src/uWebSockets/src/HttpContext.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_HTTPCONTEXT_H
19
#define UWS_HTTPCONTEXT_H
20
21
/* This class defines the main behavior of HTTP and emits various events */
22
23
#include "Loop.h"
24
#include "HttpContextData.h"
25
#include "HttpResponseData.h"
26
#include "AsyncSocket.h"
27
#include "WebSocketData.h"
28
29
#include <string_view>
30
#include <iostream>
31
#include "MoveOnlyFunction.h"
32
33
namespace uWS {
34
template<bool> struct HttpResponse;
35
36
template <bool SSL>
37
struct HttpContext {
38
    template<bool> friend struct TemplatedApp;
39
    template<bool> friend struct HttpResponse;
40
private:
41
    HttpContext() = delete;
42
43
    /* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */
44
    static const int HTTP_IDLE_TIMEOUT_S = 10;
45
46
    /* Minimum allowed receive throughput per second (clients uploading less than 16kB/sec get dropped) */
47
    static const int HTTP_RECEIVE_THROUGHPUT_BYTES = 16 * 1024;
48
49
106k
    us_socket_context_t *getSocketContext() {
50
106k
        return (us_socket_context_t *) this;
51
106k
    }
52
53
2.33M
    static us_socket_context_t *getSocketContext(us_socket_t *s) {
54
2.33M
        return (us_socket_context_t *) us_socket_context(SSL, s);
55
2.33M
    }
56
57
73.2k
    HttpContextData<SSL> *getSocketContextData() {
58
73.2k
        return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext());
59
73.2k
    }
60
61
2.33M
    static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) {
62
2.33M
        return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s));
63
2.33M
    }
64
65
    /* Init the HttpContext by registering libusockets event handlers */
66
4.13k
    HttpContext<SSL> *init() {
67
        /* Handle socket connections */
68
998k
        us_socket_context_on_open(SSL, getSocketContext(), [](us_socket_t *s, int /*is_client*/, char */*ip*/, int /*ip_length*/) {
69
            /* Any connected socket should timeout until it has a request */
70
998k
            us_socket_timeout(SSL, s, HTTP_IDLE_TIMEOUT_S);
71
72
            /* Init socket ext */
73
998k
            new (us_socket_ext(SSL, s)) HttpResponseData<SSL>;
74
75
            /* Call filter */
76
998k
            HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
77
998k
            for (auto &f : httpContextData->filterHandlers) {
78
0
                f((HttpResponse<SSL> *) s, 1);
79
0
            }
80
81
998k
            return s;
82
998k
        });
83
84
        /* Handle socket disconnections */
85
933k
        us_socket_context_on_close(SSL, getSocketContext(), [](us_socket_t *s, int /*code*/, void */*reason*/) {
86
            /* Get socket ext */
87
933k
            HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
88
89
            /* Call filter */
90
933k
            HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
91
933k
            for (auto &f : httpContextData->filterHandlers) {
92
0
                f((HttpResponse<SSL> *) s, -1);
93
0
            }
94
95
            /* Signal broken HTTP request only if we have a pending request */
96
933k
            if (httpResponseData->onAborted) {
97
0
                httpResponseData->onAborted();
98
0
            }
99
100
            /* Destruct socket ext */
101
933k
            httpResponseData->~HttpResponseData<SSL>();
102
103
933k
            return s;
104
933k
        });
105
106
        /* Handle HTTP data streams */
107
403k
        us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) {
108
109
            // total overhead is about 210k down to 180k
110
            // ~210k req/sec is the original perf with write in data
111
            // ~200k req/sec is with cork and formatting
112
            // ~190k req/sec is with http parsing
113
            // ~180k - 190k req/sec is with varying routing
114
115
403k
            HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
116
117
            /* Do not accept any data while in shutdown state */
118
403k
            if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
119
0
                return s;
120
0
            }
121
122
403k
            HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
123
124
            /* Cork this socket */
125
403k
            ((AsyncSocket<SSL> *) s)->cork();
126
127
            /* Mark that we are inside the parser now */
128
403k
            httpContextData->isParsingHttp = true;
129
130
            // clients need to know the cursor after http parse, not servers!
131
            // how far did we read then? we need to know to continue with websocket parsing data? or?
132
133
403k
            void *proxyParser = nullptr;
134
#ifdef UWS_WITH_PROXY
135
            proxyParser = &httpResponseData->proxyParser;
136
#endif
137
138
            /* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */
139
403k
            auto [err, returnedSocket] = httpResponseData->consumePostPadded(data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
140
                /* For every request we reset the timeout and hang until user makes action */
141
                /* Warning: if we are in shutdown state, resetting the timer is a security issue! */
142
79.4k
                us_socket_timeout(SSL, (us_socket_t *) s, 0);
143
144
                /* Reset httpResponse */
145
79.4k
                HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, (us_socket_t *) s);
146
79.4k
                httpResponseData->offset = 0;
147
148
                /* Are we not ready for another request yet? Terminate the connection. */
149
79.4k
                if (httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) {
150
0
                    us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
151
0
                    return nullptr;
152
0
                }
153
154
                /* Mark pending request and emit it */
155
79.4k
                httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
156
157
                /* Mark this response as connectionClose if ancient or connection: close */
158
79.4k
                if (httpRequest->isAncient() || httpRequest->getHeader("connection").length() == 5) {
159
194
                    httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
160
194
                }
161
162
                /* Select the router based on SNI (only possible for SSL) */
163
79.4k
                auto *selectedRouter = &httpContextData->router;
164
79.4k
                if constexpr (SSL) {
165
79.4k
                    void *domainRouter = us_socket_server_name_userdata(SSL, (struct us_socket_t *) s);
166
79.4k
                    if (domainRouter) {
167
0
                        selectedRouter = (decltype(selectedRouter)) domainRouter;
168
0
                    }
169
79.4k
                }
170
171
                /* Route the method and URL */
172
79.4k
                selectedRouter->getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
173
79.4k
                if (!selectedRouter->route(httpRequest->getCaseSensitiveMethod(), httpRequest->getUrl())) {
174
                    /* We have to force close this socket as we have no handler for it */
175
14.5k
                    us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
176
14.5k
                    return nullptr;
177
14.5k
                }
178
179
                /* First of all we need to check if this socket was deleted due to upgrade */
180
64.9k
                if (httpContextData->upgradedWebSocket) {
181
                    /* We differ between closed and upgraded below */
182
64.9k
                    return nullptr;
183
64.9k
                }
184
185
                /* Was the socket closed? */
186
0
                if (us_socket_is_closed(SSL, (struct us_socket_t *) s)) {
187
0
                    return nullptr;
188
0
                }
189
190
                /* We absolutely have to terminate parsing if shutdown */
191
0
                if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
192
0
                    return nullptr;
193
0
                }
194
195
                /* Returning from a request handler without responding or attaching an onAborted handler is ill-use */
196
0
                if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted) {
197
                    /* Throw exception here? */
198
0
                    std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl;
199
0
                    std::terminate();
200
0
                }
201
202
                /* If we have not responded and we have a data handler, we need to timeout to enfore client sending the data */
203
0
                if (!((HttpResponse<SSL> *) s)->hasResponded() && httpResponseData->inStream) {
204
0
                    us_socket_timeout(SSL, (us_socket_t *) s, HTTP_IDLE_TIMEOUT_S);
205
0
                }
206
207
                /* Continue parsing */
208
0
                return s;
209
210
0
            }, [httpResponseData](void *user, std::string_view data, bool fin) -> void * {
211
                /* We always get an empty chunk even if there is no data */
212
0
                if (httpResponseData->inStream) {
213
214
                    /* Todo: can this handle timeout for non-post as well? */
215
0
                    if (fin) {
216
                        /* If we just got the last chunk (or empty chunk), disable timeout */
217
0
                        us_socket_timeout(SSL, (struct us_socket_t *) user, 0);
218
0
                    } else {
219
                        /* We still have some more data coming in later, so reset timeout */
220
                        /* Only reset timeout if we got enough bytes (16kb/sec) since last time we reset here */
221
0
                        httpResponseData->received_bytes_per_timeout += (unsigned int) data.length();
222
0
                        if (httpResponseData->received_bytes_per_timeout >= HTTP_RECEIVE_THROUGHPUT_BYTES * HTTP_IDLE_TIMEOUT_S) {
223
0
                            us_socket_timeout(SSL, (struct us_socket_t *) user, HTTP_IDLE_TIMEOUT_S);
224
0
                            httpResponseData->received_bytes_per_timeout = 0;
225
0
                        }
226
0
                    }
227
228
                    /* We might respond in the handler, so do not change timeout after this */
229
0
                    httpResponseData->inStream(data, fin);
230
231
                    /* Was the socket closed? */
232
0
                    if (us_socket_is_closed(SSL, (struct us_socket_t *) user)) {
233
0
                        return nullptr;
234
0
                    }
235
236
                    /* We absolutely have to terminate parsing if shutdown */
237
0
                    if (us_socket_is_shut_down(SSL, (us_socket_t *) user)) {
238
0
                        return nullptr;
239
0
                    }
240
241
                    /* If we were given the last data chunk, reset data handler to ensure following
242
                     * requests on the same socket won't trigger any previously registered behavior */
243
0
                    if (fin) {
244
0
                        httpResponseData->inStream = nullptr;
245
0
                    }
246
0
                }
247
0
                return user;
248
0
            });
249
250
            /* Mark that we are no longer parsing Http */
251
403k
            httpContextData->isParsingHttp = false;
252
253
            /* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */
254
403k
            if (returnedSocket == FULLPTR) {
255
                /* For errors, we only deliver them "at most once". We don't care if they get halfways delivered or not. */
256
303k
                us_socket_write(SSL, s, httpErrorResponses[err].data(), (int) httpErrorResponses[err].length(), false);
257
303k
                us_socket_shutdown(SSL, s);
258
                /* Close any socket on HTTP errors */
259
303k
                us_socket_close(SSL, s, 0, nullptr);
260
                /* This just makes the following code act as if the socket was closed from error inside the parser. */
261
303k
                returnedSocket = nullptr;
262
303k
            }
263
264
            /* We need to uncork in all cases, except for nullptr (closed socket, or upgraded socket) */
265
403k
            if (returnedSocket != nullptr) {
266
                /* Timeout on uncork failure */
267
20.2k
                auto [written, failed] = ((AsyncSocket<SSL> *) returnedSocket)->uncork();
268
20.2k
                if (failed) {
269
                    /* All Http sockets timeout by this, and this behavior match the one in HttpResponse::cork */
270
                    /* Warning: both HTTP_IDLE_TIMEOUT_S and HTTP_TIMEOUT_S are 10 seconds and both are used the same */
271
0
                    ((AsyncSocket<SSL> *) s)->timeout(HTTP_IDLE_TIMEOUT_S);
272
0
                }
273
274
                /* We need to check if we should close this socket here now */
275
20.2k
                if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
276
0
                    if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
277
0
                        if (((AsyncSocket<SSL> *) s)->getBufferedAmount() == 0) {
278
0
                            ((AsyncSocket<SSL> *) s)->shutdown();
279
                            /* We need to force close after sending FIN since we want to hinder
280
                             * clients from keeping to send their huge data */
281
0
                            ((AsyncSocket<SSL> *) s)->close();
282
0
                        }
283
0
                    }
284
0
                }
285
286
20.2k
                return (us_socket_t *) returnedSocket;
287
20.2k
            }
288
289
            /* If we upgraded, check here (differ between nullptr close and nullptr upgrade) */
290
382k
            if (httpContextData->upgradedWebSocket) {
291
                /* This path is only for upgraded websockets */
292
64.9k
                AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) httpContextData->upgradedWebSocket;
293
294
                /* Uncork here as well (note: what if we failed to uncork and we then pub/sub before we even upgraded?) */
295
64.9k
                auto [written, failed] = asyncSocket->uncork();
296
297
                /* If we succeeded in uncorking, check if we have sent WebSocket FIN */
298
64.9k
                if (!failed) {
299
951
                    WebSocketData *webSocketData = (WebSocketData *) asyncSocket->getAsyncSocketData();
300
951
                    if (webSocketData->isShuttingDown) {
301
                        /* In that case, also send TCP FIN (this is similar to what we have in ws drain handler) */
302
0
                        asyncSocket->shutdown();
303
0
                    }
304
951
                }
305
306
                /* Reset upgradedWebSocket before we return */
307
64.9k
                httpContextData->upgradedWebSocket = nullptr;
308
309
                /* Return the new upgraded websocket */
310
64.9k
                return (us_socket_t *) asyncSocket;
311
64.9k
            }
312
313
            /* It is okay to uncork a closed socket and we need to */
314
317k
            ((AsyncSocket<SSL> *) s)->uncork();
315
316
            /* We cannot return nullptr to the underlying stack in any case */
317
317k
            return s;
318
382k
        });
319
320
        /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
321
4.13k
        us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) {
322
323
0
            AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
324
0
            HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData();
325
326
            /* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */
327
0
            if (httpResponseData->onWritable) {
328
                /* We are now writable, so hang timeout again, the user does not have to do anything so we should hang until end or tryEnd rearms timeout */
329
0
                us_socket_timeout(SSL, s, 0);
330
331
                /* We expect the developer to return whether or not write was successful (true).
332
                 * If write was never called, the developer should still return true so that we may drain. */
333
0
                bool success = httpResponseData->callOnWritable(httpResponseData->offset);
334
335
                /* The developer indicated that their onWritable failed. */
336
0
                if (!success) {
337
                    /* Skip testing if we can drain anything since that might perform an extra syscall */
338
0
                    return s;
339
0
                }
340
341
                /* We don't want to fall through since we don't want to mess with timeout.
342
                 * It makes little sense to drain any backpressure when the user has registered onWritable. */
343
0
                return s;
344
0
            }
345
346
            /* Drain any socket buffer, this might empty our backpressure and thus finish the request */
347
0
            /*auto [written, failed] = */asyncSocket->write(nullptr, 0, true, 0);
348
349
            /* Should we close this connection after a response - and is this response really done? */
350
0
            if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
351
0
                if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
352
0
                    if (asyncSocket->getBufferedAmount() == 0) {
353
0
                        asyncSocket->shutdown();
354
                        /* We need to force close after sending FIN since we want to hinder
355
                         * clients from keeping to send their huge data */
356
0
                        asyncSocket->close();
357
0
                    }
358
0
                }
359
0
            }
360
361
            /* Expect another writable event, or another request within the timeout */
362
0
            asyncSocket->timeout(HTTP_IDLE_TIMEOUT_S);
363
364
0
            return s;
365
0
        });
366
367
        /* Handle FIN, HTTP does not support half-closed sockets, so simply close */
368
55.6k
        us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) {
369
370
            /* We do not care for half closed sockets */
371
55.6k
            AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
372
55.6k
            return asyncSocket->close();
373
374
55.6k
        });
375
376
        /* Handle socket timeouts, simply close them so to not confuse client with FIN */
377
4.13k
        us_socket_context_on_timeout(SSL, getSocketContext(), [](us_socket_t *s) {
378
379
            /* Force close rather than gracefully shutdown and risk confusing the client with a complete download */
380
3.15k
            AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
381
3.15k
            return asyncSocket->close();
382
383
3.15k
        });
384
385
4.13k
        return this;
386
4.13k
    }
387
388
public:
389
    /* Construct a new HttpContext using specified loop */
390
4.13k
    static HttpContext *create(Loop *loop, us_socket_context_options_t options = {}) {
391
4.13k
        HttpContext *httpContext;
392
393
4.13k
        httpContext = (HttpContext *) us_create_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options);
394
395
4.13k
        if (!httpContext) {
396
0
            return nullptr;
397
0
        }
398
399
        /* Init socket context data */
400
4.13k
        new ((HttpContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) httpContext)) HttpContextData<SSL>();
401
4.13k
        return httpContext->init();
402
4.13k
    }
403
404
    /* Destruct the HttpContext, it does not follow RAII */
405
4.13k
    void free() {
406
        /* Destruct socket context data */
407
4.13k
        HttpContextData<SSL> *httpContextData = getSocketContextData();
408
4.13k
        httpContextData->~HttpContextData<SSL>();
409
410
        /* Free the socket context in whole */
411
4.13k
        us_socket_context_free(SSL, getSocketContext());
412
4.13k
    }
413
414
    void filter(MoveOnlyFunction<void(HttpResponse<SSL> *, int)> &&filterHandler) {
415
        getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler));
416
    }
417
418
    /* Register an HTTP route handler acording to URL pattern */
419
4.13k
    void onHttp(std::string method, std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
420
4.13k
        HttpContextData<SSL> *httpContextData = getSocketContextData();
421
422
        /* Todo: This is ugly, fix */
423
4.13k
        std::vector<std::string> methods;
424
4.13k
        if (method == "*") {
425
0
            methods = httpContextData->currentRouter->upperCasedMethods;
426
4.13k
        } else {
427
4.13k
            methods = {method};
428
4.13k
        }
429
430
4.13k
        uint32_t priority = method == "*" ? httpContextData->currentRouter->LOW_PRIORITY : (upgrade ? httpContextData->currentRouter->HIGH_PRIORITY : httpContextData->currentRouter->MEDIUM_PRIORITY);
431
432
        /* If we are passed nullptr then remove this */
433
4.13k
        if (!handler) {
434
0
            httpContextData->currentRouter->remove(methods[0], pattern, priority);
435
0
            return;
436
0
        }
437
438
66.5k
        httpContextData->currentRouter->add(methods, pattern, [handler = std::move(handler)](auto *r) mutable {
439
66.5k
            auto user = r->getUserData();
440
66.5k
            user.httpRequest->setYield(false);
441
66.5k
            user.httpRequest->setParameters(r->getParameters());
442
443
            /* Middleware? Automatically respond to expectations */
444
66.5k
            std::string_view expect = user.httpRequest->getHeader("expect");
445
66.5k
            if (expect.length() && expect == "100-continue") {
446
227
                user.httpResponse->writeContinue();
447
227
            }
448
449
66.5k
            handler(user.httpResponse, user.httpRequest);
450
451
            /* If any handler yielded, the router will keep looking for a suitable handler. */
452
66.5k
            if (user.httpRequest->getYield()) {
453
1.57k
                return false;
454
1.57k
            }
455
64.9k
            return true;
456
66.5k
        }, priority);
457
4.13k
    }
458
459
    /* Listen to port using this HttpContext */
460
4.13k
    us_listen_socket_t *listen(const char *host, int port, int options) {
461
4.13k
        return us_socket_context_listen(SSL, getSocketContext(), host, port, options, sizeof(HttpResponseData<SSL>));
462
4.13k
    }
463
464
    /* Listen to unix domain socket using this HttpContext */
465
    us_listen_socket_t *listen(const char *path, int options) {
466
        return us_socket_context_listen_unix(SSL, getSocketContext(), path, options, sizeof(HttpResponseData<SSL>));
467
    }
468
};
469
470
}
471
472
#endif // UWS_HTTPCONTEXT_H