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