opal-up 0.0.3 → 0.0.5

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