quico 0.4.0 → 0.7.0
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.
- package/README.md +1768 -155
- package/index.js +55 -36
- package/package.json +3 -3
- package/src/agent.js +306 -36
- package/src/client.js +447 -120
- package/src/client_profile.js +266 -0
- package/src/connect_udp.js +578 -0
- package/src/connect_udp_client.js +770 -0
- package/src/crypto.js +240 -140
- package/src/h1.js +953 -0
- package/src/h1_client.js +731 -0
- package/src/h1_server.js +624 -0
- package/src/h2.js +1325 -0
- package/src/h2_client.js +675 -0
- package/src/h2_fingerprint.js +403 -0
- package/src/h2_frames.js +582 -0
- package/src/h2_server.js +862 -0
- package/src/h3.js +1966 -469
- package/src/h3_client.js +467 -48
- package/src/h3_server.js +1349 -94
- package/src/hpack.js +455 -0
- package/src/hpack_primitives.js +520 -0
- package/src/http_common.js +901 -0
- package/src/ja4h.js +256 -0
- package/src/qpack.js +555 -0
- package/src/quic_connection.js +6517 -657
- package/src/quic_server.js +768 -73
- package/src/quic_socket.js +134 -9
- package/src/quic_version.js +143 -0
- package/src/server.js +843 -115
- package/src/server_stats.js +93 -0
- package/src/streams.js +361 -35
- package/src/tcp_listener.js +557 -0
- package/src/tls_bridge.js +474 -73
- package/src/tls_options.js +108 -0
- package/src/transport.js +455 -46
- package/src/utils.js +94 -12
- package/src/webtransport.js +433 -436
- package/src/ws.js +624 -0
- package/src/ws_client.js +641 -0
- package/src/ws_server.js +833 -0
- package/src/wt_stream.js +618 -0
package/src/h1_server.js
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* quico: HTTP/3 and QUIC implementation for Node.js
|
|
3
|
+
* Copyright 2025 colocohen
|
|
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
|
+
* https://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* This file is part of the open-source project hosted at:
|
|
12
|
+
* https://github.com/colocohen/quico
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { EventEmitter } from 'node:events';
|
|
16
|
+
|
|
17
|
+
import { H1Parser, buildResponseHead, encodeChunk, encodeLastChunk } from './h1.js';
|
|
18
|
+
import { corkForTick, STATUS_TEXT, runRequestHandler, DEFAULT_HEADERS_TIMEOUT, DEFAULT_REQUEST_TIMEOUT, maxRequestsPerSocket, trailerFields, statusHasNoBody, httpDate } from './http_common.js';
|
|
19
|
+
import { IncomingMessage, ServerResponse } from './streams.js';
|
|
20
|
+
import { createTcpListener } from './tcp_listener.js';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// ============================================================
|
|
24
|
+
// HTTP/1.1 server.
|
|
25
|
+
//
|
|
26
|
+
// Layered exactly like h3_server on top of quic_server: the transport below
|
|
27
|
+
// (net, or lemon-tls's TLSSocket over net) hands us a Duplex per peer, h1.js
|
|
28
|
+
// turns its bytes into messages, and streams.js wraps those in the
|
|
29
|
+
// IncomingMessage / ServerResponse pair every framework expects. Nothing here
|
|
30
|
+
// parses HTTP and nothing in h1.js touches a socket.
|
|
31
|
+
//
|
|
32
|
+
// TLS comes from lemon-tls, not node:tls — that is the whole point of
|
|
33
|
+
// replacing node:https. `options.tls` carries the credentials; without it the
|
|
34
|
+
// server speaks cleartext (http://, ws://), which is what a local dev server
|
|
35
|
+
// or a service behind a terminating proxy wants.
|
|
36
|
+
// ============================================================
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// A peer that opens a connection and then says nothing — or sends a head one
|
|
40
|
+
// byte at a time — costs us a socket and a buffer for as long as we let it.
|
|
41
|
+
// h1.js deliberately cannot help here: an incomplete head is indistinguishable
|
|
42
|
+
// from a slow one, and it has no clock. The clock lives here. (The headers and
|
|
43
|
+
// request defaults are every version's: http_common.)
|
|
44
|
+
var DEFAULT_KEEPALIVE_TIMEOUT = 5 * 1000;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// The hints as header fields: names lowercased, and only real values.
|
|
48
|
+
// Everything else about them (which links, which rels) is the caller's.
|
|
49
|
+
function earlyHintHeaders(hints) {
|
|
50
|
+
var out = {};
|
|
51
|
+
for (var n in (hints || {})) {
|
|
52
|
+
if (!Object.prototype.hasOwnProperty.call(hints, n)) continue;
|
|
53
|
+
var v = hints[n];
|
|
54
|
+
if (v === undefined || v === null || (Array.isArray(v) && v.length === 0)) continue;
|
|
55
|
+
out[String(n).toLowerCase()] = v;
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
function createServer(options, handler) {
|
|
62
|
+
if (typeof options === 'function') { handler = options; options = {}; }
|
|
63
|
+
options = options || {};
|
|
64
|
+
|
|
65
|
+
var ev = new EventEmitter();
|
|
66
|
+
var tcpServer = null;
|
|
67
|
+
var connections = new Set();
|
|
68
|
+
var closing = false;
|
|
69
|
+
|
|
70
|
+
var headersTimeout = options.headersTimeout != null ? options.headersTimeout : DEFAULT_HEADERS_TIMEOUT;
|
|
71
|
+
var keepAliveTimeout = options.keepAliveTimeout != null ? options.keepAliveTimeout : DEFAULT_KEEPALIVE_TIMEOUT;
|
|
72
|
+
var requestTimeout = options.requestTimeout != null ? options.requestTimeout : DEFAULT_REQUEST_TIMEOUT;
|
|
73
|
+
// maxHeaderSize is node:http's name for the same limit.
|
|
74
|
+
var maxHeadSize = options.maxHeadSize || options.maxHeaderSize;
|
|
75
|
+
var maxHeaderCount = options.maxHeaderCount;
|
|
76
|
+
// node:http's: the Nth response says Connection: close and the connection
|
|
77
|
+
// ends after it; a request past it (pipelined) is answered 503.
|
|
78
|
+
var maxRequests = maxRequestsPerSocket(options.maxRequestsPerSocket);
|
|
79
|
+
|
|
80
|
+
// Advertised on every response so a client learns HTTP/3 is available on the
|
|
81
|
+
// same authority (RFC 9114 §3.3). The unified server sets it; a standalone
|
|
82
|
+
// h1 server has nothing to advertise.
|
|
83
|
+
var altSvc = options.altSvc || null;
|
|
84
|
+
|
|
85
|
+
// node:http's shouldUpgradeCallback(request): whether an upgrade request
|
|
86
|
+
// reaches 'upgrade' (true) or is handled as an ordinary 'request' (false).
|
|
87
|
+
// Node's default, and ours: true when something listens for 'upgrade'.
|
|
88
|
+
// It used to be answered 501 when nothing did — a refusal Node never gives.
|
|
89
|
+
var shouldUpgradeCallback = typeof options.shouldUpgradeCallback === 'function'
|
|
90
|
+
? options.shouldUpgradeCallback
|
|
91
|
+
: function () { return ev.listenerCount('upgrade') > 0; };
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
// ============================================================
|
|
95
|
+
// One connection
|
|
96
|
+
// ============================================================
|
|
97
|
+
|
|
98
|
+
function handleConnection(socket) {
|
|
99
|
+
if (closing) { socket.destroy(); return; }
|
|
100
|
+
connections.add(socket);
|
|
101
|
+
|
|
102
|
+
var parser = new H1Parser({
|
|
103
|
+
isServer: true,
|
|
104
|
+
maxHeadSize: maxHeadSize,
|
|
105
|
+
maxHeaderCount: maxHeaderCount,
|
|
106
|
+
// The request as the callback sees it: method, url, headers — what
|
|
107
|
+
// node:http's IncomingMessage carries at that point.
|
|
108
|
+
shouldUpgrade: function (info) {
|
|
109
|
+
return shouldUpgradeCallback({
|
|
110
|
+
method: info.method, url: info.url, headers: info.headers,
|
|
111
|
+
rawHeaders: info.rawHeaders, httpVersion: info.httpVersion, socket: socket
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
var current = null; // { req, res, info }
|
|
117
|
+
var served = 0; // requests seen on this connection (maxRequestsPerSocket)
|
|
118
|
+
var destroyed = false;
|
|
119
|
+
var timer = null;
|
|
120
|
+
|
|
121
|
+
function setTimer(ms, reason) {
|
|
122
|
+
clearTimer();
|
|
123
|
+
if (!ms || ms <= 0) return;
|
|
124
|
+
timer = setTimeout(function () {
|
|
125
|
+
timer = null;
|
|
126
|
+
// Nothing is half-written at this point in the states we arm the timer
|
|
127
|
+
// for, so a hard destroy is honest: a 408 to a peer that has sent no
|
|
128
|
+
// complete request may itself never be read.
|
|
129
|
+
// requestTimeout with nothing answered yet: a 408 too, as node:http.
|
|
130
|
+
if ((reason === 'headers' && !current) || (reason === 'request' && current && !current.res.headersSent)) {
|
|
131
|
+
try { socket.end(buildResponseHead(408, null, { 'Connection': 'close', 'Content-Length': '0' })); }
|
|
132
|
+
catch (e) {}
|
|
133
|
+
}
|
|
134
|
+
destroy(new Error('timeout: ' + reason));
|
|
135
|
+
}, ms);
|
|
136
|
+
if (timer.unref) timer.unref();
|
|
137
|
+
}
|
|
138
|
+
function clearTimer() { if (timer) { clearTimeout(timer); timer = null; } }
|
|
139
|
+
|
|
140
|
+
function destroy(err) {
|
|
141
|
+
if (destroyed) return;
|
|
142
|
+
destroyed = true;
|
|
143
|
+
clearTimer();
|
|
144
|
+
connections.delete(socket);
|
|
145
|
+
if (current && current.req && !current.req.complete) current.req._abort(err || null);
|
|
146
|
+
if (current && current.res) current.res._abortResponse(); // see ServerResponse
|
|
147
|
+
try { socket.destroy(); } catch (e) {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Waiting for the first byte of the first request.
|
|
151
|
+
setTimer(headersTimeout, 'headers');
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
// ---- Reading ----
|
|
155
|
+
|
|
156
|
+
socket.on('data', function (chunk) {
|
|
157
|
+
if (destroyed) return;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
parser.execute(chunk);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// h1.js reports parse failures via 'error'. A throw from here is a bug
|
|
163
|
+
// in a listener (a handler that threw synchronously), not bad input.
|
|
164
|
+
ev.emit('clientError', err, socket);
|
|
165
|
+
destroy(err);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function resumeParsing() {
|
|
170
|
+
if (destroyed) return;
|
|
171
|
+
holdForResponse = false;
|
|
172
|
+
updateReading();
|
|
173
|
+
try { parser.resume(); }
|
|
174
|
+
catch (err) { ev.emit('clientError', err, socket); destroy(err); }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reading from the socket stops while nothing would take what it brings,
|
|
178
|
+
// as node:http does it — the kernel's buffer fills, and TCP slows the
|
|
179
|
+
// client down:
|
|
180
|
+
// holdForReader the request body is not being read: the
|
|
181
|
+
// IncomingMessage's buffer is full (push() said so),
|
|
182
|
+
// until it asks for more (_read).
|
|
183
|
+
// holdForResponse the request is complete and its response is not:
|
|
184
|
+
// what follows is the next request, not looked at
|
|
185
|
+
// until then (pipelining, RFC 9112 §9.3).
|
|
186
|
+
// Neither stopped the socket. The parser took everything in — a body
|
|
187
|
+
// the handler had not started reading (it was waiting on a lookup, say)
|
|
188
|
+
// was held in memory as fast as the client could send it, hundreds of MB
|
|
189
|
+
// in seconds; so were pipelined bytes behind a slow response.
|
|
190
|
+
var holdForReader = false, holdForResponse = false;
|
|
191
|
+
function updateReading() {
|
|
192
|
+
if (destroyed) return;
|
|
193
|
+
if (holdForReader || holdForResponse) { if (!socket.isPaused()) socket.pause(); }
|
|
194
|
+
else if (socket.isPaused()) socket.resume();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
socket.on('error', function (err) {
|
|
198
|
+
// A peer that vanishes mid-request is ordinary, not exceptional; surface
|
|
199
|
+
// it for logging but never let it reach an uncaught handler.
|
|
200
|
+
ev.emit('clientError', err, socket);
|
|
201
|
+
destroy(err);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
socket.on('close', function () { destroy(null); });
|
|
205
|
+
|
|
206
|
+
socket.on('end', function () {
|
|
207
|
+
// The peer half-closed. Anything mid-message is truncated; the parser
|
|
208
|
+
// decides which.
|
|
209
|
+
try { parser.eof(); } catch (e) {}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// ---- Parser events ----
|
|
214
|
+
|
|
215
|
+
parser.on('error', function (err) {
|
|
216
|
+
ev.emit('clientError', err, socket);
|
|
217
|
+
|
|
218
|
+
// Answer if we still can. A framing failure means we no longer know where
|
|
219
|
+
// the next message starts, so the connection always closes after this.
|
|
220
|
+
if (!destroyed && socket.writable) {
|
|
221
|
+
var code = err.statusCode || 400;
|
|
222
|
+
if (options.stats) options.stats.status(options.stats.closes.http1.sent, code);
|
|
223
|
+
try {
|
|
224
|
+
socket.end(buildResponseHead(code, null, {
|
|
225
|
+
'Connection': 'close',
|
|
226
|
+
'Content-Length': '0'
|
|
227
|
+
}));
|
|
228
|
+
} catch (e) {}
|
|
229
|
+
}
|
|
230
|
+
destroy(err);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
parser.on('headers', function (info) {
|
|
234
|
+
clearTimer();
|
|
235
|
+
setTimer(requestTimeout, 'request');
|
|
236
|
+
served++;
|
|
237
|
+
if (maxRequests > 0 && served > maxRequests) {
|
|
238
|
+
// Past the limit (node:http: "subsequent requests ... will get 503").
|
|
239
|
+
try {
|
|
240
|
+
socket.end(buildResponseHead(503, null, { 'Connection': 'close', 'Content-Length': '0' }));
|
|
241
|
+
} catch (e) {}
|
|
242
|
+
destroy(null);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
var req = new IncomingMessage({
|
|
247
|
+
httpVersion: info.httpVersion,
|
|
248
|
+
method: info.method,
|
|
249
|
+
url: info.url,
|
|
250
|
+
headers: info.headers,
|
|
251
|
+
socket: socket
|
|
252
|
+
});
|
|
253
|
+
req.rawHeaders = info.rawHeaders;
|
|
254
|
+
req.complete = false;
|
|
255
|
+
// Read whole from 0-RTT data (TLS early data): it could be a replay
|
|
256
|
+
// (server.js holds it to earlyDataFilter). A request completed by
|
|
257
|
+
// bytes after the handshake cannot be replayed, and is not early.
|
|
258
|
+
req.earlyData = socket.isEarlyData === true;
|
|
259
|
+
|
|
260
|
+
var res = new ServerResponse({ socket: socket });
|
|
261
|
+
res.req = req;
|
|
262
|
+
// res.destroy([err]) / req.destroy([err]) by the application, the
|
|
263
|
+
// response unfinished: the socket goes, as on node:http — the only way
|
|
264
|
+
// HTTP/1.1 has to say the response will not be completed. It stayed
|
|
265
|
+
// open, and the client waited for the rest.
|
|
266
|
+
res._onDestroy(function () { destroy(null); });
|
|
267
|
+
req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); });
|
|
268
|
+
// The reader wants more: reading from the socket goes on (see
|
|
269
|
+
// holdForReader).
|
|
270
|
+
req._read = function () {
|
|
271
|
+
if (holdForReader && current && current.req === req) { holdForReader = false; updateReading(); }
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
current = { req: req, res: res, info: info };
|
|
275
|
+
wireResponse(info, req, res, maxRequests > 0 && served >= maxRequests);
|
|
276
|
+
|
|
277
|
+
// Expect: 100-continue (RFC 9110 §10.1.1). The client is waiting for
|
|
278
|
+
// permission before sending the body; if we never answer, it stalls for
|
|
279
|
+
// its own timeout and then sends anyway. Default to allowing it, and let
|
|
280
|
+
// a 'checkContinue' listener take over when one is registered — the same
|
|
281
|
+
// split node:http makes.
|
|
282
|
+
var expect = String(info.headers['expect'] || '').toLowerCase();
|
|
283
|
+
if (expect === '100-continue') {
|
|
284
|
+
if (ev.listenerCount('checkContinue') > 0) {
|
|
285
|
+
runRequestHandler(function () { ev.emit('checkContinue', req, res); }, handlerFailed);
|
|
286
|
+
return; // the listener owns the exchange from here
|
|
287
|
+
}
|
|
288
|
+
try { socket.write('HTTP/1.1 100 Continue\r\n\r\n'); } catch (e) {}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// No special case for info.upgrade here any more. A request that will
|
|
292
|
+
// actually switch protocols never reaches this handler — h1.js emits
|
|
293
|
+
// 'upgrade' instead of 'headers' for those. What DOES reach it is an
|
|
294
|
+
// Upgrade request that cannot switch, such as one carrying a body: that
|
|
295
|
+
// is an ordinary request the application should answer (and probably
|
|
296
|
+
// ignore the Upgrade on), not one to drop on the floor. The early return
|
|
297
|
+
// that used to sit here hung exactly those.
|
|
298
|
+
runRequestHandler(function () {
|
|
299
|
+
if (typeof handler === 'function') handler(req, res);
|
|
300
|
+
ev.emit('request', req, res);
|
|
301
|
+
}, handlerFailed);
|
|
302
|
+
|
|
303
|
+
// The handler threw (see runRequestHandler): a 500 if nothing has been
|
|
304
|
+
// sent yet, and the connection closed — the response may be half
|
|
305
|
+
// written, and nothing after it on this connection can be trusted to
|
|
306
|
+
// line up. A response it had already ended is left to finish.
|
|
307
|
+
function handlerFailed() {
|
|
308
|
+
if (destroyed || res.writableEnded) return;
|
|
309
|
+
if (!res.headersSent && socket.writable) {
|
|
310
|
+
try {
|
|
311
|
+
socket.end(buildResponseHead(500, null, { 'Connection': 'close', 'Content-Length': '0' }));
|
|
312
|
+
} catch (e) {}
|
|
313
|
+
}
|
|
314
|
+
destroy(null);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
parser.on('body', function (chunk) {
|
|
319
|
+
var req = current && current.req;
|
|
320
|
+
if (!req) return;
|
|
321
|
+
// Destroyed by the application: it will read no more. Dropped, and
|
|
322
|
+
// reading goes on — a body nobody reads must not stop the connection.
|
|
323
|
+
if (req.destroyed) return;
|
|
324
|
+
if (req._pushData(chunk) === false && !holdForReader) {
|
|
325
|
+
holdForReader = true;
|
|
326
|
+
updateReading();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
parser.on('trailers', function (trailers) {
|
|
331
|
+
if (current && current.req) current.req.trailers = trailers;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
parser.on('end', function () {
|
|
335
|
+
// requestTimeout bounds the request's arrival, as node:http's does —
|
|
336
|
+
// not the response: a long download or an event stream runs as long as
|
|
337
|
+
// the application keeps writing. It ran until the response was done,
|
|
338
|
+
// harmless while the default was 0 (none), not with node's 300 s.
|
|
339
|
+
clearTimer();
|
|
340
|
+
|
|
341
|
+
// The request is complete. Stop looking at anything after it until its
|
|
342
|
+
// response is on the wire — pipelined request N+1 must not be handled
|
|
343
|
+
// before response N is sent. Pausing here rather than at 'headers' is
|
|
344
|
+
// the difference between "do not read ahead" and "do not read the body
|
|
345
|
+
// of the request we are answering", and the latter deadlocks any client
|
|
346
|
+
// that waits for 100-continue.
|
|
347
|
+
if (current && !current.res.finished) { parser.pause(); holdForResponse = true; updateReading(); }
|
|
348
|
+
if (current && current.req) current.req._endData();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
parser.on('upgrade', function (info, head) {
|
|
352
|
+
clearTimer();
|
|
353
|
+
|
|
354
|
+
var req = new IncomingMessage({
|
|
355
|
+
httpVersion: info.httpVersion,
|
|
356
|
+
method: info.method,
|
|
357
|
+
url: info.url,
|
|
358
|
+
headers: info.headers,
|
|
359
|
+
socket: socket
|
|
360
|
+
});
|
|
361
|
+
req.rawHeaders = info.rawHeaders;
|
|
362
|
+
req._endData();
|
|
363
|
+
|
|
364
|
+
// Hand the raw socket over, exactly as node:http does. `head` is the
|
|
365
|
+
// bytes that arrived after the request head and already belong to the
|
|
366
|
+
// new protocol — a WebSocket client may put its first frame in the same
|
|
367
|
+
// TCP segment as the handshake, and dropping it loses that frame.
|
|
368
|
+
//
|
|
369
|
+
// From here we own nothing: no parser, no timers, no keep-alive.
|
|
370
|
+
connections.delete(socket);
|
|
371
|
+
destroyed = true; // stop our own handlers touching the socket
|
|
372
|
+
clearTimer();
|
|
373
|
+
socket.removeAllListeners('data');
|
|
374
|
+
socket.removeAllListeners('end');
|
|
375
|
+
|
|
376
|
+
if (ev.listenerCount('upgrade') === 0) {
|
|
377
|
+
// Accepted by shouldUpgradeCallback, yet nobody listens: as node:http
|
|
378
|
+
// does, the socket is destroyed — the request is half-switched, and
|
|
379
|
+
// there is no one to finish the job.
|
|
380
|
+
try { socket.destroy(); } catch (e) {}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
ev.emit('upgrade', req, socket, head);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
// ============================================================
|
|
389
|
+
// Response wiring
|
|
390
|
+
//
|
|
391
|
+
// ServerResponse is transport-agnostic (h3_server overrides the same
|
|
392
|
+
// three hooks to write H3 frames). Here they become HTTP/1.1 bytes.
|
|
393
|
+
// ============================================================
|
|
394
|
+
|
|
395
|
+
function wireResponse(info, req, res, lastOnSocket) {
|
|
396
|
+
var chunked = false;
|
|
397
|
+
var bodyAllowed = true;
|
|
398
|
+
var willClose = !!lastOnSocket; // maxRequestsPerSocket reached: this one closes it
|
|
399
|
+
var finished = false;
|
|
400
|
+
|
|
401
|
+
res._flushHeaders = function () {
|
|
402
|
+
if (res.headersSent) return;
|
|
403
|
+
|
|
404
|
+
var headers = res.getHeaders();
|
|
405
|
+
var code = res.statusCode;
|
|
406
|
+
|
|
407
|
+
// Status codes that define themselves as bodiless, plus HEAD, whose
|
|
408
|
+
// response carries the headers of the body it is not sending.
|
|
409
|
+
var bodiless = statusHasNoBody(code);
|
|
410
|
+
bodyAllowed = !bodiless && info.method !== 'HEAD';
|
|
411
|
+
|
|
412
|
+
// A bodiless status must not carry framing headers at all: a
|
|
413
|
+
// Content-Length on a 204 makes some clients wait for a body that is
|
|
414
|
+
// never coming.
|
|
415
|
+
if (bodiless) {
|
|
416
|
+
delete headers['content-length'];
|
|
417
|
+
delete headers['transfer-encoding'];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Framing, in the order RFC 9112 §6 resolves it.
|
|
421
|
+
if (!bodiless) {
|
|
422
|
+
if ('transfer-encoding' in headers) {
|
|
423
|
+
chunked = String(headers['transfer-encoding']).toLowerCase().indexOf('chunked') !== -1;
|
|
424
|
+
} else if ('content-length' in headers) {
|
|
425
|
+
chunked = false;
|
|
426
|
+
} else if (res._endLength !== undefined) {
|
|
427
|
+
// The whole body was given to end() with nothing written before
|
|
428
|
+
// it: its length is known, and sent — as node:http does. It was
|
|
429
|
+
// chunked: res.end('hello') cost a chunk header and a terminating
|
|
430
|
+
// chunk, and a client could not tell the size before it arrived.
|
|
431
|
+
headers['content-length'] = String(res._endLength);
|
|
432
|
+
chunked = false;
|
|
433
|
+
} else if (info.httpVersion === '1.1' && info.keepAlive) {
|
|
434
|
+
// Length unknown and the connection is reusable, so chunk it —
|
|
435
|
+
// otherwise the only way to delimit the body is to close, and
|
|
436
|
+
// keep-alive is lost for no reason.
|
|
437
|
+
headers['transfer-encoding'] = 'chunked';
|
|
438
|
+
chunked = true;
|
|
439
|
+
} else {
|
|
440
|
+
// HTTP/1.0, or the client asked to close: the body ends when the
|
|
441
|
+
// connection does. (1.0 has no chunked encoding.)
|
|
442
|
+
willClose = true;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Connection header. `keepAlive` already accounts for the client's
|
|
447
|
+
// Connection: close and for HTTP/1.0 semantics.
|
|
448
|
+
var keepAlive = info.keepAlive && !willClose && !closing;
|
|
449
|
+
if (String(headers['connection'] || '').toLowerCase() === 'close') keepAlive = false;
|
|
450
|
+
|
|
451
|
+
headers['connection'] = keepAlive ? 'keep-alive' : 'close';
|
|
452
|
+
willClose = !keepAlive;
|
|
453
|
+
|
|
454
|
+
if (keepAlive && info.httpVersion === '1.0') headers['keep-alive'] = 'timeout=5';
|
|
455
|
+
if (!('date' in headers)) headers['date'] = httpDate();
|
|
456
|
+
// A function when the server was built before its port was known —
|
|
457
|
+
// the unified server builds its layers at creation and learns the
|
|
458
|
+
// port from listen(), or from options.port when fed from outside.
|
|
459
|
+
var altSvcNow = typeof altSvc === 'function' ? altSvc() : altSvc;
|
|
460
|
+
if (altSvcNow && !('alt-svc' in headers)) headers['alt-svc'] = altSvcNow;
|
|
461
|
+
|
|
462
|
+
// The application's reason phrase, else the standard one for the code
|
|
463
|
+
// — and statusMessage then says what was sent, as node:http does.
|
|
464
|
+
var reason = res.statusMessage || STATUS_TEXT[code] || '';
|
|
465
|
+
res.statusMessage = reason;
|
|
466
|
+
var head = buildResponseHead(code, reason, headers);
|
|
467
|
+
// Corked: the head and the body written after it in this tick — all
|
|
468
|
+
// of res.end(body) — leave as one segment, not one each.
|
|
469
|
+
corkForTick(socket);
|
|
470
|
+
try { socket.write(head); } catch (e) { destroy(e); return; }
|
|
471
|
+
res.headersSent = true;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// 103 Early Hints before the response (RFC 8297): an interim response,
|
|
475
|
+
// written whole before the final head. HTTP/1.0 has no interim
|
|
476
|
+
// responses, and after the head it is too late — both send nothing.
|
|
477
|
+
// node:http's signature: (hints, callback).
|
|
478
|
+
res.writeEarlyHints = function (hints, callback) {
|
|
479
|
+
var ok = !res.headersSent && info.httpVersion !== '1.0' && !destroyed;
|
|
480
|
+
if (ok) {
|
|
481
|
+
try { socket.write(buildResponseHead(103, null, earlyHintHeaders(hints))); }
|
|
482
|
+
catch (e) { ok = false; }
|
|
483
|
+
}
|
|
484
|
+
if (typeof callback === 'function') process.nextTick(callback);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
res._write = function (chunk, encoding, callback) {
|
|
488
|
+
if (!res.headersSent) res._flushHeaders();
|
|
489
|
+
if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding);
|
|
490
|
+
|
|
491
|
+
// A HEAD response, or a bodiless status, must send no body — but the
|
|
492
|
+
// application still wrote one, and that is fine: it wrote the body it
|
|
493
|
+
// WOULD have sent. Swallow the bytes; the headers already described
|
|
494
|
+
// them.
|
|
495
|
+
if (!bodyAllowed) { callback(); return; }
|
|
496
|
+
|
|
497
|
+
var ok = true;
|
|
498
|
+
try {
|
|
499
|
+
if (!chunked) ok = socket.write(chunk);
|
|
500
|
+
else {
|
|
501
|
+
var parts = encodeChunk(chunk);
|
|
502
|
+
if (parts.length) corkForTick(socket);
|
|
503
|
+
for (var i = 0; i < parts.length; i++) ok = socket.write(parts[i]);
|
|
504
|
+
}
|
|
505
|
+
} catch (e) { callback(e); return; }
|
|
506
|
+
|
|
507
|
+
// Respect back-pressure: without this a fast producer piped into a slow
|
|
508
|
+
// client buffers the whole body in memory.
|
|
509
|
+
if (ok) callback();
|
|
510
|
+
else socket.once('drain', callback);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
res._final = function (callback) {
|
|
514
|
+
if (finished) { callback(); return; }
|
|
515
|
+
finished = true;
|
|
516
|
+
if (!res.headersSent) res._flushHeaders();
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
if (bodyAllowed && chunked) socket.write(encodeLastChunk(trailerFields(res.trailers)));
|
|
520
|
+
} catch (e) { callback(e); return; }
|
|
521
|
+
|
|
522
|
+
callback();
|
|
523
|
+
onResponseComplete();
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
function onResponseComplete() {
|
|
527
|
+
clearTimer();
|
|
528
|
+
current = null;
|
|
529
|
+
// Whatever of the body is still to come is dropped from here on (the
|
|
530
|
+
// parser reads it, to find the next request): the reader no longer
|
|
531
|
+
// holds the socket back.
|
|
532
|
+
holdForReader = false;
|
|
533
|
+
updateReading();
|
|
534
|
+
|
|
535
|
+
if (willClose || closing) {
|
|
536
|
+
// Half-close: let the peer read what we wrote before the socket goes.
|
|
537
|
+
try { socket.end(); } catch (e) {}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Ready for the next request on this connection.
|
|
542
|
+
setTimer(keepAliveTimeout, 'keep-alive');
|
|
543
|
+
resumeParsing();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
// ============================================================
|
|
550
|
+
// Listening
|
|
551
|
+
// ============================================================
|
|
552
|
+
|
|
553
|
+
// ============================================================
|
|
554
|
+
// Listening
|
|
555
|
+
//
|
|
556
|
+
// Sockets, TLS and ALPN belong to tcp_listener, which h2_server and
|
|
557
|
+
// ws_server share — a client picks between them with ALPN, and that choice
|
|
558
|
+
// has to be made in one place or the three ALPN lists drift apart. What
|
|
559
|
+
// remains here is a convenience: an h1-only server that owns its listener,
|
|
560
|
+
// so this file can still be used (and tested) on its own.
|
|
561
|
+
//
|
|
562
|
+
// The unified server does NOT call this. It builds one listener, registers
|
|
563
|
+
// handleConnection under 'http/1.1', and registers h2_server's under 'h2'.
|
|
564
|
+
// ============================================================
|
|
565
|
+
|
|
566
|
+
function listen(port, host, callback) {
|
|
567
|
+
if (typeof host === 'function') { callback = host; host = undefined; }
|
|
568
|
+
|
|
569
|
+
tcpServer = createTcpListener({
|
|
570
|
+
tls: options.tls,
|
|
571
|
+
key: options.key,
|
|
572
|
+
cert: options.cert,
|
|
573
|
+
ca: options.ca,
|
|
574
|
+
SNICallback: options.SNICallback
|
|
575
|
+
});
|
|
576
|
+
tcpServer.register('http/1.1', handleConnection);
|
|
577
|
+
tcpServer.on('error', function (err) { ev.emit('error', err); });
|
|
578
|
+
tcpServer.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
|
|
579
|
+
tcpServer.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
|
|
580
|
+
tcpServer.listen(port, host, function () {
|
|
581
|
+
ev.emit('listening');
|
|
582
|
+
if (typeof callback === 'function') callback();
|
|
583
|
+
});
|
|
584
|
+
return server;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function close(callback) {
|
|
588
|
+
closing = true;
|
|
589
|
+
if (!tcpServer) {
|
|
590
|
+
// Sockets we were handed rather than accepted: still ours to close.
|
|
591
|
+
for (var s of connections) { try { s.end(); } catch (e) {} }
|
|
592
|
+
if (callback) callback();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
tcpServer.close(callback);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function address() {
|
|
599
|
+
return tcpServer && tcpServer.address ? tcpServer.address() : null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
var server = {
|
|
603
|
+
listen: listen,
|
|
604
|
+
close: close,
|
|
605
|
+
address: address,
|
|
606
|
+
on: function (n, f) { ev.on(n, f); return server; },
|
|
607
|
+
once: function (n, f) { ev.once(n, f); return server; },
|
|
608
|
+
off: function (n, f) { ev.off(n, f); return server; },
|
|
609
|
+
removeListener: function (n, f) { ev.removeListener(n, f); return server; },
|
|
610
|
+
emit: function () { ev.emit.apply(ev, arguments); return server; },
|
|
611
|
+
listenerCount: function (n) { return ev.listenerCount(n); },
|
|
612
|
+
setTimeout: function (ms) { headersTimeout = ms; return server; },
|
|
613
|
+
|
|
614
|
+
/** Attach an already-connected Duplex, bypassing the listener. Lets the
|
|
615
|
+
* unified server share one TCP port, and makes the whole stack testable
|
|
616
|
+
* without binding anything. */
|
|
617
|
+
handleConnection: handleConnection
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
return server;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
export { createServer };
|