opal-up 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +81 -28
  4. data/bin/up_ruby +4 -0
  5. data/bin/up_ruby_cluster +4 -0
  6. data/ext/up_ext/App.h +606 -0
  7. data/ext/up_ext/AsyncSocket.h +355 -0
  8. data/ext/up_ext/AsyncSocketData.h +87 -0
  9. data/ext/up_ext/BloomFilter.h +83 -0
  10. data/ext/up_ext/ChunkedEncoding.h +236 -0
  11. data/ext/up_ext/ClientApp.h +36 -0
  12. data/ext/up_ext/HttpContext.h +502 -0
  13. data/ext/up_ext/HttpContextData.h +56 -0
  14. data/ext/up_ext/HttpErrors.h +53 -0
  15. data/ext/up_ext/HttpParser.h +680 -0
  16. data/ext/up_ext/HttpResponse.h +578 -0
  17. data/ext/up_ext/HttpResponseData.h +95 -0
  18. data/ext/up_ext/HttpRouter.h +380 -0
  19. data/ext/up_ext/Loop.h +204 -0
  20. data/ext/up_ext/LoopData.h +112 -0
  21. data/ext/up_ext/MoveOnlyFunction.h +377 -0
  22. data/ext/up_ext/PerMessageDeflate.h +315 -0
  23. data/ext/up_ext/ProxyParser.h +163 -0
  24. data/ext/up_ext/QueryParser.h +120 -0
  25. data/ext/up_ext/TopicTree.h +363 -0
  26. data/ext/up_ext/Utilities.h +66 -0
  27. data/ext/up_ext/WebSocket.h +381 -0
  28. data/ext/up_ext/WebSocketContext.h +434 -0
  29. data/ext/up_ext/WebSocketContextData.h +109 -0
  30. data/ext/up_ext/WebSocketData.h +86 -0
  31. data/ext/up_ext/WebSocketExtensions.h +256 -0
  32. data/ext/up_ext/WebSocketHandshake.h +145 -0
  33. data/ext/up_ext/WebSocketProtocol.h +506 -0
  34. data/ext/up_ext/bsd.c +767 -0
  35. data/ext/up_ext/bsd.h +109 -0
  36. data/ext/up_ext/context.c +524 -0
  37. data/ext/up_ext/epoll_kqueue.c +458 -0
  38. data/ext/up_ext/epoll_kqueue.h +67 -0
  39. data/ext/up_ext/extconf.rb +5 -0
  40. data/ext/up_ext/internal.h +224 -0
  41. data/ext/up_ext/libusockets.h +350 -0
  42. data/ext/up_ext/libuwebsockets.cpp +1374 -0
  43. data/ext/up_ext/libuwebsockets.h +260 -0
  44. data/ext/up_ext/loop.c +386 -0
  45. data/ext/up_ext/loop_data.h +38 -0
  46. data/ext/up_ext/socket.c +231 -0
  47. data/ext/up_ext/up_ext.c +278 -0
  48. data/lib/up/node/rack_env.rb +2 -2
  49. data/lib/up/ruby/cluster_cli.rb +10 -0
  50. data/lib/up/ruby/rack_cluster.rb +26 -0
  51. data/lib/up/ruby/rack_env.rb +97 -0
  52. data/lib/up/ruby/rack_server.rb +26 -0
  53. data/lib/up/ruby/server_cli.rb +10 -0
  54. data/lib/up/u_web_socket/rack_env.rb +1 -1
  55. data/lib/up/version.rb +1 -1
  56. metadata +71 -18
  57. data/.gitignore +0 -5
  58. data/Gemfile +0 -2
  59. data/example_rack_app/Gemfile +0 -3
  60. data/example_rack_app/config.ru +0 -6
  61. data/example_rack_app/rack_app.rb +0 -5
  62. data/example_roda_app/Gemfile +0 -6
  63. data/example_roda_app/config.ru +0 -6
  64. data/example_roda_app/roda_app.rb +0 -37
  65. data/example_sinatra_app/Gemfile +0 -6
  66. data/example_sinatra_app/config.ru +0 -6
  67. data/example_sinatra_app/sinatra_app.rb +0 -7
  68. data/opal-up.gemspec +0 -27
  69. data/up_logo.svg +0 -256
