opal-up 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +209 -0
- data/README.md +81 -28
- data/bin/up_ruby +4 -0
- data/bin/up_ruby_cluster +4 -0
- data/ext/up_ext/App.h +606 -0
- data/ext/up_ext/AsyncSocket.h +355 -0
- data/ext/up_ext/AsyncSocketData.h +87 -0
- data/ext/up_ext/BloomFilter.h +83 -0
- data/ext/up_ext/ChunkedEncoding.h +236 -0
- data/ext/up_ext/ClientApp.h +36 -0
- data/ext/up_ext/HttpContext.h +502 -0
- data/ext/up_ext/HttpContextData.h +56 -0
- data/ext/up_ext/HttpErrors.h +53 -0
- data/ext/up_ext/HttpParser.h +680 -0
- data/ext/up_ext/HttpResponse.h +578 -0
- data/ext/up_ext/HttpResponseData.h +95 -0
- data/ext/up_ext/HttpRouter.h +380 -0
- data/ext/up_ext/Loop.h +204 -0
- data/ext/up_ext/LoopData.h +112 -0
- data/ext/up_ext/MoveOnlyFunction.h +377 -0
- data/ext/up_ext/PerMessageDeflate.h +315 -0
- data/ext/up_ext/ProxyParser.h +163 -0
- data/ext/up_ext/QueryParser.h +120 -0
- data/ext/up_ext/TopicTree.h +363 -0
- data/ext/up_ext/Utilities.h +66 -0
- data/ext/up_ext/WebSocket.h +381 -0
- data/ext/up_ext/WebSocketContext.h +434 -0
- data/ext/up_ext/WebSocketContextData.h +109 -0
- data/ext/up_ext/WebSocketData.h +86 -0
- data/ext/up_ext/WebSocketExtensions.h +256 -0
- data/ext/up_ext/WebSocketHandshake.h +145 -0
- data/ext/up_ext/WebSocketProtocol.h +506 -0
- data/ext/up_ext/bsd.c +767 -0
- data/ext/up_ext/bsd.h +109 -0
- data/ext/up_ext/context.c +524 -0
- data/ext/up_ext/epoll_kqueue.c +458 -0
- data/ext/up_ext/epoll_kqueue.h +67 -0
- data/ext/up_ext/extconf.rb +5 -0
- data/ext/up_ext/internal.h +224 -0
- data/ext/up_ext/libusockets.h +350 -0
- data/ext/up_ext/libuwebsockets.cpp +1374 -0
- data/ext/up_ext/libuwebsockets.h +260 -0
- data/ext/up_ext/loop.c +386 -0
- data/ext/up_ext/loop_data.h +38 -0
- data/ext/up_ext/socket.c +231 -0
- data/ext/up_ext/up_ext.c +278 -0
- data/lib/up/node/rack_env.rb +2 -2
- data/lib/up/ruby/cluster_cli.rb +10 -0
- data/lib/up/ruby/rack_cluster.rb +26 -0
- data/lib/up/ruby/rack_env.rb +97 -0
- data/lib/up/ruby/rack_server.rb +26 -0
- data/lib/up/ruby/server_cli.rb +10 -0
- data/lib/up/u_web_socket/rack_env.rb +1 -1
- data/lib/up/version.rb +1 -1
- metadata +71 -18
- data/.gitignore +0 -5
- data/Gemfile +0 -2
- data/example_rack_app/Gemfile +0 -3
- data/example_rack_app/config.ru +0 -6
- data/example_rack_app/rack_app.rb +0 -5
- data/example_roda_app/Gemfile +0 -6
- data/example_roda_app/config.ru +0 -6
- data/example_roda_app/roda_app.rb +0 -37
- data/example_sinatra_app/Gemfile +0 -6
- data/example_sinatra_app/config.ru +0 -6
- data/example_sinatra_app/sinatra_app.rb +0 -7
- data/opal-up.gemspec +0 -27
- data/up_logo.svg +0 -256
@@ -0,0 +1,502 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2020.
|
3
|
+
* Intellectual property of third-party.
|
4
|
+
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
* See the License for the specific language governing permissions and
|
15
|
+
* limitations under the License.
|
16
|
+
*/
|
17
|
+
|
18
|
+
#ifndef UWS_HTTPCONTEXT_H
|
19
|
+
#define UWS_HTTPCONTEXT_H
|
20
|
+
|
21
|
+
/* This class defines the main behavior of HTTP and emits various events */
|
22
|
+
|
23
|
+
#include "Loop.h"
|
24
|
+
#include "HttpContextData.h"
|
25
|
+
#include "HttpResponseData.h"
|
26
|
+
#include "AsyncSocket.h"
|
27
|
+
#include "WebSocketData.h"
|
28
|
+
|
29
|
+
#include <string_view>
|
30
|
+
#include <iostream>
|
31
|
+
#include "MoveOnlyFunction.h"
|
32
|
+
|
33
|
+
namespace uWS {
|
34
|
+
template<bool> struct HttpResponse;
|
35
|
+
|
36
|
+
template <bool SSL>
|
37
|
+
struct HttpContext {
|
38
|
+
template<bool> friend struct TemplatedApp;
|
39
|
+
template<bool> friend struct HttpResponse;
|
40
|
+
private:
|
41
|
+
HttpContext() = delete;
|
42
|
+
|
43
|
+
/* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */
|
44
|
+
static const int HTTP_IDLE_TIMEOUT_S = 10;
|
45
|
+
|
46
|
+
/* Minimum allowed receive throughput per second (clients uploading less than 16kB/sec get dropped) */
|
47
|
+
static const int HTTP_RECEIVE_THROUGHPUT_BYTES = 16 * 1024;
|
48
|
+
|
49
|
+
us_loop_t *getLoop() {
|
50
|
+
return us_socket_context_loop(SSL, getSocketContext());
|
51
|
+
}
|
52
|
+
|
53
|
+
us_socket_context_t *getSocketContext() {
|
54
|
+
return (us_socket_context_t *) this;
|
55
|
+
}
|
56
|
+
|
57
|
+
static us_socket_context_t *getSocketContext(us_socket_t *s) {
|
58
|
+
return (us_socket_context_t *) us_socket_context(SSL, s);
|
59
|
+
}
|
60
|
+
|
61
|
+
HttpContextData<SSL> *getSocketContextData() {
|
62
|
+
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext());
|
63
|
+
}
|
64
|
+
|
65
|
+
static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) {
|
66
|
+
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s));
|
67
|
+
}
|
68
|
+
|
69
|
+
/* Init the HttpContext by registering libusockets event handlers */
|
70
|
+
HttpContext<SSL> *init() {
|
71
|
+
/* Handle socket connections */
|
72
|
+
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
|
+
us_socket_timeout(SSL, s, HTTP_IDLE_TIMEOUT_S);
|
75
|
+
|
76
|
+
/* Init socket ext */
|
77
|
+
new (us_socket_ext(SSL, s)) HttpResponseData<SSL>;
|
78
|
+
|
79
|
+
/* Call filter */
|
80
|
+
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
|
81
|
+
for (auto &f : httpContextData->filterHandlers) {
|
82
|
+
f((HttpResponse<SSL> *) s, 1);
|
83
|
+
}
|
84
|
+
|
85
|
+
return s;
|
86
|
+
});
|
87
|
+
|
88
|
+
/* Handle socket disconnections */
|
89
|
+
us_socket_context_on_close(SSL, getSocketContext(), [](us_socket_t *s, int /*code*/, void */*reason*/) {
|
90
|
+
/* Get socket ext */
|
91
|
+
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
|
92
|
+
|
93
|
+
/* Call filter */
|
94
|
+
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
|
95
|
+
for (auto &f : httpContextData->filterHandlers) {
|
96
|
+
f((HttpResponse<SSL> *) s, -1);
|
97
|
+
}
|
98
|
+
|
99
|
+
/* Signal broken HTTP request only if we have a pending request */
|
100
|
+
if (httpResponseData->onAborted) {
|
101
|
+
httpResponseData->onAborted();
|
102
|
+
}
|
103
|
+
|
104
|
+
/* Destruct socket ext */
|
105
|
+
httpResponseData->~HttpResponseData<SSL>();
|
106
|
+
|
107
|
+
return s;
|
108
|
+
});
|
109
|
+
|
110
|
+
/* Handle HTTP data streams */
|
111
|
+
us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) {
|
112
|
+
|
113
|
+
// total overhead is about 210k down to 180k
|
114
|
+
// ~210k req/sec is the original perf with write in data
|
115
|
+
// ~200k req/sec is with cork and formatting
|
116
|
+
// ~190k req/sec is with http parsing
|
117
|
+
// ~180k - 190k req/sec is with varying routing
|
118
|
+
|
119
|
+
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
|
120
|
+
|
121
|
+
/* Do not accept any data while in shutdown state */
|
122
|
+
if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
|
123
|
+
return s;
|
124
|
+
}
|
125
|
+
|
126
|
+
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
|
127
|
+
|
128
|
+
/* Cork this socket */
|
129
|
+
((AsyncSocket<SSL> *) s)->cork();
|
130
|
+
|
131
|
+
/* Mark that we are inside the parser now */
|
132
|
+
httpContextData->isParsingHttp = true;
|
133
|
+
|
134
|
+
// clients need to know the cursor after http parse, not servers!
|
135
|
+
// how far did we read then? we need to know to continue with websocket parsing data? or?
|
136
|
+
|
137
|
+
void *proxyParser = nullptr;
|
138
|
+
#ifdef UWS_WITH_PROXY
|
139
|
+
proxyParser = &httpResponseData->proxyParser;
|
140
|
+
#endif
|
141
|
+
|
142
|
+
/* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */
|
143
|
+
auto [err, returnedSocket] = httpResponseData->consumePostPadded(data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
|
144
|
+
/* For every request we reset the timeout and hang until user makes action */
|
145
|
+
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
|
146
|
+
us_socket_timeout(SSL, (us_socket_t *) s, 0);
|
147
|
+
|
148
|
+
/* Reset httpResponse */
|
149
|
+
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, (us_socket_t *) s);
|
150
|
+
httpResponseData->offset = 0;
|
151
|
+
|
152
|
+
/* Are we not ready for another request yet? Terminate the connection. */
|
153
|
+
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) {
|
154
|
+
us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
|
155
|
+
return nullptr;
|
156
|
+
}
|
157
|
+
|
158
|
+
/* Mark pending request and emit it */
|
159
|
+
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
|
160
|
+
|
161
|
+
/* Mark this response as connectionClose if ancient or connection: close */
|
162
|
+
if (httpRequest->isAncient() || httpRequest->getHeader("connection").length() == 5) {
|
163
|
+
httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
|
164
|
+
}
|
165
|
+
|
166
|
+
/* Select the router based on SNI (only possible for SSL) */
|
167
|
+
auto *selectedRouter = &httpContextData->router;
|
168
|
+
if constexpr (SSL) {
|
169
|
+
void *domainRouter = us_socket_server_name_userdata(SSL, (struct us_socket_t *) s);
|
170
|
+
if (domainRouter) {
|
171
|
+
selectedRouter = (decltype(selectedRouter)) domainRouter;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
/* Route the method and URL */
|
176
|
+
selectedRouter->getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
|
177
|
+
if (!selectedRouter->route(httpRequest->getCaseSensitiveMethod(), httpRequest->getUrl())) {
|
178
|
+
/* We have to force close this socket as we have no handler for it */
|
179
|
+
us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
|
180
|
+
return nullptr;
|
181
|
+
}
|
182
|
+
|
183
|
+
/* First of all we need to check if this socket was deleted due to upgrade */
|
184
|
+
if (httpContextData->upgradedWebSocket) {
|
185
|
+
/* We differ between closed and upgraded below */
|
186
|
+
return nullptr;
|
187
|
+
}
|
188
|
+
|
189
|
+
/* Was the socket closed? */
|
190
|
+
if (us_socket_is_closed(SSL, (struct us_socket_t *) s)) {
|
191
|
+
return nullptr;
|
192
|
+
}
|
193
|
+
|
194
|
+
/* We absolutely have to terminate parsing if shutdown */
|
195
|
+
if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
|
196
|
+
return nullptr;
|
197
|
+
}
|
198
|
+
|
199
|
+
/* Returning from a request handler without responding or attaching an onAborted handler is ill-use */
|
200
|
+
if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted) {
|
201
|
+
/* Throw exception here? */
|
202
|
+
std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl;
|
203
|
+
std::terminate();
|
204
|
+
}
|
205
|
+
|
206
|
+
/* If we have not responded and we have a data handler, we need to timeout to enfore client sending the data */
|
207
|
+
if (!((HttpResponse<SSL> *) s)->hasResponded() && httpResponseData->inStream) {
|
208
|
+
us_socket_timeout(SSL, (us_socket_t *) s, HTTP_IDLE_TIMEOUT_S);
|
209
|
+
}
|
210
|
+
|
211
|
+
/* Continue parsing */
|
212
|
+
return s;
|
213
|
+
|
214
|
+
}, [httpResponseData](void *user, std::string_view data, bool fin) -> void * {
|
215
|
+
/* We always get an empty chunk even if there is no data */
|
216
|
+
if (httpResponseData->inStream) {
|
217
|
+
|
218
|
+
/* Todo: can this handle timeout for non-post as well? */
|
219
|
+
if (fin) {
|
220
|
+
/* If we just got the last chunk (or empty chunk), disable timeout */
|
221
|
+
us_socket_timeout(SSL, (struct us_socket_t *) user, 0);
|
222
|
+
} else {
|
223
|
+
/* We still have some more data coming in later, so reset timeout */
|
224
|
+
/* Only reset timeout if we got enough bytes (16kb/sec) since last time we reset here */
|
225
|
+
httpResponseData->received_bytes_per_timeout += (unsigned int) data.length();
|
226
|
+
if (httpResponseData->received_bytes_per_timeout >= HTTP_RECEIVE_THROUGHPUT_BYTES * HTTP_IDLE_TIMEOUT_S) {
|
227
|
+
us_socket_timeout(SSL, (struct us_socket_t *) user, HTTP_IDLE_TIMEOUT_S);
|
228
|
+
httpResponseData->received_bytes_per_timeout = 0;
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
/* We might respond in the handler, so do not change timeout after this */
|
233
|
+
httpResponseData->inStream(data, fin);
|
234
|
+
|
235
|
+
/* Was the socket closed? */
|
236
|
+
if (us_socket_is_closed(SSL, (struct us_socket_t *) user)) {
|
237
|
+
return nullptr;
|
238
|
+
}
|
239
|
+
|
240
|
+
/* We absolutely have to terminate parsing if shutdown */
|
241
|
+
if (us_socket_is_shut_down(SSL, (us_socket_t *) user)) {
|
242
|
+
return nullptr;
|
243
|
+
}
|
244
|
+
|
245
|
+
/* If we were given the last data chunk, reset data handler to ensure following
|
246
|
+
* requests on the same socket won't trigger any previously registered behavior */
|
247
|
+
if (fin) {
|
248
|
+
httpResponseData->inStream = nullptr;
|
249
|
+
}
|
250
|
+
}
|
251
|
+
return user;
|
252
|
+
});
|
253
|
+
|
254
|
+
/* Mark that we are no longer parsing Http */
|
255
|
+
httpContextData->isParsingHttp = false;
|
256
|
+
|
257
|
+
/* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */
|
258
|
+
if (returnedSocket == FULLPTR) {
|
259
|
+
/* For errors, we only deliver them "at most once". We don't care if they get halfways delivered or not. */
|
260
|
+
us_socket_write(SSL, s, httpErrorResponses[err].data(), (int) httpErrorResponses[err].length(), false);
|
261
|
+
us_socket_shutdown(SSL, s);
|
262
|
+
/* Close any socket on HTTP errors */
|
263
|
+
us_socket_close(SSL, s, 0, nullptr);
|
264
|
+
/* This just makes the following code act as if the socket was closed from error inside the parser. */
|
265
|
+
returnedSocket = nullptr;
|
266
|
+
}
|
267
|
+
|
268
|
+
/* We need to uncork in all cases, except for nullptr (closed socket, or upgraded socket) */
|
269
|
+
if (returnedSocket != nullptr) {
|
270
|
+
/* Timeout on uncork failure */
|
271
|
+
auto [written, failed] = ((AsyncSocket<SSL> *) returnedSocket)->uncork();
|
272
|
+
if (failed) {
|
273
|
+
/* All Http sockets timeout by this, and this behavior match the one in HttpResponse::cork */
|
274
|
+
/* Warning: both HTTP_IDLE_TIMEOUT_S and HTTP_TIMEOUT_S are 10 seconds and both are used the same */
|
275
|
+
((AsyncSocket<SSL> *) s)->timeout(HTTP_IDLE_TIMEOUT_S);
|
276
|
+
}
|
277
|
+
|
278
|
+
/* We need to check if we should close this socket here now */
|
279
|
+
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
|
280
|
+
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
|
281
|
+
if (((AsyncSocket<SSL> *) s)->getBufferedAmount() == 0) {
|
282
|
+
((AsyncSocket<SSL> *) s)->shutdown();
|
283
|
+
/* We need to force close after sending FIN since we want to hinder
|
284
|
+
* clients from keeping to send their huge data */
|
285
|
+
((AsyncSocket<SSL> *) s)->close();
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
return (us_socket_t *) returnedSocket;
|
291
|
+
}
|
292
|
+
|
293
|
+
/* If we upgraded, check here (differ between nullptr close and nullptr upgrade) */
|
294
|
+
if (httpContextData->upgradedWebSocket) {
|
295
|
+
/* This path is only for upgraded websockets */
|
296
|
+
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) httpContextData->upgradedWebSocket;
|
297
|
+
|
298
|
+
/* Uncork here as well (note: what if we failed to uncork and we then pub/sub before we even upgraded?) */
|
299
|
+
auto [written, failed] = asyncSocket->uncork();
|
300
|
+
|
301
|
+
/* If we succeeded in uncorking, check if we have sent WebSocket FIN */
|
302
|
+
if (!failed) {
|
303
|
+
WebSocketData *webSocketData = (WebSocketData *) asyncSocket->getAsyncSocketData();
|
304
|
+
if (webSocketData->isShuttingDown) {
|
305
|
+
/* In that case, also send TCP FIN (this is similar to what we have in ws drain handler) */
|
306
|
+
asyncSocket->shutdown();
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
/* Reset upgradedWebSocket before we return */
|
311
|
+
httpContextData->upgradedWebSocket = nullptr;
|
312
|
+
|
313
|
+
/* Return the new upgraded websocket */
|
314
|
+
return (us_socket_t *) asyncSocket;
|
315
|
+
}
|
316
|
+
|
317
|
+
/* It is okay to uncork a closed socket and we need to */
|
318
|
+
((AsyncSocket<SSL> *) s)->uncork();
|
319
|
+
|
320
|
+
/* We cannot return nullptr to the underlying stack in any case */
|
321
|
+
return s;
|
322
|
+
});
|
323
|
+
|
324
|
+
/* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
|
325
|
+
us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) {
|
326
|
+
|
327
|
+
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
328
|
+
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData();
|
329
|
+
|
330
|
+
/* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */
|
331
|
+
if (httpResponseData->onWritable) {
|
332
|
+
/* 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 */
|
333
|
+
us_socket_timeout(SSL, s, 0);
|
334
|
+
|
335
|
+
/* We expect the developer to return whether or not write was successful (true).
|
336
|
+
* If write was never called, the developer should still return true so that we may drain. */
|
337
|
+
bool success = httpResponseData->callOnWritable(httpResponseData->offset);
|
338
|
+
|
339
|
+
/* The developer indicated that their onWritable failed. */
|
340
|
+
if (!success) {
|
341
|
+
/* Skip testing if we can drain anything since that might perform an extra syscall */
|
342
|
+
return s;
|
343
|
+
}
|
344
|
+
|
345
|
+
/* We don't want to fall through since we don't want to mess with timeout.
|
346
|
+
* It makes little sense to drain any backpressure when the user has registered onWritable. */
|
347
|
+
return s;
|
348
|
+
}
|
349
|
+
|
350
|
+
/* Drain any socket buffer, this might empty our backpressure and thus finish the request */
|
351
|
+
/*auto [written, failed] = */asyncSocket->write(nullptr, 0, true, 0);
|
352
|
+
|
353
|
+
/* Should we close this connection after a response - and is this response really done? */
|
354
|
+
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
|
355
|
+
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
|
356
|
+
if (asyncSocket->getBufferedAmount() == 0) {
|
357
|
+
asyncSocket->shutdown();
|
358
|
+
/* We need to force close after sending FIN since we want to hinder
|
359
|
+
* clients from keeping to send their huge data */
|
360
|
+
asyncSocket->close();
|
361
|
+
}
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
/* Expect another writable event, or another request within the timeout */
|
366
|
+
asyncSocket->timeout(HTTP_IDLE_TIMEOUT_S);
|
367
|
+
|
368
|
+
return s;
|
369
|
+
});
|
370
|
+
|
371
|
+
/* Handle FIN, HTTP does not support half-closed sockets, so simply close */
|
372
|
+
us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) {
|
373
|
+
|
374
|
+
/* We do not care for half closed sockets */
|
375
|
+
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
376
|
+
return asyncSocket->close();
|
377
|
+
|
378
|
+
});
|
379
|
+
|
380
|
+
/* Handle socket timeouts, simply close them so to not confuse client with FIN */
|
381
|
+
us_socket_context_on_timeout(SSL, getSocketContext(), [](us_socket_t *s) {
|
382
|
+
|
383
|
+
/* Force close rather than gracefully shutdown and risk confusing the client with a complete download */
|
384
|
+
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
385
|
+
return asyncSocket->close();
|
386
|
+
|
387
|
+
});
|
388
|
+
|
389
|
+
return this;
|
390
|
+
}
|
391
|
+
|
392
|
+
public:
|
393
|
+
/* Construct a new HttpContext using specified loop */
|
394
|
+
static HttpContext *create(Loop *loop, us_socket_context_options_t options = {}) {
|
395
|
+
HttpContext *httpContext;
|
396
|
+
|
397
|
+
httpContext = (HttpContext *) us_create_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options);
|
398
|
+
|
399
|
+
if (!httpContext) {
|
400
|
+
return nullptr;
|
401
|
+
}
|
402
|
+
|
403
|
+
/* Init socket context data */
|
404
|
+
new ((HttpContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) httpContext)) HttpContextData<SSL>();
|
405
|
+
return httpContext->init();
|
406
|
+
}
|
407
|
+
|
408
|
+
/* Destruct the HttpContext, it does not follow RAII */
|
409
|
+
void free() {
|
410
|
+
/* Destruct socket context data */
|
411
|
+
HttpContextData<SSL> *httpContextData = getSocketContextData();
|
412
|
+
httpContextData->~HttpContextData<SSL>();
|
413
|
+
|
414
|
+
/* Free the socket context in whole */
|
415
|
+
us_socket_context_free(SSL, getSocketContext());
|
416
|
+
}
|
417
|
+
|
418
|
+
void filter(MoveOnlyFunction<void(HttpResponse<SSL> *, int)> &&filterHandler) {
|
419
|
+
getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler));
|
420
|
+
}
|
421
|
+
|
422
|
+
/* Register an HTTP route handler acording to URL pattern */
|
423
|
+
void onHttp(std::string method, std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
|
424
|
+
HttpContextData<SSL> *httpContextData = getSocketContextData();
|
425
|
+
|
426
|
+
/* Todo: This is ugly, fix */
|
427
|
+
std::vector<std::string> methods;
|
428
|
+
if (method == "*") {
|
429
|
+
methods = {"*"};
|
430
|
+
} else {
|
431
|
+
methods = {method};
|
432
|
+
}
|
433
|
+
|
434
|
+
uint32_t priority = method == "*" ? httpContextData->currentRouter->LOW_PRIORITY : (upgrade ? httpContextData->currentRouter->HIGH_PRIORITY : httpContextData->currentRouter->MEDIUM_PRIORITY);
|
435
|
+
|
436
|
+
/* If we are passed nullptr then remove this */
|
437
|
+
if (!handler) {
|
438
|
+
httpContextData->currentRouter->remove(methods[0], pattern, priority);
|
439
|
+
return;
|
440
|
+
}
|
441
|
+
|
442
|
+
/* Record this route's parameter offsets */
|
443
|
+
std::map<std::string, unsigned short, std::less<>> parameterOffsets;
|
444
|
+
unsigned short offset = 0;
|
445
|
+
for (unsigned int i = 0; i < pattern.length(); i++) {
|
446
|
+
if (pattern[i] == ':') {
|
447
|
+
i++;
|
448
|
+
unsigned int start = i;
|
449
|
+
while (i < pattern.length() && pattern[i] != '/') {
|
450
|
+
i++;
|
451
|
+
}
|
452
|
+
parameterOffsets[std::string(pattern.data() + start, i - start)] = offset;
|
453
|
+
//std::cout << "<" << std::string(pattern.data() + start, i - start) << "> is offset " << offset;
|
454
|
+
offset++;
|
455
|
+
}
|
456
|
+
}
|
457
|
+
|
458
|
+
httpContextData->currentRouter->add(methods, pattern, [handler = std::move(handler), parameterOffsets = std::move(parameterOffsets)](auto *r) mutable {
|
459
|
+
auto user = r->getUserData();
|
460
|
+
user.httpRequest->setYield(false);
|
461
|
+
user.httpRequest->setParameters(r->getParameters());
|
462
|
+
user.httpRequest->setParameterOffsets(¶meterOffsets);
|
463
|
+
|
464
|
+
/* Middleware? Automatically respond to expectations */
|
465
|
+
std::string_view expect = user.httpRequest->getHeader("expect");
|
466
|
+
if (expect.length() && expect == "100-continue") {
|
467
|
+
user.httpResponse->writeContinue();
|
468
|
+
}
|
469
|
+
|
470
|
+
handler(user.httpResponse, user.httpRequest);
|
471
|
+
|
472
|
+
/* If any handler yielded, the router will keep looking for a suitable handler. */
|
473
|
+
if (user.httpRequest->getYield()) {
|
474
|
+
return false;
|
475
|
+
}
|
476
|
+
return true;
|
477
|
+
}, priority);
|
478
|
+
}
|
479
|
+
|
480
|
+
/* Listen to port using this HttpContext */
|
481
|
+
us_listen_socket_t *listen(const char *host, int port, int options) {
|
482
|
+
return us_socket_context_listen(SSL, getSocketContext(), host, port, options, sizeof(HttpResponseData<SSL>));
|
483
|
+
}
|
484
|
+
|
485
|
+
/* Listen to unix domain socket using this HttpContext */
|
486
|
+
us_listen_socket_t *listen(const char *path, int options) {
|
487
|
+
return us_socket_context_listen_unix(SSL, getSocketContext(), path, options, sizeof(HttpResponseData<SSL>));
|
488
|
+
}
|
489
|
+
|
490
|
+
void onPreOpen(LIBUS_SOCKET_DESCRIPTOR (*handler)(LIBUS_SOCKET_DESCRIPTOR)) {
|
491
|
+
us_socket_context_on_pre_open(SSL, getSocketContext(), handler);
|
492
|
+
}
|
493
|
+
|
494
|
+
/* Adopt an externally accepted socket into this HttpContext */
|
495
|
+
us_socket_t *adoptAcceptedSocket(LIBUS_SOCKET_DESCRIPTOR accepted_fd) {
|
496
|
+
return us_adopt_accepted_socket(SSL, getSocketContext(), accepted_fd, sizeof(HttpResponseData<SSL>), 0, 0);
|
497
|
+
}
|
498
|
+
};
|
499
|
+
|
500
|
+
}
|
501
|
+
|
502
|
+
#endif // UWS_HTTPCONTEXT_H
|
@@ -0,0 +1,56 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2020.
|
3
|
+
* Intellectual property of third-party.
|
4
|
+
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
* See the License for the specific language governing permissions and
|
15
|
+
* limitations under the License.
|
16
|
+
*/
|
17
|
+
|
18
|
+
#ifndef UWS_HTTPCONTEXTDATA_H
|
19
|
+
#define UWS_HTTPCONTEXTDATA_H
|
20
|
+
|
21
|
+
#include "HttpRouter.h"
|
22
|
+
|
23
|
+
#include <vector>
|
24
|
+
#include "MoveOnlyFunction.h"
|
25
|
+
|
26
|
+
namespace uWS {
|
27
|
+
template<bool> struct HttpResponse;
|
28
|
+
struct HttpRequest;
|
29
|
+
|
30
|
+
template <bool SSL>
|
31
|
+
struct alignas(16) HttpContextData {
|
32
|
+
template <bool> friend struct HttpContext;
|
33
|
+
template <bool> friend struct HttpResponse;
|
34
|
+
template <bool> friend struct TemplatedApp;
|
35
|
+
private:
|
36
|
+
std::vector<MoveOnlyFunction<void(HttpResponse<SSL> *, int)>> filterHandlers;
|
37
|
+
|
38
|
+
MoveOnlyFunction<void(const char *hostname)> missingServerNameHandler;
|
39
|
+
|
40
|
+
struct RouterData {
|
41
|
+
HttpResponse<SSL> *httpResponse;
|
42
|
+
HttpRequest *httpRequest;
|
43
|
+
};
|
44
|
+
|
45
|
+
/* This is the currently browsed-to router when using SNI */
|
46
|
+
HttpRouter<RouterData> *currentRouter = &router;
|
47
|
+
|
48
|
+
/* This is the default router for default SNI or non-SSL */
|
49
|
+
HttpRouter<RouterData> router;
|
50
|
+
void *upgradedWebSocket = nullptr;
|
51
|
+
bool isParsingHttp = false;
|
52
|
+
};
|
53
|
+
|
54
|
+
}
|
55
|
+
|
56
|
+
#endif // UWS_HTTPCONTEXTDATA_H
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2023.
|
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_HTTP_ERRORS
|
19
|
+
#define UWS_HTTP_ERRORS
|
20
|
+
|
21
|
+
#include <string_view>
|
22
|
+
|
23
|
+
namespace uWS {
|
24
|
+
/* Possible errors from http parsing */
|
25
|
+
enum HttpError {
|
26
|
+
HTTP_ERROR_505_HTTP_VERSION_NOT_SUPPORTED = 1,
|
27
|
+
HTTP_ERROR_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 2,
|
28
|
+
HTTP_ERROR_400_BAD_REQUEST = 3
|
29
|
+
};
|
30
|
+
|
31
|
+
#ifndef UWS_HTTPRESPONSE_NO_WRITEMARK
|
32
|
+
|
33
|
+
/* Returned parser errors match this LUT. */
|
34
|
+
static const std::string_view httpErrorResponses[] = {
|
35
|
+
"", /* Zeroth place is no error so don't use it */
|
36
|
+
"HTTP/1.1 505 HTTP Version Not Supported\r\nConnection: close\r\n\r\n<h1>HTTP Version Not Supported</h1><p>This server does not support HTTP/1.0.</p><hr><i>uWebSockets/20 Server</i>",
|
37
|
+
"HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n<h1>Request Header Fields Too Large</h1><hr><i>uWebSockets/20 Server</i>",
|
38
|
+
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n<h1>Bad Request</h1><hr><i>uWebSockets/20 Server</i>",
|
39
|
+
};
|
40
|
+
|
41
|
+
#else
|
42
|
+
/* Anonymized pages */
|
43
|
+
static const std::string_view httpErrorResponses[] = {
|
44
|
+
"", /* Zeroth place is no error so don't use it */
|
45
|
+
"HTTP/1.1 505 HTTP Version Not Supported\r\nConnection: close\r\n\r\n",
|
46
|
+
"HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n",
|
47
|
+
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"
|
48
|
+
};
|
49
|
+
#endif
|
50
|
+
|
51
|
+
}
|
52
|
+
|
53
|
+
#endif
|