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