@@ -0,0 +1,578 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #ifndef UWS_HTTPRESPONSE_H
19
+ #define UWS_HTTPRESPONSE_H
20
+
21
+ /* An HttpResponse is the channel on which you send back a response */
22
+
23
+ #include "AsyncSocket.h"
24
+ #include "HttpResponseData.h"
25
+ #include "HttpContext.h"
26
+ #include "HttpContextData.h"
27
+ #include "Utilities.h"
28
+
29
+ #include "WebSocketExtensions.h"
30
+ #include "WebSocketHandshake.h"
31
+ #include "WebSocket.h"
32
+ #include "WebSocketContextData.h"
33
+
34
+ #include "MoveOnlyFunction.h"
35
+
36
+ /* todo: tryWrite is missing currently, only send smaller segments with write */
37
+
38
+ namespace uWS {
39
+
40
+ /* Some pre-defined status constants to use with writeStatus */
41
+ static const char *HTTP_200_OK = "200 OK";
42
+
43
+ /* The general timeout for HTTP sockets */
44
+ static const int HTTP_TIMEOUT_S = 10;
45
+
46
+ template <bool SSL>
47
+ struct HttpResponse : public AsyncSocket<SSL> {
48
+ /* Solely used for getHttpResponseData() */
49
+ template <bool> friend struct TemplatedApp;
50
+ typedef AsyncSocket<SSL> Super;
51
+ private:
52
+ HttpResponseData<SSL> *getHttpResponseData() {
53
+ return (HttpResponseData<SSL> *) Super::getAsyncSocketData();
54
+ }
55
+
56
+ /* Write an unsigned 32-bit integer in hex */
57
+ void writeUnsignedHex(unsigned int value) {
58
+ /* Buf really only needs to be 8 long but building with
59
+ * -mavx2, GCC still wants to overstep it so made it 16 */
60
+ char buf[16];
61
+ int length = utils::u32toaHex(value, buf);
62
+
63
+ /* For now we do this copy */
64
+ Super::write(buf, length);
65
+ }
66
+
67
+ /* Write an unsigned 64-bit integer */
68
+ void writeUnsigned64(uint64_t value) {
69
+ char buf[20];
70
+ int length = utils::u64toa(value, buf);
71
+
72
+ /* For now we do this copy */
73
+ Super::write(buf, length);
74
+ }
75
+
76
+ /* Called only once per request */
77
+ void writeMark() {
78
+ /* Date is always written */
79
+ writeHeader("Date", std::string_view(((LoopData *) us_loop_ext(us_socket_context_loop(SSL, (us_socket_context(SSL, (us_socket_t *) this)))))->date, 29));
80
+
81
+ /* You can disable this altogether */
82
+ #ifndef UWS_HTTPRESPONSE_NO_WRITEMARK
83
+ if (!Super::getLoopData()->noMark) {
84
+ /* We only expose major version */
85
+ writeHeader("uWebSockets", "20");
86
+ }
87
+ #endif
88
+ }
89
+
90
+ /* Returns true on success, indicating that it might be feasible to write more data.
91
+ * Will start timeout if stream reaches totalSize or write failure. */
92
+ bool internalEnd(std::string_view data, uintmax_t totalSize, bool optional, bool allowContentLength = true, bool closeConnection = false) {
93
+ /* Write status if not already done */
94
+ writeStatus(HTTP_200_OK);
95
+
96
+ /* If no total size given then assume this chunk is everything */
97
+ if (!totalSize) {
98
+ totalSize = data.length();
99
+ }
100
+
101
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
102
+
103
+ /* In some cases, such as when refusing huge data we want to close the connection when drained */
104
+ if (closeConnection) {
105
+
106
+ /* HTTP 1.1 must send this back unless the client already sent it to us.
107
+ * It is a connection close when either of the two parties say so but the
108
+ * one party must tell the other one so.
109
+ *
110
+ * This check also serves to limit writing the header only once. */
111
+ if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) == 0) {
112
+ writeHeader("Connection", "close");
113
+ }
114
+
115
+ httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
116
+ }
117
+
118
+ if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) {
119
+
120
+ /* We do not have tryWrite-like functionalities, so ignore optional in this path */
121
+
122
+ /* Do not allow sending 0 chunk here */
123
+ if (data.length()) {
124
+ Super::write("\r\n", 2);
125
+ writeUnsignedHex((unsigned int) data.length());
126
+ Super::write("\r\n", 2);
127
+
128
+ /* Ignoring optional for now */
129
+ Super::write(data.data(), (int) data.length());
130
+ }
131
+
132
+ /* Terminating 0 chunk */
133
+ Super::write("\r\n0\r\n\r\n", 7);
134
+
135
+ httpResponseData->markDone();
136
+
137
+ /* We need to check if we should close this socket here now */
138
+ if (!Super::isCorked()) {
139
+ if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
140
+ if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
141
+ if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
142
+ ((AsyncSocket<SSL> *) this)->shutdown();
143
+ /* We need to force close after sending FIN since we want to hinder
144
+ * clients from keeping to send their huge data */
145
+ ((AsyncSocket<SSL> *) this)->close();
146
+ return true;
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ /* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */
153
+ Super::timeout(HTTP_TIMEOUT_S);
154
+ return true;
155
+ } else {
156
+ /* Write content-length on first call */
157
+ if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) {
158
+ /* Write mark, this propagates to WebSockets too */
159
+ writeMark();
160
+
161
+ /* WebSocket upgrades does not allow content-length */
162
+ if (allowContentLength) {
163
+ /* Even zero is a valid content-length */
164
+ Super::write("Content-Length: ", 16);
165
+ writeUnsigned64(totalSize);
166
+ Super::write("\r\n\r\n", 4);
167
+ } else {
168
+ Super::write("\r\n", 2);
169
+ }
170
+
171
+ /* Mark end called */
172
+ httpResponseData->state |= HttpResponseData<SSL>::HTTP_END_CALLED;
173
+ }
174
+
175
+ /* Even if we supply no new data to write, its failed boolean is useful to know
176
+ * if it failed to drain any prior failed header writes */
177
+
178
+ /* Write as much as possible without causing backpressure */
179
+ size_t written = 0;
180
+ bool failed = false;
181
+ while (written < data.length() && !failed) {
182
+ /* uSockets only deals with int sizes, so pass chunks of max signed int size */
183
+ auto writtenFailed = Super::write(data.data() + written, (int) std::min<size_t>(data.length() - written, INT_MAX), optional);
184
+
185
+ written += (size_t) writtenFailed.first;
186
+ failed = writtenFailed.second;
187
+ }
188
+
189
+ httpResponseData->offset += written;
190
+
191
+ /* Success is when we wrote the entire thing without any failures */
192
+ bool success = written == data.length() && !failed;
193
+
194
+ /* If we are now at the end, start a timeout. Also start a timeout if we failed. */
195
+ if (!success || httpResponseData->offset == totalSize) {
196
+ Super::timeout(HTTP_TIMEOUT_S);
197
+ }
198
+
199
+ /* Remove onAborted function if we reach the end */
200
+ if (httpResponseData->offset == totalSize) {
201
+ httpResponseData->markDone();
202
+
203
+ /* We need to check if we should close this socket here now */
204
+ if (!Super::isCorked()) {
205
+ if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
206
+ if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
207
+ if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
208
+ ((AsyncSocket<SSL> *) this)->shutdown();
209
+ /* We need to force close after sending FIN since we want to hinder
210
+ * clients from keeping to send their huge data */
211
+ ((AsyncSocket<SSL> *) this)->close();
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ return success;
219
+ }
220
+ }
221
+
222
+ public:
223
+ /* If we have proxy support; returns the proxed source address as reported by the proxy. */
224
+ #ifdef UWS_WITH_PROXY
225
+ std::string_view getProxiedRemoteAddress() {
226
+ return getHttpResponseData()->proxyParser.getSourceAddress();
227
+ }
228
+
229
+ std::string_view getProxiedRemoteAddressAsText() {
230
+ return Super::addressAsText(getProxiedRemoteAddress());
231
+ }
232
+ #endif
233
+
234
+ /* Manually upgrade to WebSocket. Typically called in upgrade handler. Immediately calls open handler.
235
+ * NOTE: Will invalidate 'this' as socket might change location in memory. Throw away after use. */
236
+ template <typename UserData>
237
+ void upgrade(UserData &&userData, std::string_view secWebSocketKey, std::string_view secWebSocketProtocol,
238
+ std::string_view secWebSocketExtensions,
239
+ struct us_socket_context_t *webSocketContext) {
240
+
241
+ /* Extract needed parameters from WebSocketContextData */
242
+ WebSocketContextData<SSL, UserData> *webSocketContextData = (WebSocketContextData<SSL, UserData> *) us_socket_context_ext(SSL, webSocketContext);
243
+
244
+ /* Note: OpenSSL can be used here to speed this up somewhat */
245
+ char secWebSocketAccept[29] = {};
246
+ WebSocketHandshake::generate(secWebSocketKey.data(), secWebSocketAccept);
247
+
248
+ writeStatus("101 Switching Protocols")
249
+ ->writeHeader("Upgrade", "websocket")
250
+ ->writeHeader("Connection", "Upgrade")
251
+ ->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept);
252
+
253
+ /* Select first subprotocol if present */
254
+ if (secWebSocketProtocol.length()) {
255
+ writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(',')));
256
+ }
257
+
258
+ /* Negotiate compression */
259
+ bool perMessageDeflate = false;
260
+ CompressOptions compressOptions = CompressOptions::DISABLED;
261
+ if (secWebSocketExtensions.length() && webSocketContextData->compression != DISABLED) {
262
+
263
+ /* Make sure to map SHARED_DECOMPRESSOR to windowBits = 0, not 1 */
264
+ int wantedInflationWindow = 0;
265
+ if ((webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) != CompressOptions::SHARED_DECOMPRESSOR) {
266
+ wantedInflationWindow = (webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) >> 8;
267
+ }
268
+
269
+ /* Map from selected compressor (this automatically maps SHARED_COMPRESSOR to windowBits 0, not 1) */
270
+ int wantedCompressionWindow = (webSocketContextData->compression & CompressOptions::_COMPRESSOR_MASK) >> 4;
271
+
272
+ auto [negCompression, negCompressionWindow, negInflationWindow, negResponse] =
273
+ negotiateCompression(true, wantedCompressionWindow, wantedInflationWindow,
274
+ secWebSocketExtensions);
275
+
276
+ if (negCompression) {
277
+ perMessageDeflate = true;
278
+
279
+ /* Map from negotiated windowBits to compressor and decompressor */
280
+ if (negCompressionWindow == 0) {
281
+ compressOptions = CompressOptions::SHARED_COMPRESSOR;
282
+ } else {
283
+ compressOptions = (CompressOptions) ((uint32_t) (negCompressionWindow << 4)
284
+ | (uint32_t) (negCompressionWindow - 7));
285
+
286
+ /* If we are dedicated and have the 3kb then correct any 4kb to 3kb,
287
+ * (they both share the windowBits = 9) */
288
+ if (webSocketContextData->compression & DEDICATED_COMPRESSOR_3KB) {
289
+ compressOptions = DEDICATED_COMPRESSOR_3KB;
290
+ }
291
+ }
292
+
293
+ /* Here we modify the above compression with negotiated decompressor */
294
+ if (negInflationWindow == 0) {
295
+ compressOptions = CompressOptions(compressOptions | CompressOptions::SHARED_DECOMPRESSOR);
296
+ } else {
297
+ compressOptions = CompressOptions(compressOptions | (negInflationWindow << 8));
298
+ }
299
+
300
+ writeHeader("Sec-WebSocket-Extensions", negResponse);
301
+ }
302
+ }
303
+
304
+ internalEnd({nullptr, 0}, 0, false, false);
305
+
306
+ /* Grab the httpContext from res */
307
+ HttpContext<SSL> *httpContext = (HttpContext<SSL> *) us_socket_context(SSL, (struct us_socket_t *) this);
308
+
309
+ /* Move any backpressure out of HttpResponse */
310
+ BackPressure backpressure(std::move(((AsyncSocketData<SSL> *) getHttpResponseData())->buffer));
311
+
312
+ /* Destroy HttpResponseData */
313
+ getHttpResponseData()->~HttpResponseData();
314
+
315
+ /* Before we adopt and potentially change socket, check if we are corked */
316
+ bool wasCorked = Super::isCorked();
317
+
318
+ /* Adopting a socket invalidates it, do not rely on it directly to carry any data */
319
+ WebSocket<SSL, true, UserData> *webSocket = (WebSocket<SSL, true, UserData> *) us_socket_context_adopt_socket(SSL,
320
+ (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(WebSocketData) + sizeof(UserData));
321
+
322
+ /* For whatever reason we were corked, update cork to the new socket */
323
+ if (wasCorked) {
324
+ webSocket->AsyncSocket<SSL>::corkUnchecked();
325
+ }
326
+
327
+ /* Initialize websocket with any moved backpressure intact */
328
+ webSocket->init(perMessageDeflate, compressOptions, std::move(backpressure));
329
+
330
+ /* We should only mark this if inside the parser; if upgrading "async" we cannot set this */
331
+ HttpContextData<SSL> *httpContextData = httpContext->getSocketContextData();
332
+ if (httpContextData->isParsingHttp) {
333
+ /* We need to tell the Http parser that we changed socket */
334
+ httpContextData->upgradedWebSocket = webSocket;
335
+ }
336
+
337
+ /* Arm maxLifetime timeout */
338
+ us_socket_long_timeout(SSL, (us_socket_t *) webSocket, webSocketContextData->maxLifetime);
339
+
340
+ /* Arm idleTimeout */
341
+ us_socket_timeout(SSL, (us_socket_t *) webSocket, webSocketContextData->idleTimeoutComponents.first);
342
+
343
+ /* Move construct the UserData right before calling open handler */
344
+ new (webSocket->getUserData()) UserData(std::move(userData));
345
+
346
+ /* Emit open event and start the timeout */
347
+ if (webSocketContextData->openHandler) {
348
+ webSocketContextData->openHandler(webSocket);
349
+ }
350
+ }
351
+
352
+ /* Immediately terminate this Http response */
353
+ using Super::close;
354
+
355
+ /* See AsyncSocket */
356
+ using Super::getRemoteAddress;
357
+ using Super::getRemoteAddressAsText;
358
+ using Super::getNativeHandle;
359
+
360
+ /* Throttle reads and writes */
361
+ HttpResponse *pause() {
362
+ Super::pause();
363
+ Super::timeout(0);
364
+ return this;
365
+ }
366
+
367
+ HttpResponse *resume() {
368
+ Super::resume();
369
+ Super::timeout(HTTP_TIMEOUT_S);
370
+ return this;
371
+ }
372
+
373
+ /* Note: Headers are not checked in regards to timeout.
374
+ * We only check when you actively push data or end the request */
375
+
376
+ /* Write 100 Continue, can be done any amount of times */
377
+ HttpResponse *writeContinue() {
378
+ Super::write("HTTP/1.1 100 Continue\r\n\r\n", 25);
379
+ return this;
380
+ }
381
+
382
+ /* Write the HTTP status */
383
+ HttpResponse *writeStatus(std::string_view status) {
384
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
385
+
386
+ /* Do not allow writing more than one status */
387
+ if (httpResponseData->state & HttpResponseData<SSL>::HTTP_STATUS_CALLED) {
388
+ return this;
389
+ }
390
+
391
+ /* Update status */
392
+ httpResponseData->state |= HttpResponseData<SSL>::HTTP_STATUS_CALLED;
393
+
394
+ Super::write("HTTP/1.1 ", 9);
395
+ Super::write(status.data(), (int) status.length());
396
+ Super::write("\r\n", 2);
397
+ return this;
398
+ }
399
+
400
+ /* Write an HTTP header with string value */
401
+ HttpResponse *writeHeader(std::string_view key, std::string_view value) {
402
+ writeStatus(HTTP_200_OK);
403
+
404
+ Super::write(key.data(), (int) key.length());
405
+ Super::write(": ", 2);
406
+ Super::write(value.data(), (int) value.length());
407
+ Super::write("\r\n", 2);
408
+ return this;
409
+ }
410
+
411
+ /* Write an HTTP header with unsigned int value */
412
+ HttpResponse *writeHeader(std::string_view key, uint64_t value) {
413
+ writeStatus(HTTP_200_OK);
414
+
415
+ Super::write(key.data(), (int) key.length());
416
+ Super::write(": ", 2);
417
+ writeUnsigned64(value);
418
+ Super::write("\r\n", 2);
419
+ return this;
420
+ }
421
+
422
+ /* End without a body (no content-length) or end with a spoofed content-length. */
423
+ void endWithoutBody(std::optional<size_t> reportedContentLength = std::nullopt, bool closeConnection = false) {
424
+ if (reportedContentLength.has_value()) {
425
+ internalEnd({nullptr, 0}, reportedContentLength.value(), false, true, closeConnection);
426
+ } else {
427
+ internalEnd({nullptr, 0}, 0, false, false, closeConnection);
428
+ }
429
+ }
430
+
431
+ /* End the response with an optional data chunk. Always starts a timeout. */
432
+ void end(std::string_view data = {}, bool closeConnection = false) {
433
+ internalEnd(data, data.length(), false, true, closeConnection);
434
+ }
435
+
436
+ /* Try and end the response. Returns [true, true] on success.
437
+ * Starts a timeout in some cases. Returns [ok, hasResponded] */
438
+ std::pair<bool, bool> tryEnd(std::string_view data, uintmax_t totalSize = 0, bool closeConnection = false) {
439
+ return {internalEnd(data, totalSize, true, true, closeConnection), hasResponded()};
440
+ }
441
+
442
+ /* Write parts of the response in chunking fashion. Starts timeout if failed. */
443
+ bool write(std::string_view data) {
444
+ writeStatus(HTTP_200_OK);
445
+
446
+ /* Do not allow sending 0 chunks, they mark end of response */
447
+ if (!data.length()) {
448
+ /* If you called us, then according to you it was fine to call us so it's fine to still call us */
449
+ return true;
450
+ }
451
+
452
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
453
+
454
+ if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
455
+ /* Write mark on first call to write */
456
+ writeMark();
457
+
458
+ writeHeader("Transfer-Encoding", "chunked");
459
+ httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
460
+ }
461
+
462
+ Super::write("\r\n", 2);
463
+ writeUnsignedHex((unsigned int) data.length());
464
+ Super::write("\r\n", 2);
465
+
466
+ auto [written, failed] = Super::write(data.data(), (int) data.length());
467
+ if (failed) {
468
+ Super::timeout(HTTP_TIMEOUT_S);
469
+ }
470
+
471
+ /* If we did not fail the write, accept more */
472
+ return !failed;
473
+ }
474
+
475
+ /* Get the current byte write offset for this Http response */
476
+ uintmax_t getWriteOffset() {
477
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
478
+
479
+ return httpResponseData->offset;
480
+ }
481
+
482
+ /* If you are messing around with sendfile you might want to override the offset. */
483
+ void overrideWriteOffset(uintmax_t offset) {
484
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
485
+
486
+ httpResponseData->offset = offset;
487
+ }
488
+
489
+ /* Checking if we have fully responded and are ready for another request */
490
+ bool hasResponded() {
491
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
492
+
493
+ return !(httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING);
494
+ }
495
+
496
+ /* Corks the response if possible. Leaves already corked socket be. */
497
+ HttpResponse *cork(MoveOnlyFunction<void()> &&handler) {
498
+ if (!Super::isCorked() && Super::canCork()) {
499
+ LoopData *loopData = Super::getLoopData();
500
+ Super::cork();
501
+ handler();
502
+
503
+ /* The only way we could possibly have changed the corked socket during handler call, would be if
504
+ * the HTTP socket was upgraded to WebSocket and caused a realloc. Because of this we cannot use "this"
505
+ * from here downwards. The corking is done with corkUnchecked() in upgrade. It steals cork. */
506
+ auto *newCorkedSocket = loopData->corkedSocket;
507
+
508
+ /* If nobody is corked, it means most probably that large amounts of data has
509
+ * been written and the cork buffer has already been sent off and uncorked.
510
+ * We are done here, if that is the case. */
511
+ if (!newCorkedSocket) {
512
+ return this;
513
+ }
514
+
515
+ /* Timeout on uncork failure, since most writes will succeed while corked */
516
+ auto [written, failed] = static_cast<Super *>(newCorkedSocket)->uncork();
517
+
518
+ /* If we are no longer an HTTP socket then early return the new "this".
519
+ * We don't want to even overwrite timeout as it is set in upgrade already. */
520
+ if (this != newCorkedSocket) {
521
+ return static_cast<HttpResponse *>(newCorkedSocket);
522
+ }
523
+
524
+ if (failed) {
525
+ /* For now we only have one single timeout so let's use it */
526
+ /* This behavior should equal the behavior in HttpContext when uncorking fails */
527
+ Super::timeout(HTTP_TIMEOUT_S);
528
+ }
529
+
530
+ /* If we have no backbuffer and we are connection close and we responded fully then close */
531
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
532
+ if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
533
+ if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
534
+ if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
535
+ ((AsyncSocket<SSL> *) this)->shutdown();
536
+ /* We need to force close after sending FIN since we want to hinder
537
+ * clients from keeping to send their huge data */
538
+ ((AsyncSocket<SSL> *) this)->close();
539
+ }
540
+ }
541
+ }
542
+ } else {
543
+ /* We are already corked, or can't cork so let's just call the handler */
544
+ handler();
545
+ }
546
+
547
+ return this;
548
+ }
549
+
550
+ /* Attach handler for writable HTTP response */
551
+ HttpResponse *onWritable(MoveOnlyFunction<bool(uintmax_t)> &&handler) {
552
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
553
+
554
+ httpResponseData->onWritable = std::move(handler);
555
+ return this;
556
+ }
557
+
558
+ /* Attach handler for aborted HTTP request */
559
+ HttpResponse *onAborted(MoveOnlyFunction<void()> &&handler) {
560
+ HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
561
+
562
+ httpResponseData->onAborted = std::move(handler);
563
+ return this;
564
+ }
565
+
566
+ /* Attach a read handler for data sent. Will be called with FIN set true if last segment. */
567
+ void onData(MoveOnlyFunction<void(std::string_view, bool)> &&handler) {
568
+ HttpResponseData<SSL> *data = getHttpResponseData();
569
+ data->inStream = std::move(handler);
570
+
571
+ /* Always reset this counter here */
572
+ data->received_bytes_per_timeout = 0;
573
+ }
574
+ };
575
+
576
+ }
577
+
578
+ #endif // UWS_HTTPRESPONSE_H
@@ -0,0 +1,95 @@
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_HTTPRESPONSEDATA_H
19
+ #define UWS_HTTPRESPONSEDATA_H
20
+
21
+ /* This data belongs to the HttpResponse */
22
+
23
+ #include "HttpParser.h"
24
+ #include "AsyncSocketData.h"
25
+ #include "ProxyParser.h"
26
+
27
+ #include "MoveOnlyFunction.h"
28
+
29
+ namespace uWS {
30
+
31
+ template <bool SSL>
32
+ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
33
+ template <bool> friend struct HttpResponse;
34
+ template <bool> friend struct HttpContext;
35
+
36
+ /* When we are done with a response we mark it like so */
37
+ void markDone() {
38
+ onAborted = nullptr;
39
+ /* Also remove onWritable so that we do not emit when draining behind the scenes. */
40
+ onWritable = nullptr;
41
+
42
+ /* We are done with this request */
43
+ state &= ~HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
44
+ }
45
+
46
+ /* Caller of onWritable. It is possible onWritable calls markDone so we need to borrow it. */
47
+ bool callOnWritable(uintmax_t offset) {
48
+ /* Borrow real onWritable */
49
+ MoveOnlyFunction<bool(uintmax_t)> borrowedOnWritable = std::move(onWritable);
50
+
51
+ /* Set onWritable to placeholder */
52
+ onWritable = [](uintmax_t) {return true;};
53
+
54
+ /* Run borrowed onWritable */
55
+ bool ret = borrowedOnWritable(offset);
56
+
57
+ /* If we still have onWritable (the placeholder) then move back the real one */
58
+ if (onWritable) {
59
+ /* We haven't reset onWritable, so give it back */
60
+ onWritable = std::move(borrowedOnWritable);
61
+ }
62
+
63
+ return ret;
64
+ }
65
+ private:
66
+ /* Bits of status */
67
+ enum {
68
+ HTTP_STATUS_CALLED = 1, // used
69
+ HTTP_WRITE_CALLED = 2, // used
70
+ HTTP_END_CALLED = 4, // used
71
+ HTTP_RESPONSE_PENDING = 8, // used
72
+ HTTP_CONNECTION_CLOSE = 16 // used
73
+ };
74
+
75
+ /* Per socket event handlers */
76
+ MoveOnlyFunction<bool(uintmax_t)> onWritable;
77
+ MoveOnlyFunction<void()> onAborted;
78
+ MoveOnlyFunction<void(std::string_view, bool)> inStream; // onData
79
+ /* Outgoing offset */
80
+ uintmax_t offset = 0;
81
+
82
+ /* Let's track number of bytes since last timeout reset in data handler */
83
+ unsigned int received_bytes_per_timeout = 0;
84
+
85
+ /* Current state (content-length sent, status sent, write called, etc */
86
+ int state = 0;
87
+
88
+ #ifdef UWS_WITH_PROXY
89
+ ProxyParser proxyParser;
90
+ #endif
91
+ };
92
+
93
+ }
94
+
95
+ #endif // UWS_HTTPRESPONSEDATA_H