visaljs 0.0.10 → 0.0.11
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 +12 -4
- package/THIRD_PARTY_LICENSES +31 -1
- package/cli.js +962 -68
- package/node/include/js_node.h +11 -1
- package/node/prelude/_http_common.js +29 -0
- package/node/prelude/async_hooks.js +50 -11
- package/node/prelude/child_process.js +31 -4
- package/node/prelude/console.js +311 -143
- package/node/prelude/crypto.js +6 -4
- package/node/prelude/dgram.js +1169 -0
- package/node/prelude/diagnostics_channel.js +3 -1
- package/node/prelude/dns.js +250 -45
- package/node/prelude/dns_promises.js +8 -2
- package/node/prelude/fs_promises.js +9 -1
- package/node/prelude/http.js +395 -86
- package/node/prelude/https.js +13 -4
- package/node/prelude/internal/console_class.js +7 -0
- package/node/prelude/internal/console_global.js +25 -0
- package/node/prelude/internal/crypto/key_encryption.js +217 -0
- package/node/prelude/internal/crypto/keys.js +161 -26
- package/node/prelude/internal/crypto/random.js +24 -0
- package/node/prelude/internal/crypto/util.js +2 -1
- package/node/prelude/internal/crypto_x509.js +161 -0
- package/node/prelude/internal/dns_classes.js +6 -0
- package/node/prelude/internal/dns_resolver.js +371 -0
- package/node/prelude/internal/errors.js +9 -0
- package/node/prelude/internal/events_on.js +82 -37
- package/node/prelude/internal/features.js +8 -4
- package/node/prelude/internal/fs_filehandle.js +10 -4
- package/node/prelude/internal/perf_histogram.js +1 -1
- package/node/prelude/internal/process_more.js +32 -17
- package/node/prelude/internal/process_signals.js +8 -2
- package/node/prelude/internal/punycode.ts +163 -0
- package/node/prelude/internal/readline.js +648 -274
- package/node/prelude/internal/readline_editor.js +1240 -0
- package/node/prelude/internal/string_width.js +43 -0
- package/node/prelude/internal/tls/common.js +145 -33
- package/node/prelude/internal/tls/wrap.js +182 -49
- package/node/prelude/internal/tls/x509.js +4 -1
- package/node/prelude/internal/udp.js +177 -0
- package/node/prelude/net.js +13 -1
- package/node/prelude/os.js +24 -6
- package/node/prelude/perf_hooks.js +37 -2
- package/node/prelude/punycode.js +2 -130
- package/node/prelude/readline.js +113 -4
- package/node/prelude/readline_promises.js +56 -32
- package/node/prelude/tls.js +21 -4
- package/node/prelude/util.js +3 -16
- package/node/src/host_dns.c +623 -0
- package/node/src/host_http.c +12 -0
- package/node/src/host_net.c +46 -0
- package/node/src/host_os.c +1 -6
- package/node/src/host_sys.c +70 -0
- package/node/src/host_udp.c +418 -0
- package/node/third_party/c-ares/LICENSE.md +24 -0
- package/node/third_party/c-ares/config/darwin/ares_config.h +573 -0
- package/node/third_party/c-ares/config/freebsd/ares_config.h +573 -0
- package/node/third_party/c-ares/config/linux/ares_config.h +573 -0
- package/node/third_party/c-ares/config/openbsd/ares_config.h +583 -0
- package/node/third_party/c-ares/include/ares.h +1237 -0
- package/node/third_party/c-ares/include/ares_build.h +202 -0
- package/node/third_party/c-ares/include/ares_dns.h +127 -0
- package/node/third_party/c-ares/include/ares_dns_record.h +1119 -0
- package/node/third_party/c-ares/include/ares_nameser.h +510 -0
- package/node/third_party/c-ares/include/ares_version.h +47 -0
- package/node/third_party/c-ares/src/lib/ares_addrinfo2hostent.c +305 -0
- package/node/third_party/c-ares/src/lib/ares_addrinfo_localhost.c +245 -0
- package/node/third_party/c-ares/src/lib/ares_android.c +496 -0
- package/node/third_party/c-ares/src/lib/ares_android.h +38 -0
- package/node/third_party/c-ares/src/lib/ares_cancel.c +87 -0
- package/node/third_party/c-ares/src/lib/ares_close_sockets.c +137 -0
- package/node/third_party/c-ares/src/lib/ares_conn.c +511 -0
- package/node/third_party/c-ares/src/lib/ares_conn.h +202 -0
- package/node/third_party/c-ares/src/lib/ares_cookie.c +461 -0
- package/node/third_party/c-ares/src/lib/ares_data.c +168 -0
- package/node/third_party/c-ares/src/lib/ares_data.h +92 -0
- package/node/third_party/c-ares/src/lib/ares_destroy.c +159 -0
- package/node/third_party/c-ares/src/lib/ares_free_hostent.c +54 -0
- package/node/third_party/c-ares/src/lib/ares_free_string.c +33 -0
- package/node/third_party/c-ares/src/lib/ares_freeaddrinfo.c +67 -0
- package/node/third_party/c-ares/src/lib/ares_getaddrinfo.c +806 -0
- package/node/third_party/c-ares/src/lib/ares_getenv.c +38 -0
- package/node/third_party/c-ares/src/lib/ares_getenv.h +34 -0
- package/node/third_party/c-ares/src/lib/ares_gethostbyaddr.c +229 -0
- package/node/third_party/c-ares/src/lib/ares_gethostbyname.c +341 -0
- package/node/third_party/c-ares/src/lib/ares_getnameinfo.c +437 -0
- package/node/third_party/c-ares/src/lib/ares_hosts_file.c +960 -0
- package/node/third_party/c-ares/src/lib/ares_inet_net_pton.h +31 -0
- package/node/third_party/c-ares/src/lib/ares_init.c +631 -0
- package/node/third_party/c-ares/src/lib/ares_ipv6.h +115 -0
- package/node/third_party/c-ares/src/lib/ares_library_init.c +190 -0
- package/node/third_party/c-ares/src/lib/ares_metrics.c +261 -0
- package/node/third_party/c-ares/src/lib/ares_options.c +492 -0
- package/node/third_party/c-ares/src/lib/ares_parse_into_addrinfo.c +179 -0
- package/node/third_party/c-ares/src/lib/ares_private.h +648 -0
- package/node/third_party/c-ares/src/lib/ares_process.c +1649 -0
- package/node/third_party/c-ares/src/lib/ares_qcache.c +442 -0
- package/node/third_party/c-ares/src/lib/ares_query.c +151 -0
- package/node/third_party/c-ares/src/lib/ares_search.c +623 -0
- package/node/third_party/c-ares/src/lib/ares_send.c +300 -0
- package/node/third_party/c-ares/src/lib/ares_set_socket_functions.c +590 -0
- package/node/third_party/c-ares/src/lib/ares_setup.h +224 -0
- package/node/third_party/c-ares/src/lib/ares_socket.c +427 -0
- package/node/third_party/c-ares/src/lib/ares_socket.h +163 -0
- package/node/third_party/c-ares/src/lib/ares_sortaddrinfo.c +447 -0
- package/node/third_party/c-ares/src/lib/ares_strerror.c +91 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig.c +642 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_files.c +851 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_mac.c +373 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_win.c +673 -0
- package/node/third_party/c-ares/src/lib/ares_timeout.c +162 -0
- package/node/third_party/c-ares/src/lib/ares_update_servers.c +1376 -0
- package/node/third_party/c-ares/src/lib/ares_version.c +36 -0
- package/node/third_party/c-ares/src/lib/config-dos.h +114 -0
- package/node/third_party/c-ares/src/lib/config-win32.h +396 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_array.c +409 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable.c +451 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable.h +174 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_asvp.c +224 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_dict.c +230 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_strvp.c +210 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_szvp.c +188 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpstr.c +186 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpvp.c +194 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_llist.c +385 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_slist.c +479 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_slist.h +207 -0
- package/node/third_party/c-ares/src/lib/event/ares_event.h +191 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_configchg.c +746 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_epoll.c +192 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_kqueue.c +248 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_poll.c +140 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_select.c +159 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_thread.c +575 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_wake_pipe.c +172 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_win32.c +981 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_win32.h +161 -0
- package/node/third_party/c-ares/src/lib/include/ares_array.h +276 -0
- package/node/third_party/c-ares/src/lib/include/ares_buf.h +732 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_asvp.h +130 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_dict.h +123 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_strvp.h +130 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_szvp.h +118 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_vpstr.h +111 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_vpvp.h +128 -0
- package/node/third_party/c-ares/src/lib/include/ares_llist.h +239 -0
- package/node/third_party/c-ares/src/lib/include/ares_mem.h +41 -0
- package/node/third_party/c-ares/src/lib/include/ares_str.h +261 -0
- package/node/third_party/c-ares/src/lib/inet_net_pton.c +451 -0
- package/node/third_party/c-ares/src/lib/inet_ntop.c +219 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_create_query.c +78 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_expand_name.c +99 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_expand_string.c +107 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_fds.c +80 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_getsock.c +85 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_a_reply.c +107 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c +109 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_caa_reply.c +137 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_mx_reply.c +110 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_naptr_reply.c +132 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_ns_reply.c +154 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_ptr_reply.c +213 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_soa_reply.c +115 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_srv_reply.c +114 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_txt_reply.c +144 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_uri_reply.c +113 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_mapping.c +982 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.c +309 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.h +72 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_name.c +736 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_parse.c +1360 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_private.h +273 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_record.c +1701 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_write.c +1229 -0
- package/node/third_party/c-ares/src/lib/str/ares_buf.c +1510 -0
- package/node/third_party/c-ares/src/lib/str/ares_str.c +562 -0
- package/node/third_party/c-ares/src/lib/str/ares_strsplit.c +90 -0
- package/node/third_party/c-ares/src/lib/str/ares_strsplit.h +51 -0
- package/node/third_party/c-ares/src/lib/thirdparty/apple/dnsinfo.h +122 -0
- package/node/third_party/c-ares/src/lib/util/ares_iface_ips.c +640 -0
- package/node/third_party/c-ares/src/lib/util/ares_iface_ips.h +140 -0
- package/node/third_party/c-ares/src/lib/util/ares_math.c +167 -0
- package/node/third_party/c-ares/src/lib/util/ares_math.h +58 -0
- package/node/third_party/c-ares/src/lib/util/ares_rand.c +389 -0
- package/node/third_party/c-ares/src/lib/util/ares_rand.h +36 -0
- package/node/third_party/c-ares/src/lib/util/ares_threads.c +613 -0
- package/node/third_party/c-ares/src/lib/util/ares_threads.h +60 -0
- package/node/third_party/c-ares/src/lib/util/ares_time.h +49 -0
- package/node/third_party/c-ares/src/lib/util/ares_timeval.c +95 -0
- package/node/third_party/c-ares/src/lib/util/ares_uri.c +1627 -0
- package/node/third_party/c-ares/src/lib/util/ares_uri.h +252 -0
- package/node/third_party/c-ares/src/lib/windows_port.c +29 -0
- package/package.json +1 -1
- package/prebuilt/darwin-arm64/cares-a6286c65ca9374a3.a +0 -0
- package/prebuilt/darwin-x64/cares-a6286c65ca9374a3.a +0 -0
- package/prebuilt/linux-arm64/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-arm64-musl/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-x64/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-x64-musl/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/cares-49c9ec95162b912d.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_builtins.h +5 -0
- package/runtime/include/js_crypto.h +11 -0
- package/runtime/include/js_gc.h +13 -10
- package/runtime/include/js_jobs.h +2 -0
- package/runtime/include/js_object.h +58 -0
- package/runtime/include/jscontext.h +1 -0
- package/runtime/src/crypto_internal.h +1 -0
- package/runtime/src/crypto_subtle.c +66 -7
- package/runtime/src/crypto_tls.c +265 -19
- package/runtime/src/gc.c +73 -92
- package/runtime/src/inspect.c +18 -1
- package/runtime/src/jobs.c +37 -22
- package/runtime/src/object.c +49 -2
- package/runtime/src/os.c +16 -6
- package/runtime/src/os.h +2 -0
- package/runtime/src/process.c +97 -10
package/node/prelude/http.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
// Not yet: the client, headersTimeout and requestTimeout, trailers.
|
|
10
10
|
import { hasInvalidHeaderChar, isHttpToken } from "./internal/http_chars.ts";
|
|
11
11
|
import {
|
|
12
|
-
httpAttach, httpBusy, httpDate, httpPending, httpWatchDrain, httpDetach, httpExpired, httpFedClose, httpFedOpen, httpFeed, httpFlush, httpParsers, httpWrite, timerUnref,
|
|
12
|
+
httpAttach, httpBusy, httpDate, httpPending, httpWatchDrain, httpWritten, httpDetach, httpExpired, httpFedClose, httpFedOpen, httpFeed, httpFlush, httpParsers, httpWrite, timerUnref,
|
|
13
13
|
} from "visaljs:host";
|
|
14
|
+
import { channel } from "./diagnostics_channel.js";
|
|
14
15
|
import { bindContext, currentContext, runInContext } from "./internal/async_context.js";
|
|
15
16
|
import { codeError, invalidArgType, invalidArgValue, outOfRange, validateInteger, validateString } from "./internal/errors.js";
|
|
16
17
|
import { Agent, globalAgent, setGlobalAgent } from "./internal/http_agent.js";
|
|
@@ -19,6 +20,14 @@ import { createConnection, Server as NetServer } from "./net.js";
|
|
|
19
20
|
import { addAbortSignal, Readable, Stream } from "./stream.js";
|
|
20
21
|
import { callableClass } from "./internal/features.js";
|
|
21
22
|
|
|
23
|
+
const onRequestStartChannel = channel("http.server.request.start");
|
|
24
|
+
const onResponseCreatedChannel = channel("http.server.response.created");
|
|
25
|
+
const onResponseFinishChannel = channel("http.server.response.finish");
|
|
26
|
+
const onClientRequestCreatedChannel = channel("http.client.request.created");
|
|
27
|
+
const onClientRequestStartChannel = channel("http.client.request.start");
|
|
28
|
+
const onClientRequestErrorChannel = channel("http.client.request.error");
|
|
29
|
+
const onClientResponseFinishChannel = channel("http.client.response.finish");
|
|
30
|
+
|
|
22
31
|
export const METHODS = ["ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "GET", "HEAD", "LINK", "LOCK", "M-SEARCH", "MERGE",
|
|
23
32
|
"MKACTIVITY", "MKCALENDAR", "MKCOL", "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", "PURGE", "PUT", "QUERY",
|
|
24
33
|
"REBIND", "REPORT", "SEARCH", "SOURCE", "SUBSCRIBE", "TRACE", "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE"];
|
|
@@ -65,7 +74,8 @@ function chunkLength(chunk) {
|
|
|
65
74
|
/* A request: a Readable of the body's Buffers, pushed as llhttp parses them. */
|
|
66
75
|
export class IncomingMessage extends Readable {
|
|
67
76
|
constructor(socket) {
|
|
68
|
-
|
|
77
|
+
const highWaterMark = socket?._readableState?.highWaterMark;
|
|
78
|
+
super(highWaterMark !== undefined && highWaterMark !== 65536 ? { highWaterMark } : undefined); /* the socket's, as Node's */
|
|
69
79
|
this.socket = socket;
|
|
70
80
|
this.httpVersionMajor = 1;
|
|
71
81
|
this.httpVersionMinor = 1;
|
|
@@ -89,6 +99,31 @@ export class IncomingMessage extends Readable {
|
|
|
89
99
|
return this.socket;
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
set connection(value) {
|
|
103
|
+
this.socket = value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_addHeaderLine(field, value, dest) {
|
|
107
|
+
addHeader(dest, field, value, this.joinDuplicateHeaders);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Node's: each name's values as they came, made when first asked for */
|
|
111
|
+
get headersDistinct() {
|
|
112
|
+
return (this._headersDistinct ??= distinct(this.rawHeaders));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
set headersDistinct(value) {
|
|
116
|
+
this._headersDistinct = value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get trailersDistinct() {
|
|
120
|
+
return (this._trailersDistinct ??= distinct(this.rawTrailers));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
set trailersDistinct(value) {
|
|
124
|
+
this._trailersDistinct = value;
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
_read() {
|
|
93
128
|
this._consuming = true;
|
|
94
129
|
}
|
|
@@ -166,6 +201,16 @@ const SINGLE = new Set(["age", "authorization", "content-length", "content-type"
|
|
|
166
201
|
"if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer",
|
|
167
202
|
"retry-after", "server", "user-agent"]);
|
|
168
203
|
|
|
204
|
+
function distinct(raw) {
|
|
205
|
+
const dest = { __proto__: null };
|
|
206
|
+
for (let i = 0; i < raw.length; i += 2) {
|
|
207
|
+
const key = raw[i].toLowerCase();
|
|
208
|
+
if (dest[key]) dest[key].push(raw[i + 1]);
|
|
209
|
+
else dest[key] = [raw[i + 1]];
|
|
210
|
+
}
|
|
211
|
+
return dest;
|
|
212
|
+
}
|
|
213
|
+
|
|
169
214
|
function addHeader(headers, name, value, join) {
|
|
170
215
|
const key = name.toLowerCase();
|
|
171
216
|
const existing = headers[key];
|
|
@@ -196,6 +241,68 @@ export function validateHeaderValue(name, value) {
|
|
|
196
241
|
if (hasInvalidHeaderChar(String(value))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"" + name + "\"]");
|
|
197
242
|
}
|
|
198
243
|
|
|
244
|
+
/* Node's processHeader: an array is a line each, but cookies and the unique headers are one, joined by "; " */
|
|
245
|
+
function processHeader(msg, state, key, value, validate) {
|
|
246
|
+
if (validate) validateHeaderName(key);
|
|
247
|
+
if (Array.isArray(value)) {
|
|
248
|
+
const lower = key.toLowerCase();
|
|
249
|
+
if ((value.length < 2 || lower !== "cookie") && !msg._uniqueHeaders?.has(lower)) {
|
|
250
|
+
for (const v of value) storeHeader(msg, state, key, v, validate);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
value = value.join("; ");
|
|
254
|
+
}
|
|
255
|
+
storeHeader(msg, state, key, value, validate);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function storeHeader(msg, state, key, value, validate) {
|
|
259
|
+
if (validate) validateHeaderValue(key, value);
|
|
260
|
+
state.header += key + ": " + value + "\r\n";
|
|
261
|
+
switch (key.toLowerCase()) {
|
|
262
|
+
case "connection":
|
|
263
|
+
state.connection = true;
|
|
264
|
+
msg._removedConnection = false;
|
|
265
|
+
if (/(?:^|\W)close(?:$|\W)/i.test(value)) msg._last = true;
|
|
266
|
+
else msg.shouldKeepAlive = true;
|
|
267
|
+
break;
|
|
268
|
+
case "transfer-encoding":
|
|
269
|
+
state.te = true;
|
|
270
|
+
msg._removedTE = false;
|
|
271
|
+
if (/(?:^|\W)chunked(?:$|\W)/i.test(value)) msg.chunkedEncoding = true;
|
|
272
|
+
break;
|
|
273
|
+
case "content-length":
|
|
274
|
+
state.contLen = true;
|
|
275
|
+
msg._contentLength = +value;
|
|
276
|
+
msg._removedContLen = false;
|
|
277
|
+
break;
|
|
278
|
+
case "date":
|
|
279
|
+
case "expect":
|
|
280
|
+
case "trailer":
|
|
281
|
+
state[key.toLowerCase()] = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Node's parseUniqueHeadersOption */
|
|
287
|
+
function uniqueHeadersOption(headers) {
|
|
288
|
+
return Array.isArray(headers) ? new Set(headers.map((h) => h.toLowerCase())) : null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Node's validateLinkHeaderValue: one link, or links joined with ", " */
|
|
292
|
+
const linkValue = /^(?:<[^>\r\n]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/;
|
|
293
|
+
function linkHeaderValue(hints) {
|
|
294
|
+
const check = (link) => {
|
|
295
|
+
if (typeof link !== "string" || !linkValue.test(link)) {
|
|
296
|
+
throw invalidArgValue("hints", "must be an array or string of format \"</styles.css>; rel=preload; as=style\"", link);
|
|
297
|
+
}
|
|
298
|
+
return link;
|
|
299
|
+
};
|
|
300
|
+
if (typeof hints === "string") return check(hints);
|
|
301
|
+
if (Array.isArray(hints)) return hints.map(check).join(", ");
|
|
302
|
+
throw invalidArgValue("hints", "must be an array or string of format \"</styles.css>; rel=preload; as=style\"", hints);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const bodyNotAllowed = () => codeError(Error, "ERR_HTTP_BODY_NOT_ALLOWED", "Adding content for this request method or response status is not allowed.");
|
|
199
306
|
const headersSent = (what) => codeError(Error, "ERR_HTTP_HEADERS_SENT", "Cannot " + what + " headers after they are sent to the client");
|
|
200
307
|
|
|
201
308
|
/* Node's write_: a chunk is a string or bytes */
|
|
@@ -365,14 +472,19 @@ export class OutgoingMessage extends Stream {
|
|
|
365
472
|
}
|
|
366
473
|
|
|
367
474
|
hasHeader(name) {
|
|
475
|
+
validateString(name, "name");
|
|
368
476
|
return this._headers.has(String(name).toLowerCase());
|
|
369
477
|
}
|
|
370
478
|
|
|
371
479
|
removeHeader(name) {
|
|
372
480
|
validateString(name, "name");
|
|
373
481
|
if (this._header || this.headersSent) throw headersSent("remove");
|
|
374
|
-
|
|
375
|
-
|
|
482
|
+
const key = name.toLowerCase();
|
|
483
|
+
if (key === "connection") this._removedConnection = true;
|
|
484
|
+
else if (key === "content-length") this._removedContLen = true;
|
|
485
|
+
else if (key === "transfer-encoding") this._removedTE = true;
|
|
486
|
+
else if (key === "date") this.sendDate = false;
|
|
487
|
+
this._headers.delete(key);
|
|
376
488
|
}
|
|
377
489
|
|
|
378
490
|
getHeaderNames() {
|
|
@@ -391,51 +503,52 @@ export class OutgoingMessage extends Stream {
|
|
|
391
503
|
|
|
392
504
|
/* Node's OutgoingMessage, as a ClientRequest (and an OutgoingMessage made by hand) writes: the
|
|
393
505
|
* head stored, then the bytes through the socket (ServerResponse has its own, on llhttp's
|
|
394
|
-
* connection). _storeHeader: the first line, the headers as set (or as given
|
|
506
|
+
* connection). Node's _storeHeader: the first line, the headers as set (or as given), then Date,
|
|
395
507
|
* Connection and the body's framing */
|
|
396
|
-
_storeHeader(firstLine,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
} else
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (/chunked/i.test(String(value))) this.chunkedEncoding = true;
|
|
409
|
-
} else if (key === "trailer") hasTrailer = true;
|
|
410
|
-
if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
|
|
411
|
-
else head += name + ": " + value + "\r\n";
|
|
412
|
-
};
|
|
413
|
-
if (headersArray) {
|
|
414
|
-
if (Array.isArray(headersArray[0])) for (const [name, value] of headersArray) add(name, value);
|
|
415
|
-
else for (let i = 0; i < headersArray.length; i += 2) add(headersArray[i], headersArray[i + 1]);
|
|
508
|
+
_storeHeader(firstLine, headers) {
|
|
509
|
+
const state = { connection: false, contLen: false, te: false, date: false, expect: false, trailer: false, header: firstLine };
|
|
510
|
+
if (headers) {
|
|
511
|
+
if (Array.isArray(headers)) {
|
|
512
|
+
if (headers.length && Array.isArray(headers[0])) for (const [name, value] of headers) processHeader(this, state, name, value, true);
|
|
513
|
+
else {
|
|
514
|
+
if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
|
|
515
|
+
for (let i = 0; i < headers.length; i += 2) processHeader(this, state, headers[i], headers[i + 1], true);
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
for (const key of Object.keys(headers)) processHeader(this, state, key, headers[key], true);
|
|
519
|
+
}
|
|
416
520
|
} else {
|
|
417
|
-
for (const [, [name, value]] of this._headers)
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
521
|
+
for (const [, [name, value]] of this._headers) processHeader(this, state, name, value, false);
|
|
522
|
+
}
|
|
523
|
+
let head = state.header;
|
|
524
|
+
if (this.sendDate && !state.date) head += "Date: " + httpDate() + "\r\n";
|
|
525
|
+
if (this._removedConnection) {
|
|
526
|
+
this._last = !this.shouldKeepAlive;
|
|
527
|
+
} else if (!state.connection) {
|
|
528
|
+
const shouldSendKeepAlive = this.shouldKeepAlive && (state.contLen || this.useChunkedEncodingByDefault || this.agent);
|
|
421
529
|
if (shouldSendKeepAlive) head += "Connection: keep-alive\r\n";
|
|
422
530
|
else {
|
|
423
531
|
this._last = true;
|
|
424
532
|
head += "Connection: close\r\n";
|
|
425
533
|
}
|
|
426
534
|
}
|
|
427
|
-
if (!
|
|
535
|
+
if (!state.contLen && !state.te) {
|
|
428
536
|
if (!this._hasBody) this.chunkedEncoding = false;
|
|
429
537
|
else if (!this.useChunkedEncodingByDefault) this._last = true;
|
|
430
|
-
else if (!
|
|
431
|
-
else {
|
|
538
|
+
else if (!state.trailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
|
|
539
|
+
else if (!this._removedTE) {
|
|
432
540
|
head += "Transfer-Encoding: chunked\r\n";
|
|
433
541
|
this.chunkedEncoding = true;
|
|
542
|
+
} else {
|
|
543
|
+
this._last = true; /* neither length nor chunks: the body ends with the connection */
|
|
434
544
|
}
|
|
435
545
|
}
|
|
546
|
+
if (this.chunkedEncoding !== true && state.trailer) throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
|
|
436
547
|
this._header = head + "\r\n";
|
|
437
548
|
this._headerSent = false;
|
|
438
549
|
this.headersSent = true;
|
|
550
|
+
/* as Node's: the head goes now when the server is to answer 100 Continue before the body */
|
|
551
|
+
if (state.expect) this._send("");
|
|
439
552
|
}
|
|
440
553
|
|
|
441
554
|
/* bytes for the socket: written now, or kept until it's assigned */
|
|
@@ -561,7 +674,6 @@ export class OutgoingMessage extends Stream {
|
|
|
561
674
|
else process.nextTick(finish);
|
|
562
675
|
this.finished = true;
|
|
563
676
|
this.writableEnded = true;
|
|
564
|
-
this.writable = false;
|
|
565
677
|
return this;
|
|
566
678
|
}
|
|
567
679
|
|
|
@@ -581,7 +693,8 @@ export class OutgoingMessage extends Stream {
|
|
|
581
693
|
const entries = Array.isArray(headers) ? headers : Object.keys(headers).map((key) => [key, headers[key]]);
|
|
582
694
|
for (const [field, value] of entries) {
|
|
583
695
|
validateHeaderName(field, "Trailer name");
|
|
584
|
-
const
|
|
696
|
+
const several = Array.isArray(value) && value.length > 1 && !this._uniqueHeaders?.has(field.toLowerCase());
|
|
697
|
+
const values = several ? value : [Array.isArray(value) ? value.join("; ") : value];
|
|
585
698
|
for (const v of values) {
|
|
586
699
|
if (hasInvalidHeaderChar(String(v))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in trailer content [\"" + field + "\"]");
|
|
587
700
|
this._trailer += field + ": " + v + "\r\n";
|
|
@@ -590,6 +703,12 @@ export class OutgoingMessage extends Stream {
|
|
|
590
703
|
}
|
|
591
704
|
}
|
|
592
705
|
|
|
706
|
+
/* Node's fields that are rarely set: defaults on the prototype keep them off every instance */
|
|
707
|
+
Object.assign(OutgoingMessage.prototype, {
|
|
708
|
+
_removedConnection: false, _removedTE: false, strictContentLength: false, _uniqueHeaders: null, maxRequestsOnConnectionReached: false,
|
|
709
|
+
_maxRequestsPerSocket: 0, _rejectNonStandardBodyWrites: false,
|
|
710
|
+
});
|
|
711
|
+
|
|
593
712
|
/* ---- ServerResponse ------------------------------------------------------------------------- */
|
|
594
713
|
|
|
595
714
|
export class ServerResponse extends OutgoingMessage {
|
|
@@ -607,13 +726,68 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
607
726
|
/* one made by hand (new ServerResponse(req)) is kept alive, without a Keep-Alive timeout, as Node's */
|
|
608
727
|
this._keepAlive = keepAlive ?? true;
|
|
609
728
|
this._keepAliveTimeout = server ? server.keepAliveTimeout : 0;
|
|
729
|
+
/* only a server that sets them: each field an instance gets costs every response */
|
|
730
|
+
if (server?.maxRequestsPerSocket) this._maxRequestsPerSocket = server.maxRequestsPerSocket;
|
|
731
|
+
if (server?._uniqueHeaders) this._uniqueHeaders = server._uniqueHeaders;
|
|
732
|
+
if (server?.rejectNonStandardBodyWrites) this._rejectNonStandardBodyWrites = true;
|
|
610
733
|
this._head = req.method === "HEAD";
|
|
734
|
+
if (onResponseCreatedChannel.hasSubscribers) onResponseCreatedChannel.publish({ request: req, response: this });
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/* a 1xx response before the final one, as Node's */
|
|
738
|
+
writeInformation(statusCode, headers, callback) {
|
|
739
|
+
if (this.headersSent) throw headersSent("write");
|
|
740
|
+
validateInteger(statusCode, "statusCode");
|
|
741
|
+
if (statusCode < 100 || statusCode > 199) throw outOfRange("statusCode", ">= 100 && <= 199", statusCode);
|
|
742
|
+
if (statusCode === 101) throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${statusCode}`);
|
|
743
|
+
let head = "HTTP/1.1 " + statusCode + " " + (STATUS_CODES[statusCode] || "unknown") + "\r\n";
|
|
744
|
+
const line = (name, value) => {
|
|
745
|
+
validateHeaderName(name);
|
|
746
|
+
validateHeaderValue(name, value);
|
|
747
|
+
head += name + ": " + value + "\r\n";
|
|
748
|
+
};
|
|
749
|
+
if (Array.isArray(headers)) {
|
|
750
|
+
if (headers.length && Array.isArray(headers[0])) for (const [name, value] of headers) line(name, value);
|
|
751
|
+
else {
|
|
752
|
+
if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
|
|
753
|
+
for (let i = 0; i < headers.length; i += 2) line(headers[i], headers[i + 1]);
|
|
754
|
+
}
|
|
755
|
+
} else if (headers !== undefined && headers !== null) {
|
|
756
|
+
if (typeof headers !== "object") throw invalidArgType("headers", "of type object", headers);
|
|
757
|
+
for (const name of Object.keys(headers)) line(name, headers[name]);
|
|
758
|
+
}
|
|
759
|
+
this._send(head + "\r\n", true);
|
|
760
|
+
if (typeof callback === "function") queueMicrotask(callback);
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
writeContinue(callback) {
|
|
765
|
+
this.writeInformation(100, null, callback);
|
|
766
|
+
this._sent100 = true;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
writeProcessing(callback) {
|
|
770
|
+
this.writeInformation(102, null, callback);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
writeEarlyHints(hints, callback) {
|
|
774
|
+
if (typeof hints !== "object" || hints === null || Array.isArray(hints)) throw invalidArgType("hints", "of type object", hints);
|
|
775
|
+
if (hints.link === null || hints.link === undefined) return;
|
|
776
|
+
const link = linkHeaderValue(hints.link);
|
|
777
|
+
if (link.length === 0) return;
|
|
778
|
+
if (hasInvalidHeaderChar(link)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"Link\"]");
|
|
779
|
+
const headers = { __proto__: null, Link: link };
|
|
780
|
+
for (const key of Object.keys(hints)) if (key !== "link") headers[key] = hints[key];
|
|
781
|
+
this.writeInformation(103, headers, callback);
|
|
611
782
|
}
|
|
612
783
|
|
|
613
784
|
/* the response's bytes: to the connection's parser, which corks them; or, for a response made by
|
|
614
785
|
* hand (new ServerResponse(req), Node's assignSocket), to its socket, kept until it has one */
|
|
615
786
|
_send(data, latin1 = false) {
|
|
616
|
-
if (this._queued)
|
|
787
|
+
if (this._queued) { /* behind the connection's earlier responses: Node's outputData */
|
|
788
|
+
(this._unsent ??= []).push([data, latin1]);
|
|
789
|
+
this.outputSize += data.length;
|
|
790
|
+
}
|
|
617
791
|
else if (this._conn !== undefined) httpWrite(this._conn, data, latin1);
|
|
618
792
|
else if (this.socket) this.socket.write(data, latin1 ? "latin1" : undefined);
|
|
619
793
|
else (this._unsent ??= []).push([data, latin1]);
|
|
@@ -664,7 +838,6 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
664
838
|
if (this.destroyed) return this;
|
|
665
839
|
this.destroyed = true;
|
|
666
840
|
this._errored = error;
|
|
667
|
-
this.writable = false;
|
|
668
841
|
this._closeEmitted = true;
|
|
669
842
|
if (this.socket) this.socket.destroy();
|
|
670
843
|
queueMicrotask(() => {
|
|
@@ -679,11 +852,11 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
679
852
|
if (this._closeEmitted) return;
|
|
680
853
|
this._closeEmitted = true;
|
|
681
854
|
this.destroyed = true;
|
|
682
|
-
this.writable = false;
|
|
683
855
|
queueMicrotask(() => this.emit("close"));
|
|
684
856
|
}
|
|
685
857
|
|
|
686
858
|
writeHead(statusCode, statusMessage, headers) {
|
|
859
|
+
if (this.headersSent) throw headersSent("write");
|
|
687
860
|
/* as Node's: an integer status from 100 to 999 */
|
|
688
861
|
const originalStatusCode = statusCode;
|
|
689
862
|
statusCode |= 0;
|
|
@@ -692,12 +865,16 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
692
865
|
throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${shown}`);
|
|
693
866
|
}
|
|
694
867
|
if (typeof statusMessage !== "string") {
|
|
695
|
-
headers
|
|
868
|
+
headers ??= statusMessage;
|
|
696
869
|
statusMessage = undefined;
|
|
697
870
|
}
|
|
698
871
|
this.statusCode = statusCode;
|
|
699
872
|
if (statusMessage !== undefined) this.statusMessage = statusMessage;
|
|
700
|
-
|
|
873
|
+
else this.statusMessage ||= STATUS_CODES[statusCode] || "unknown";
|
|
874
|
+
if (Array.isArray(headers) && Array.isArray(headers[0]) && this._headers.size === 0) {
|
|
875
|
+
/* as Node's when only writeHead sets headers: [[name, value], …] too */
|
|
876
|
+
for (const [name, value] of headers) this.appendHeader(name, value);
|
|
877
|
+
} else if (Array.isArray(headers)) {
|
|
701
878
|
/* as Node's: they replace the headers set before, and may repeat a name among themselves */
|
|
702
879
|
if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
|
|
703
880
|
for (let i = 0; i < headers.length; i += 2) if (this.hasHeader(headers[i])) this.removeHeader(headers[i]);
|
|
@@ -707,6 +884,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
707
884
|
}
|
|
708
885
|
/* Node renders the head now (sent with the first write): without a known length, the body
|
|
709
886
|
* is chunked even when end() brings all of it */
|
|
887
|
+
if (this.statusMessage && hasInvalidHeaderChar(this.statusMessage)) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in statusMessage");
|
|
710
888
|
if (!this.headersSent) this._pendingHead = this._renderHead(this._implicitLength);
|
|
711
889
|
return this;
|
|
712
890
|
}
|
|
@@ -723,42 +901,57 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
723
901
|
_renderHead(length) {
|
|
724
902
|
const status = this.statusCode;
|
|
725
903
|
const noBody = status === 204 || status === 304 || (status >= 100 && status < 200);
|
|
904
|
+
if (noBody) this._hasBody = false;
|
|
726
905
|
/* an empty statusMessage gets the standard one, as in Node (Next.js's route handlers set "") */
|
|
727
906
|
let head = "HTTP/1.1 " + status + " " + (this.statusMessage || STATUS_CODES[status] || "unknown") + "\r\n";
|
|
728
|
-
for (const [, [name, value]] of this._headers) {
|
|
729
|
-
if (Array.isArray(value))
|
|
730
|
-
else head += name + ": " + value + "\r\n";
|
|
907
|
+
for (const [key, [name, value]] of this._headers) {
|
|
908
|
+
if (!Array.isArray(value)) head += name + ": " + value + "\r\n";
|
|
909
|
+
else if (this._uniqueHeaders?.has(key)) head += name + ": " + value.join("; ") + "\r\n";
|
|
910
|
+
else for (const v of value) head += name + ": " + v + "\r\n";
|
|
731
911
|
}
|
|
732
912
|
if (this.sendDate && !this._headers.has("date")) head += "Date: " + httpDate() + "\r\n";
|
|
733
|
-
const connection = this._headers.get("connection");
|
|
734
|
-
if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
|
|
735
|
-
if (connection === undefined) {
|
|
736
|
-
if (!this._keepAlive) head += "Connection: close\r\n";
|
|
737
|
-
else {
|
|
738
|
-
head += "Connection: keep-alive\r\n";
|
|
739
|
-
if (this._keepAliveTimeout) head += "Keep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
913
|
/* Node: no length for HEAD; an HTTP/1.0 client gets a body that ends with the connection */
|
|
743
914
|
const http10 = this.req.httpVersionMajor < 1 || this.req.httpVersionMinor < 1;
|
|
744
|
-
/* a Transfer-Encoding: chunked the program set: the body is chunk-encoded, as Node does
|
|
915
|
+
/* a Transfer-Encoding: chunked the program set: the body is chunk-encoded, as Node does; on a
|
|
916
|
+
* 204 or 304 there's no body, and the connection closes */
|
|
745
917
|
const te = this._headers.get("transfer-encoding");
|
|
746
|
-
if (te !== undefined && /\bchunked\b/i.test(String(te[1]))
|
|
747
|
-
|
|
918
|
+
if (te !== undefined && /\bchunked\b/i.test(String(te[1]))) {
|
|
919
|
+
if (status === 204 || status === 304) this._keepAlive = false;
|
|
920
|
+
else if (!noBody) this.chunkedEncoding = true;
|
|
921
|
+
}
|
|
922
|
+
const hasTrailer = this._headers.has("trailer");
|
|
923
|
+
let framing = "";
|
|
924
|
+
if (!noBody && this._hasBody && !http10 && !this._headers.has("content-length") && te === undefined) {
|
|
748
925
|
if (this._contentLength !== null && this._contentLength !== undefined) length = this._contentLength;
|
|
749
|
-
if (length !== undefined) {
|
|
750
|
-
|
|
751
|
-
} else {
|
|
752
|
-
|
|
926
|
+
if (length !== undefined && !this._removedContLen && !hasTrailer) {
|
|
927
|
+
framing = "Content-Length: " + length + "\r\n";
|
|
928
|
+
} else if (!this._removedTE) {
|
|
929
|
+
framing = "Transfer-Encoding: chunked\r\n";
|
|
753
930
|
this.chunkedEncoding = true;
|
|
931
|
+
} else {
|
|
932
|
+
this._keepAlive = false; /* neither length nor chunks: the body ends with the connection */
|
|
754
933
|
}
|
|
755
934
|
}
|
|
935
|
+
const connection = this._headers.get("connection");
|
|
936
|
+
if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
|
|
937
|
+
if (connection === undefined && !this._removedConnection) {
|
|
938
|
+
const limit = this._maxRequestsPerSocket;
|
|
939
|
+
if (limit > 0 && this.maxRequestsOnConnectionReached) this._keepAlive = false;
|
|
940
|
+
if (!this._keepAlive) head += "Connection: close\r\n";
|
|
941
|
+
else if (!this._keepAliveTimeout || this._headers.has("keep-alive")) head += "Connection: keep-alive\r\n";
|
|
942
|
+
else if (limit > 0) head += "Connection: keep-alive\r\nKeep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + ", max=" + limit + "\r\n";
|
|
943
|
+
else head += "Connection: keep-alive\r\nKeep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
|
|
944
|
+
}
|
|
945
|
+
if (framing !== "") head += framing;
|
|
756
946
|
/* as Node's _storeHeader: only a chunked body can have trailers (the head isn't sent) */
|
|
757
|
-
if (this.chunkedEncoding !== true &&
|
|
947
|
+
if (this.chunkedEncoding !== true && hasTrailer) {
|
|
758
948
|
throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
|
|
759
949
|
}
|
|
760
950
|
this.headersSent = true;
|
|
761
|
-
|
|
951
|
+
/* as Node's: middleware such as compression tests _header, not headersSent, before calling
|
|
952
|
+
* _implicitHeader, which throws once the head is rendered */
|
|
953
|
+
this._header = head + "\r\n";
|
|
954
|
+
return this._header;
|
|
762
955
|
}
|
|
763
956
|
|
|
764
957
|
write(chunk, encoding, callback) {
|
|
@@ -771,9 +964,11 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
771
964
|
const e = new Error(ended ? "write after end" : "Cannot call write after a stream was destroyed");
|
|
772
965
|
e.code = ended ? "ERR_STREAM_WRITE_AFTER_END" : "ERR_STREAM_DESTROYED";
|
|
773
966
|
if (!this.destroyed) {
|
|
967
|
+
/* Node's emitErrorNt asks again on the next tick, but this response is destroyed sooner
|
|
968
|
+
* than Node's (its 'close' doesn't wait for the socket's write) */
|
|
774
969
|
process.nextTick(() => {
|
|
775
970
|
if (typeof callback === "function") callback(e);
|
|
776
|
-
|
|
971
|
+
this.emit("error", e);
|
|
777
972
|
});
|
|
778
973
|
} else if (typeof callback === "function") {
|
|
779
974
|
process.nextTick(callback, e);
|
|
@@ -781,11 +976,13 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
781
976
|
return false;
|
|
782
977
|
}
|
|
783
978
|
if (typeof chunk === "string" && typeof encoding === "string" && encoding !== "utf8" && encoding !== "utf-8") chunk = Buffer.from(chunk, encoding);
|
|
979
|
+
if (this.strictContentLength) this._countStrict(chunk, false);
|
|
784
980
|
/* a writeHead of the response's own (a wrapper) sees the implicit head, as in Node */
|
|
785
981
|
if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(undefined);
|
|
786
982
|
const head = this._takeHead(undefined);
|
|
787
983
|
if (head) this._send(head, true);
|
|
788
|
-
if (
|
|
984
|
+
if (!this._hasBody && this._rejectNonStandardBodyWrites) throw bodyNotAllowed();
|
|
985
|
+
if (chunk !== undefined && chunk !== null && chunkLength(chunk) > 0 && this._hasBody) {
|
|
789
986
|
if (this.chunkedEncoding) {
|
|
790
987
|
this._send(chunkLength(chunk).toString(16) + "\r\n");
|
|
791
988
|
this._send(chunk);
|
|
@@ -802,6 +999,22 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
802
999
|
return this._pendingBytes();
|
|
803
1000
|
}
|
|
804
1001
|
|
|
1002
|
+
/* Node's strictContentLength: the body's bytes against the Content-Length set, checked as written
|
|
1003
|
+
* and, at end(), for all of it */
|
|
1004
|
+
_countStrict(chunk, fromEnd) {
|
|
1005
|
+
const len = chunk === undefined || chunk === null ? 0 : chunkLength(chunk);
|
|
1006
|
+
const set = this._headers.get("content-length");
|
|
1007
|
+
const declared = set !== undefined ? +set[1] : this._contentLength;
|
|
1008
|
+
const written = this._bytesWritten ?? 0;
|
|
1009
|
+
const strict = declared !== null && declared !== undefined && this._hasBody && !this._removedContLen && !this.chunkedEncoding &&
|
|
1010
|
+
!this._headers.has("transfer-encoding");
|
|
1011
|
+
if (strict && (fromEnd ? written + len !== declared : written + len > declared)) {
|
|
1012
|
+
throw codeError(Error, "ERR_HTTP_CONTENT_LENGTH_MISMATCH",
|
|
1013
|
+
`Response body's content-length of ${written + len} byte(s) does not match the content-length of ${declared} byte(s) set in header`);
|
|
1014
|
+
}
|
|
1015
|
+
if (!fromEnd) this._bytesWritten = written + len; /* end()'s chunk is counted by the write() it makes */
|
|
1016
|
+
}
|
|
1017
|
+
|
|
805
1018
|
/* bytes written that haven't gone out: corked in C and in libuv's queue, or a TLS socket's */
|
|
806
1019
|
_pendingBytes() {
|
|
807
1020
|
if (this._conn === undefined || this._queued) return 0;
|
|
@@ -841,31 +1054,40 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
841
1054
|
if (chunk !== undefined && chunk !== null) validateChunk(chunk);
|
|
842
1055
|
if (this.writableEnded) return this;
|
|
843
1056
|
if (typeof chunk === "string" && typeof encoding === "string" && encoding !== "utf8" && encoding !== "utf-8") chunk = Buffer.from(chunk, encoding);
|
|
1057
|
+
if (this.strictContentLength) this._countStrict(chunk, true);
|
|
844
1058
|
if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(chunk !== undefined && chunk !== null ? chunkLength(chunk) : 0);
|
|
845
1059
|
if (!this.headersSent) {
|
|
846
1060
|
const hasChunk = chunk !== undefined && chunk !== null;
|
|
847
1061
|
const length = hasChunk ? chunkLength(chunk) : 0;
|
|
848
|
-
|
|
1062
|
+
const head = this._renderHead(length);
|
|
1063
|
+
if (this.chunkedEncoding) { /* Transfer-Encoding or a Trailer set: the one chunk, then the last */
|
|
1064
|
+
this._send(head, true);
|
|
1065
|
+
if (length && this._hasBody) {
|
|
1066
|
+
this._send(length.toString(16) + "\r\n");
|
|
1067
|
+
this._send(chunk);
|
|
1068
|
+
this._send("\r\n");
|
|
1069
|
+
}
|
|
1070
|
+
if (this._hasBody) this._send("0\r\n" + this._trailer + "\r\n", true);
|
|
1071
|
+
} else if (hasChunk && typeof chunk === "string" && this._hasBody) {
|
|
849
1072
|
/* the head's bytes are latin1, the body's UTF-8: corked into one write by the parser's
|
|
850
1073
|
* connection; a response made by hand writes them as one string, as Node's _send */
|
|
851
1074
|
if (this._conn === undefined) {
|
|
852
|
-
this._send(
|
|
1075
|
+
this._send(head + chunk);
|
|
853
1076
|
} else {
|
|
854
|
-
this._send(
|
|
1077
|
+
this._send(head, true);
|
|
855
1078
|
this._send(chunk);
|
|
856
1079
|
}
|
|
857
1080
|
} else {
|
|
858
|
-
this._send(
|
|
859
|
-
if (hasChunk && length &&
|
|
1081
|
+
this._send(head, true);
|
|
1082
|
+
if (hasChunk && length && this._hasBody) this._send(chunk);
|
|
860
1083
|
}
|
|
861
1084
|
} else {
|
|
862
1085
|
if (chunk !== undefined && chunk !== null) this.write(chunk);
|
|
863
1086
|
const head = this._takeHead(undefined);
|
|
864
1087
|
if (head) this._send(head, true);
|
|
865
|
-
if (this.chunkedEncoding &&
|
|
1088
|
+
if (this.chunkedEncoding && this._hasBody) this._send("0\r\n" + this._trailer + "\r\n", true);
|
|
866
1089
|
}
|
|
867
|
-
this.writableEnded = this.finished = true;
|
|
868
|
-
this.writable = false;
|
|
1090
|
+
this.writableEnded = this.finished = true; /* writable stays true, as Node's (nodejs/node#15029) */
|
|
869
1091
|
if (this._queued) { /* finished once it has gone out, after the responses before it */
|
|
870
1092
|
this._endCallback = callback;
|
|
871
1093
|
return this;
|
|
@@ -877,6 +1099,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
877
1099
|
/* a response that waited behind the connection's earlier ones: its turn (Node's assignSocket) */
|
|
878
1100
|
_activate() {
|
|
879
1101
|
this._queued = false;
|
|
1102
|
+
this.outputSize = 0;
|
|
880
1103
|
const unsent = this._unsent;
|
|
881
1104
|
this._unsent = undefined;
|
|
882
1105
|
if (unsent) for (const [data, latin1] of unsent) httpWrite(this._conn, data, latin1);
|
|
@@ -886,6 +1109,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
886
1109
|
/* the response is all written: the socket ends after the last one (not kept alive, or the client
|
|
887
1110
|
* ended its side), then 'finish' and 'close' */
|
|
888
1111
|
_afterEnd(callback) {
|
|
1112
|
+
if (this.destroyed) return this; /* its connection closed: no 'finish', as Node's (the socket's write never completes) */
|
|
889
1113
|
/* the response is all written: sent now, so that by 'finish' it has gone to the socket, as Node's */
|
|
890
1114
|
if (this._conn !== undefined) httpFlush(this._conn);
|
|
891
1115
|
if ((!this._keepAlive || this._last) && this.socket) { /* the response goes out, then the socket ends */
|
|
@@ -954,6 +1178,16 @@ class ServerClass extends NetServer {
|
|
|
954
1178
|
this.insecureHTTPParser = boolean("insecureHTTPParser", undefined);
|
|
955
1179
|
this.requireHostHeader = boolean("requireHostHeader", true);
|
|
956
1180
|
this.joinDuplicateHeaders = boolean("joinDuplicateHeaders", undefined);
|
|
1181
|
+
this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
|
|
1182
|
+
this.rejectNonStandardBodyWrites = boolean("rejectNonStandardBodyWrites", false);
|
|
1183
|
+
if (options.shouldUpgradeCallback !== undefined) {
|
|
1184
|
+
if (typeof options.shouldUpgradeCallback !== "function") throw invalidArgType("options.shouldUpgradeCallback", "of type function", options.shouldUpgradeCallback);
|
|
1185
|
+
this.shouldUpgradeCallback = options.shouldUpgradeCallback;
|
|
1186
|
+
} else {
|
|
1187
|
+
this.shouldUpgradeCallback = function () {
|
|
1188
|
+
return this.listenerCount("upgrade") > 0;
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
957
1191
|
/* the classes requests and responses are made of (subclasses of node:http's), as Node's */
|
|
958
1192
|
this._IncomingMessage = options.IncomingMessage || IncomingMessage;
|
|
959
1193
|
this._ServerResponse = options.ServerResponse || ServerResponse;
|
|
@@ -964,6 +1198,7 @@ class ServerClass extends NetServer {
|
|
|
964
1198
|
throw outOfRange("headersTimeout", "<= requestTimeout", this.headersTimeout);
|
|
965
1199
|
}
|
|
966
1200
|
this.keepAliveTimeout = integer("keepAliveTimeout", 5000);
|
|
1201
|
+
this.keepAliveTimeoutBuffer = integer("keepAliveTimeoutBuffer", 1000);
|
|
967
1202
|
this.connectionsCheckingInterval = integer("connectionsCheckingInterval", 30_000);
|
|
968
1203
|
this._parsers = -1;
|
|
969
1204
|
this._states = new Map(); /* connection → { socket, req, res, keepAliveTimeoutSet } */
|
|
@@ -1086,10 +1321,12 @@ class ServerClass extends NetServer {
|
|
|
1086
1321
|
const conn = connOf(socket);
|
|
1087
1322
|
if (conn === null) return;
|
|
1088
1323
|
socket.server = this; /* as Node's: the http server, even for a socket another server accepted */
|
|
1324
|
+
socket.setEncoding = socketSetEncoding;
|
|
1325
|
+
if (!isFed(socket)) socket._httpWritten = () => httpWritten(conn);
|
|
1089
1326
|
/* as Node's connection state: the request being parsed (req); those whose responses aren't
|
|
1090
1327
|
* finished (incoming); the response writing to the connection (res), and those waiting behind it
|
|
1091
1328
|
* (outgoing: pipelined requests are answered in order) */
|
|
1092
|
-
const state = { socket, conn, req: null, incoming: [], res: null, outgoing: [], keepAliveTimeoutSet: false, listeners: null };
|
|
1329
|
+
const state = { socket, conn, req: null, incoming: [], res: null, outgoing: [], keepAliveTimeoutSet: false, listeners: null, requestsCount: 0 };
|
|
1093
1330
|
this._states.set(conn, state);
|
|
1094
1331
|
if (this.timeout && typeof socket.setTimeout === "function") socket.setTimeout(this.timeout);
|
|
1095
1332
|
const onTimeout = () => this._onTimeout(state);
|
|
@@ -1159,8 +1396,11 @@ class ServerClass extends NetServer {
|
|
|
1159
1396
|
req.httpVersionMinor = minor;
|
|
1160
1397
|
req.httpVersion = major + "." + minor;
|
|
1161
1398
|
req.rawHeaders = raw;
|
|
1162
|
-
|
|
1163
|
-
|
|
1399
|
+
/* as Node's parser: maxHeadersCount's pairs (2000 entries when not a number, all for 0) */
|
|
1400
|
+
const pairs = typeof this.maxHeadersCount === "number" ? this.maxHeadersCount << 1 : 2000;
|
|
1401
|
+
const n = pairs > 0 && pairs < raw.length ? pairs : raw.length;
|
|
1402
|
+
for (let i = 0; i < n; i += 2) addHeader(req.headers, raw[i], raw[i + 1], this.joinDuplicateHeaders);
|
|
1403
|
+
req.upgrade = upgrade && (method === "CONNECT" || !!this.shouldUpgradeCallback(req));
|
|
1164
1404
|
if (req.upgrade) {
|
|
1165
1405
|
state.upgradeReq = req;
|
|
1166
1406
|
return 2;
|
|
@@ -1175,19 +1415,46 @@ class ServerClass extends NetServer {
|
|
|
1175
1415
|
} else {
|
|
1176
1416
|
state.res = res;
|
|
1177
1417
|
}
|
|
1178
|
-
res.on("finish", () => this._resOnFinish(state, req, res)); /* Node's resOnFinish, an `on` listener too */
|
|
1179
|
-
if (
|
|
1418
|
+
res.on("finish", () => this._resOnFinish(state, req, res, socket)); /* Node's resOnFinish, an `on` listener too */
|
|
1419
|
+
if (onRequestStartChannel.hasSubscribers) onRequestStartChannel.publish({ request: req, response: res, socket, server: this });
|
|
1420
|
+
if (this.requireHostHeader && major === 1 && minor === 1 && req.headers.host === undefined) { /* Node: a 400, and the connection ends */
|
|
1180
1421
|
res.writeHead(400, ["Connection", "close"]);
|
|
1181
1422
|
res.end();
|
|
1182
1423
|
return 0;
|
|
1183
1424
|
}
|
|
1184
|
-
this.
|
|
1425
|
+
const limited = typeof this.maxRequestsPerSocket === "number" && this.maxRequestsPerSocket > 0;
|
|
1426
|
+
if (limited) {
|
|
1427
|
+
state.requestsCount++;
|
|
1428
|
+
res.maxRequestsOnConnectionReached = this.maxRequestsPerSocket <= state.requestsCount;
|
|
1429
|
+
}
|
|
1430
|
+
if (limited && this.maxRequestsPerSocket < state.requestsCount) {
|
|
1431
|
+
this.emit("dropRequest", req, socket);
|
|
1432
|
+
res.writeHead(503);
|
|
1433
|
+
res.end();
|
|
1434
|
+
} else if (req.headers.expect !== undefined) {
|
|
1435
|
+
if (/(?:^|\W)100-continue(?:$|\W)/i.test(req.headers.expect)) {
|
|
1436
|
+
res._expect_continue = true;
|
|
1437
|
+
if (this.listenerCount("checkContinue") > 0) this.emit("checkContinue", req, res);
|
|
1438
|
+
else {
|
|
1439
|
+
res.writeContinue();
|
|
1440
|
+
this.emit("request", req, res);
|
|
1441
|
+
}
|
|
1442
|
+
} else if (this.listenerCount("checkExpectation") > 0) {
|
|
1443
|
+
this.emit("checkExpectation", req, res);
|
|
1444
|
+
} else {
|
|
1445
|
+
res.writeHead(417);
|
|
1446
|
+
res.end();
|
|
1447
|
+
}
|
|
1448
|
+
} else {
|
|
1449
|
+
this.emit("request", req, res);
|
|
1450
|
+
}
|
|
1185
1451
|
return 0;
|
|
1186
1452
|
}
|
|
1187
1453
|
|
|
1188
1454
|
/* Node's resOnFinish: the request is done with; the next response's turn, or the idle keep-alive
|
|
1189
1455
|
* timeout */
|
|
1190
|
-
_resOnFinish(state, req, res) {
|
|
1456
|
+
_resOnFinish(state, req, res, socket) {
|
|
1457
|
+
if (onResponseFinishChannel.hasSubscribers) onResponseFinishChannel.publish({ request: req, response: res, socket, server: this });
|
|
1191
1458
|
if (state.incoming[0] === req) state.incoming.shift();
|
|
1192
1459
|
else {
|
|
1193
1460
|
const i = state.incoming.indexOf(req);
|
|
@@ -1195,13 +1462,22 @@ class ServerClass extends NetServer {
|
|
|
1195
1462
|
}
|
|
1196
1463
|
if (state.req === req && req.complete) state.req = null;
|
|
1197
1464
|
if (state.res === res) state.res = null;
|
|
1465
|
+
/* as Node's: a request nobody read has its body discarded. Not when it's all parsed with
|
|
1466
|
+
* nothing buffered and nobody waits for its 'end' or 'close': a bodiless GET costs ~25k
|
|
1467
|
+
* instructions to drain */
|
|
1468
|
+
const readable = req._readableState, events = req._events;
|
|
1469
|
+
if (!req._consuming && !readable.resumeScheduled &&
|
|
1470
|
+
(!req.complete || readable.length > 0 || events.end !== undefined || events.close !== undefined)) req._dump();
|
|
1198
1471
|
if (res._last || !res._keepAlive) return; /* the socket is ending */
|
|
1199
1472
|
const next = state.outgoing.shift();
|
|
1200
1473
|
if (next) {
|
|
1201
1474
|
state.res = next;
|
|
1202
1475
|
next._activate();
|
|
1203
1476
|
} else if (this.keepAliveTimeout && state.socket.writable) {
|
|
1204
|
-
|
|
1477
|
+
/* as Node's: past the advertised timeout by keepAliveTimeoutBuffer, so the client closes first */
|
|
1478
|
+
const buffer = typeof this.keepAliveTimeoutBuffer === "number" && this.keepAliveTimeoutBuffer >= 0 && this.keepAliveTimeoutBuffer !== Infinity
|
|
1479
|
+
? this.keepAliveTimeoutBuffer : 1000;
|
|
1480
|
+
state.socket.setTimeout(this.keepAliveTimeout + buffer);
|
|
1205
1481
|
state.keepAliveTimeoutSet = true;
|
|
1206
1482
|
}
|
|
1207
1483
|
}
|
|
@@ -1265,6 +1541,10 @@ function unfeed(socket) {
|
|
|
1265
1541
|
httpFedClose(fed.conn);
|
|
1266
1542
|
}
|
|
1267
1543
|
|
|
1544
|
+
function socketSetEncoding() {
|
|
1545
|
+
throw codeError(Error, "ERR_HTTP_SOCKET_ENCODING", "Changing the socket encoding is not allowed per RFC7230 Section 3.");
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1268
1548
|
/* Node's socketOnError: 'clientError', or a 400 (when nothing was written yet) and the end */
|
|
1269
1549
|
function socketOnError(e) {
|
|
1270
1550
|
this.removeListener("error", socketOnError);
|
|
@@ -1273,8 +1553,8 @@ function socketOnError(e) {
|
|
|
1273
1553
|
if (!server.emit("clientError", e, this)) {
|
|
1274
1554
|
const state = server._states.get(connKey(this));
|
|
1275
1555
|
const res = state?.res;
|
|
1556
|
+
if (state) httpFlush(state.conn); /* what the responses wrote goes out first, as Node's socket had it */
|
|
1276
1557
|
if (this.writable && (!res || !res.headersSent)) {
|
|
1277
|
-
if (state) httpFlush(state.conn);
|
|
1278
1558
|
this.write(e.code === "HPE_HEADER_OVERFLOW" ? headersTooLargeResponse : e.code === "ERR_HTTP_REQUEST_TIMEOUT" ? requestTimeoutResponse : badRequestResponse);
|
|
1279
1559
|
}
|
|
1280
1560
|
this.destroy(e);
|
|
@@ -1325,7 +1605,9 @@ function clientParsersId() {
|
|
|
1325
1605
|
res.httpVersionMinor = minor;
|
|
1326
1606
|
res.httpVersion = major + "." + minor;
|
|
1327
1607
|
res.rawHeaders = raw;
|
|
1328
|
-
|
|
1608
|
+
const pairs = typeof req.maxHeadersCount === "number" ? req.maxHeadersCount << 1 : 2000;
|
|
1609
|
+
const n = pairs > 0 && pairs < raw.length ? pairs : raw.length;
|
|
1610
|
+
for (let i = 0; i < n; i += 2) addHeader(res.headers, raw[i], raw[i + 1], req.joinDuplicateHeaders);
|
|
1329
1611
|
res.upgrade = upgrade;
|
|
1330
1612
|
return parserOnIncomingClient(req, socket, res, keepAlive);
|
|
1331
1613
|
},
|
|
@@ -1364,7 +1646,7 @@ function clientParsersId() {
|
|
|
1364
1646
|
}
|
|
1365
1647
|
if (req) {
|
|
1366
1648
|
socket._hadError = true;
|
|
1367
|
-
req
|
|
1649
|
+
emitErrorEvent(req, e);
|
|
1368
1650
|
}
|
|
1369
1651
|
},
|
|
1370
1652
|
].map(inRequestContext));
|
|
@@ -1470,8 +1752,14 @@ function clientUpgrade(req, socket, head) {
|
|
|
1470
1752
|
}
|
|
1471
1753
|
}
|
|
1472
1754
|
|
|
1755
|
+
function emitErrorEvent(request, error) {
|
|
1756
|
+
if (onClientRequestErrorChannel.hasSubscribers) onClientRequestErrorChannel.publish({ request, error });
|
|
1757
|
+
request.emit("error", error);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1473
1760
|
function responseOnEnd() {
|
|
1474
1761
|
const req = this.req;
|
|
1762
|
+
if (onClientResponseFinishChannel.hasSubscribers) onClientResponseFinishChannel.publish({ request: req, response: this });
|
|
1475
1763
|
const socket = req.socket;
|
|
1476
1764
|
if (socket) {
|
|
1477
1765
|
if (req.timeoutCb) socket.removeListener("timeout", emitRequestTimeout);
|
|
@@ -1537,7 +1825,7 @@ function socketCloseListener() {
|
|
|
1537
1825
|
} else {
|
|
1538
1826
|
if (!this._hadError) { /* closed before a response: the request's error */
|
|
1539
1827
|
this._hadError = true;
|
|
1540
|
-
req
|
|
1828
|
+
emitErrorEvent(req, connReset("socket hang up"));
|
|
1541
1829
|
}
|
|
1542
1830
|
req._closed = true;
|
|
1543
1831
|
req.emit("close");
|
|
@@ -1550,7 +1838,7 @@ function socketErrorListener(err) {
|
|
|
1550
1838
|
const req = this._httpMessage;
|
|
1551
1839
|
if (req) {
|
|
1552
1840
|
this._hadError = true;
|
|
1553
|
-
req
|
|
1841
|
+
emitErrorEvent(req, err);
|
|
1554
1842
|
}
|
|
1555
1843
|
freeParser(this);
|
|
1556
1844
|
this.removeListener("end", socketOnEnd);
|
|
@@ -1561,7 +1849,7 @@ function socketOnEnd() {
|
|
|
1561
1849
|
const req = this._httpMessage;
|
|
1562
1850
|
if (!req.res && !this._hadError) {
|
|
1563
1851
|
this._hadError = true;
|
|
1564
|
-
req
|
|
1852
|
+
emitErrorEvent(req, connReset("socket hang up"));
|
|
1565
1853
|
}
|
|
1566
1854
|
freeParser(this);
|
|
1567
1855
|
this.destroy();
|
|
@@ -1614,6 +1902,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1614
1902
|
};
|
|
1615
1903
|
const host = optsWithoutSignal.host = validHost(options.hostname, "hostname") || validHost(options.host, "host") || "localhost";
|
|
1616
1904
|
const setHost = options.setHost !== undefined ? Boolean(options.setHost) : options.setDefaultHeaders !== false;
|
|
1905
|
+
this._removedConnection = this._removedContLen = this._removedTE = options.setDefaultHeaders === false;
|
|
1617
1906
|
this.socketPath = options.socketPath;
|
|
1618
1907
|
if (options.timeout !== undefined) {
|
|
1619
1908
|
if (typeof options.timeout !== "number") throw invalidArgType("timeout", "of type number", options.timeout);
|
|
@@ -1655,6 +1944,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1655
1944
|
throw invalidArgType("options.joinDuplicateHeaders", "of type boolean", options.joinDuplicateHeaders);
|
|
1656
1945
|
}
|
|
1657
1946
|
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
|
|
1947
|
+
this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
|
|
1658
1948
|
this[kPath] = options.path || "/";
|
|
1659
1949
|
if (cb) this.once("response", cb);
|
|
1660
1950
|
this.useChunkedEncodingByDefault = !(method === "GET" || method === "HEAD" || method === "DELETE" || method === "OPTIONS" ||
|
|
@@ -1714,7 +2004,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1714
2004
|
const oncreate = (err, socket) => {
|
|
1715
2005
|
if (done) return;
|
|
1716
2006
|
done = true;
|
|
1717
|
-
if (err) process.nextTick(() => this
|
|
2007
|
+
if (err) process.nextTick(() => emitErrorEvent(this, err));
|
|
1718
2008
|
else this.onSocket(socket);
|
|
1719
2009
|
};
|
|
1720
2010
|
try {
|
|
@@ -1727,6 +2017,25 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1727
2017
|
this.onSocket(createConnection(opts));
|
|
1728
2018
|
}
|
|
1729
2019
|
}
|
|
2020
|
+
if (onClientRequestCreatedChannel.hasSubscribers) onClientRequestCreatedChannel.publish({ request: this });
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/* Node's _finish, which publishes the start: once the ended request is all on its socket, at
|
|
2024
|
+
* end() or when the socket is assigned */
|
|
2025
|
+
end(chunk, encoding, callback) {
|
|
2026
|
+
const finished = this.finished;
|
|
2027
|
+
super.end(chunk, encoding, callback);
|
|
2028
|
+
if (!finished && this.outputData.length === 0 && this.socket?._httpMessage === this) this._publishStart();
|
|
2029
|
+
return this;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
_flush() {
|
|
2033
|
+
super._flush();
|
|
2034
|
+
if (this.finished && this.socket?.writable) this._publishStart();
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
_publishStart() {
|
|
2038
|
+
if (onClientRequestStartChannel.hasSubscribers) onClientRequestStartChannel.publish({ request: this });
|
|
1730
2039
|
}
|
|
1731
2040
|
|
|
1732
2041
|
_implicitHeader() {
|
|
@@ -1747,7 +2056,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1747
2056
|
this.destroyed = true;
|
|
1748
2057
|
const done = (er) => {
|
|
1749
2058
|
if (!this.aborted && !er) er = connReset("socket hang up");
|
|
1750
|
-
if (er) this
|
|
2059
|
+
if (er) emitErrorEvent(this, er);
|
|
1751
2060
|
this._closed = true;
|
|
1752
2061
|
this.emit("close");
|
|
1753
2062
|
};
|