visaljs 0.0.9 → 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 +3745 -890
- package/node/include/js_node.h +12 -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 +405 -94
- 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/http_chars.ts +22 -0
- 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 +30 -14
- 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 +79 -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_buffer.h +185 -0
- package/runtime/include/js_builtins.h +58 -0
- package/runtime/include/js_crypto.h +11 -0
- package/runtime/include/js_exception.h +3 -0
- package/runtime/include/js_function.h +1 -1
- package/runtime/include/js_gc.h +13 -10
- package/runtime/include/js_jobs.h +2 -0
- package/runtime/include/js_object.h +79 -3
- package/runtime/include/js_ops.h +27 -0
- package/runtime/include/js_runtime.h +1 -0
- package/runtime/include/js_web.h +0 -3
- package/runtime/include/jscontext.h +8 -0
- package/runtime/src/builtins.c +17 -4
- 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/function.c +37 -10
- package/runtime/src/gc.c +76 -93
- package/runtime/src/inspect.c +18 -1
- package/runtime/src/iterator.c +5 -1
- package/runtime/src/jobs.c +37 -22
- package/runtime/src/object.c +91 -8
- package/runtime/src/os.c +16 -6
- package/runtime/src/os.h +2 -0
- package/runtime/src/process.c +97 -10
- package/runtime/src/typedarray.c +23 -0
package/node/prelude/http.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
// upgrade (or CONNECT) hands the socket over with 'upgrade' ('connect'). Idle keep-alive
|
|
8
8
|
// connections close after keepAliveTimeout; server.timeout and setTimeout are the sockets'.
|
|
9
9
|
// Not yet: the client, headersTimeout and requestTimeout, trailers.
|
|
10
|
+
import { hasInvalidHeaderChar, isHttpToken } from "./internal/http_chars.ts";
|
|
10
11
|
import {
|
|
11
|
-
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,
|
|
12
13
|
} from "visaljs:host";
|
|
14
|
+
import { channel } from "./diagnostics_channel.js";
|
|
13
15
|
import { bindContext, currentContext, runInContext } from "./internal/async_context.js";
|
|
14
16
|
import { codeError, invalidArgType, invalidArgValue, outOfRange, validateInteger, validateString } from "./internal/errors.js";
|
|
15
17
|
import { Agent, globalAgent, setGlobalAgent } from "./internal/http_agent.js";
|
|
@@ -18,6 +20,14 @@ import { createConnection, Server as NetServer } from "./net.js";
|
|
|
18
20
|
import { addAbortSignal, Readable, Stream } from "./stream.js";
|
|
19
21
|
import { callableClass } from "./internal/features.js";
|
|
20
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
|
+
|
|
21
31
|
export const METHODS = ["ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "GET", "HEAD", "LINK", "LOCK", "M-SEARCH", "MERGE",
|
|
22
32
|
"MKACTIVITY", "MKCALENDAR", "MKCOL", "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND", "PROPPATCH", "PURGE", "PUT", "QUERY",
|
|
23
33
|
"REBIND", "REPORT", "SEARCH", "SOURCE", "SUBSCRIBE", "TRACE", "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE"];
|
|
@@ -64,7 +74,8 @@ function chunkLength(chunk) {
|
|
|
64
74
|
/* A request: a Readable of the body's Buffers, pushed as llhttp parses them. */
|
|
65
75
|
export class IncomingMessage extends Readable {
|
|
66
76
|
constructor(socket) {
|
|
67
|
-
|
|
77
|
+
const highWaterMark = socket?._readableState?.highWaterMark;
|
|
78
|
+
super(highWaterMark !== undefined && highWaterMark !== 65536 ? { highWaterMark } : undefined); /* the socket's, as Node's */
|
|
68
79
|
this.socket = socket;
|
|
69
80
|
this.httpVersionMajor = 1;
|
|
70
81
|
this.httpVersionMinor = 1;
|
|
@@ -88,6 +99,31 @@ export class IncomingMessage extends Readable {
|
|
|
88
99
|
return this.socket;
|
|
89
100
|
}
|
|
90
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
|
+
|
|
91
127
|
_read() {
|
|
92
128
|
this._consuming = true;
|
|
93
129
|
}
|
|
@@ -165,6 +201,16 @@ const SINGLE = new Set(["age", "authorization", "content-length", "content-type"
|
|
|
165
201
|
"if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer",
|
|
166
202
|
"retry-after", "server", "user-agent"]);
|
|
167
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
|
+
|
|
168
214
|
function addHeader(headers, name, value, join) {
|
|
169
215
|
const key = name.toLowerCase();
|
|
170
216
|
const existing = headers[key];
|
|
@@ -183,20 +229,80 @@ function addHeader(headers, name, value, join) {
|
|
|
183
229
|
/* ---- OutgoingMessage: a response's (and a request's) headers ------------------------------------ */
|
|
184
230
|
|
|
185
231
|
/* Node's checks: a header name is an HTTP token; a value has no control characters */
|
|
186
|
-
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
|
|
187
|
-
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
|
|
188
232
|
|
|
189
233
|
export function validateHeaderName(name, label = "Header name") {
|
|
190
|
-
if (typeof name !== "string" || !
|
|
234
|
+
if (typeof name !== "string" || !isHttpToken(name)) {
|
|
191
235
|
throw codeError(TypeError, "ERR_INVALID_HTTP_TOKEN", label + " must be a valid HTTP token [\"" + name + "\"]");
|
|
192
236
|
}
|
|
193
237
|
}
|
|
194
238
|
|
|
195
239
|
export function validateHeaderValue(name, value) {
|
|
196
240
|
if (value === undefined) throw codeError(TypeError, "ERR_HTTP_INVALID_HEADER_VALUE", "Invalid value \"" + value + "\" for header \"" + name + "\"");
|
|
197
|
-
if (
|
|
241
|
+
if (hasInvalidHeaderChar(String(value))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in header content [\"" + name + "\"]");
|
|
198
242
|
}
|
|
199
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.");
|
|
200
306
|
const headersSent = (what) => codeError(Error, "ERR_HTTP_HEADERS_SENT", "Cannot " + what + " headers after they are sent to the client");
|
|
201
307
|
|
|
202
308
|
/* Node's write_: a chunk is a string or bytes */
|
|
@@ -366,14 +472,19 @@ export class OutgoingMessage extends Stream {
|
|
|
366
472
|
}
|
|
367
473
|
|
|
368
474
|
hasHeader(name) {
|
|
475
|
+
validateString(name, "name");
|
|
369
476
|
return this._headers.has(String(name).toLowerCase());
|
|
370
477
|
}
|
|
371
478
|
|
|
372
479
|
removeHeader(name) {
|
|
373
480
|
validateString(name, "name");
|
|
374
481
|
if (this._header || this.headersSent) throw headersSent("remove");
|
|
375
|
-
|
|
376
|
-
|
|
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);
|
|
377
488
|
}
|
|
378
489
|
|
|
379
490
|
getHeaderNames() {
|
|
@@ -392,51 +503,52 @@ export class OutgoingMessage extends Stream {
|
|
|
392
503
|
|
|
393
504
|
/* Node's OutgoingMessage, as a ClientRequest (and an OutgoingMessage made by hand) writes: the
|
|
394
505
|
* head stored, then the bytes through the socket (ServerResponse has its own, on llhttp's
|
|
395
|
-
* 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,
|
|
396
507
|
* Connection and the body's framing */
|
|
397
|
-
_storeHeader(firstLine,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
} else
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (/chunked/i.test(String(value))) this.chunkedEncoding = true;
|
|
410
|
-
} else if (key === "trailer") hasTrailer = true;
|
|
411
|
-
if (Array.isArray(value)) for (const v of value) head += name + ": " + v + "\r\n";
|
|
412
|
-
else head += name + ": " + value + "\r\n";
|
|
413
|
-
};
|
|
414
|
-
if (headersArray) {
|
|
415
|
-
if (Array.isArray(headersArray[0])) for (const [name, value] of headersArray) add(name, value);
|
|
416
|
-
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
|
+
}
|
|
417
520
|
} else {
|
|
418
|
-
for (const [, [name, value]] of this._headers)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
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);
|
|
422
529
|
if (shouldSendKeepAlive) head += "Connection: keep-alive\r\n";
|
|
423
530
|
else {
|
|
424
531
|
this._last = true;
|
|
425
532
|
head += "Connection: close\r\n";
|
|
426
533
|
}
|
|
427
534
|
}
|
|
428
|
-
if (!
|
|
535
|
+
if (!state.contLen && !state.te) {
|
|
429
536
|
if (!this._hasBody) this.chunkedEncoding = false;
|
|
430
537
|
else if (!this.useChunkedEncodingByDefault) this._last = true;
|
|
431
|
-
else if (!
|
|
432
|
-
else {
|
|
538
|
+
else if (!state.trailer && !this._removedContLen && typeof this._contentLength === "number") head += "Content-Length: " + this._contentLength + "\r\n";
|
|
539
|
+
else if (!this._removedTE) {
|
|
433
540
|
head += "Transfer-Encoding: chunked\r\n";
|
|
434
541
|
this.chunkedEncoding = true;
|
|
542
|
+
} else {
|
|
543
|
+
this._last = true; /* neither length nor chunks: the body ends with the connection */
|
|
435
544
|
}
|
|
436
545
|
}
|
|
546
|
+
if (this.chunkedEncoding !== true && state.trailer) throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
|
|
437
547
|
this._header = head + "\r\n";
|
|
438
548
|
this._headerSent = false;
|
|
439
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("");
|
|
440
552
|
}
|
|
441
553
|
|
|
442
554
|
/* bytes for the socket: written now, or kept until it's assigned */
|
|
@@ -562,7 +674,6 @@ export class OutgoingMessage extends Stream {
|
|
|
562
674
|
else process.nextTick(finish);
|
|
563
675
|
this.finished = true;
|
|
564
676
|
this.writableEnded = true;
|
|
565
|
-
this.writable = false;
|
|
566
677
|
return this;
|
|
567
678
|
}
|
|
568
679
|
|
|
@@ -582,15 +693,22 @@ export class OutgoingMessage extends Stream {
|
|
|
582
693
|
const entries = Array.isArray(headers) ? headers : Object.keys(headers).map((key) => [key, headers[key]]);
|
|
583
694
|
for (const [field, value] of entries) {
|
|
584
695
|
validateHeaderName(field, "Trailer name");
|
|
585
|
-
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];
|
|
586
698
|
for (const v of values) {
|
|
587
|
-
if (
|
|
699
|
+
if (hasInvalidHeaderChar(String(v))) throw codeError(TypeError, "ERR_INVALID_CHAR", "Invalid character in trailer content [\"" + field + "\"]");
|
|
588
700
|
this._trailer += field + ": " + v + "\r\n";
|
|
589
701
|
}
|
|
590
702
|
}
|
|
591
703
|
}
|
|
592
704
|
}
|
|
593
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
|
+
|
|
594
712
|
/* ---- ServerResponse ------------------------------------------------------------------------- */
|
|
595
713
|
|
|
596
714
|
export class ServerResponse extends OutgoingMessage {
|
|
@@ -608,13 +726,68 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
608
726
|
/* one made by hand (new ServerResponse(req)) is kept alive, without a Keep-Alive timeout, as Node's */
|
|
609
727
|
this._keepAlive = keepAlive ?? true;
|
|
610
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;
|
|
611
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);
|
|
612
782
|
}
|
|
613
783
|
|
|
614
784
|
/* the response's bytes: to the connection's parser, which corks them; or, for a response made by
|
|
615
785
|
* hand (new ServerResponse(req), Node's assignSocket), to its socket, kept until it has one */
|
|
616
786
|
_send(data, latin1 = false) {
|
|
617
|
-
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
|
+
}
|
|
618
791
|
else if (this._conn !== undefined) httpWrite(this._conn, data, latin1);
|
|
619
792
|
else if (this.socket) this.socket.write(data, latin1 ? "latin1" : undefined);
|
|
620
793
|
else (this._unsent ??= []).push([data, latin1]);
|
|
@@ -665,7 +838,6 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
665
838
|
if (this.destroyed) return this;
|
|
666
839
|
this.destroyed = true;
|
|
667
840
|
this._errored = error;
|
|
668
|
-
this.writable = false;
|
|
669
841
|
this._closeEmitted = true;
|
|
670
842
|
if (this.socket) this.socket.destroy();
|
|
671
843
|
queueMicrotask(() => {
|
|
@@ -680,11 +852,11 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
680
852
|
if (this._closeEmitted) return;
|
|
681
853
|
this._closeEmitted = true;
|
|
682
854
|
this.destroyed = true;
|
|
683
|
-
this.writable = false;
|
|
684
855
|
queueMicrotask(() => this.emit("close"));
|
|
685
856
|
}
|
|
686
857
|
|
|
687
858
|
writeHead(statusCode, statusMessage, headers) {
|
|
859
|
+
if (this.headersSent) throw headersSent("write");
|
|
688
860
|
/* as Node's: an integer status from 100 to 999 */
|
|
689
861
|
const originalStatusCode = statusCode;
|
|
690
862
|
statusCode |= 0;
|
|
@@ -693,12 +865,16 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
693
865
|
throw codeError(RangeError, "ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${shown}`);
|
|
694
866
|
}
|
|
695
867
|
if (typeof statusMessage !== "string") {
|
|
696
|
-
headers
|
|
868
|
+
headers ??= statusMessage;
|
|
697
869
|
statusMessage = undefined;
|
|
698
870
|
}
|
|
699
871
|
this.statusCode = statusCode;
|
|
700
872
|
if (statusMessage !== undefined) this.statusMessage = statusMessage;
|
|
701
|
-
|
|
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)) {
|
|
702
878
|
/* as Node's: they replace the headers set before, and may repeat a name among themselves */
|
|
703
879
|
if (headers.length % 2 !== 0) throw invalidArgValue("headers", "is invalid", headers);
|
|
704
880
|
for (let i = 0; i < headers.length; i += 2) if (this.hasHeader(headers[i])) this.removeHeader(headers[i]);
|
|
@@ -708,6 +884,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
708
884
|
}
|
|
709
885
|
/* Node renders the head now (sent with the first write): without a known length, the body
|
|
710
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");
|
|
711
888
|
if (!this.headersSent) this._pendingHead = this._renderHead(this._implicitLength);
|
|
712
889
|
return this;
|
|
713
890
|
}
|
|
@@ -724,42 +901,57 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
724
901
|
_renderHead(length) {
|
|
725
902
|
const status = this.statusCode;
|
|
726
903
|
const noBody = status === 204 || status === 304 || (status >= 100 && status < 200);
|
|
904
|
+
if (noBody) this._hasBody = false;
|
|
727
905
|
/* an empty statusMessage gets the standard one, as in Node (Next.js's route handlers set "") */
|
|
728
906
|
let head = "HTTP/1.1 " + status + " " + (this.statusMessage || STATUS_CODES[status] || "unknown") + "\r\n";
|
|
729
|
-
for (const [, [name, value]] of this._headers) {
|
|
730
|
-
if (Array.isArray(value))
|
|
731
|
-
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";
|
|
732
911
|
}
|
|
733
912
|
if (this.sendDate && !this._headers.has("date")) head += "Date: " + httpDate() + "\r\n";
|
|
734
|
-
const connection = this._headers.get("connection");
|
|
735
|
-
if (connection !== undefined && String(connection[1]).toLowerCase().includes("close")) this._keepAlive = false;
|
|
736
|
-
if (connection === undefined) {
|
|
737
|
-
if (!this._keepAlive) head += "Connection: close\r\n";
|
|
738
|
-
else {
|
|
739
|
-
head += "Connection: keep-alive\r\n";
|
|
740
|
-
if (this._keepAliveTimeout) head += "Keep-Alive: timeout=" + Math.floor(this._keepAliveTimeout / 1000) + "\r\n";
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
913
|
/* Node: no length for HEAD; an HTTP/1.0 client gets a body that ends with the connection */
|
|
744
914
|
const http10 = this.req.httpVersionMajor < 1 || this.req.httpVersionMinor < 1;
|
|
745
|
-
/* 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 */
|
|
746
917
|
const te = this._headers.get("transfer-encoding");
|
|
747
|
-
if (te !== undefined && /\bchunked\b/i.test(String(te[1]))
|
|
748
|
-
|
|
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) {
|
|
749
925
|
if (this._contentLength !== null && this._contentLength !== undefined) length = this._contentLength;
|
|
750
|
-
if (length !== undefined) {
|
|
751
|
-
|
|
752
|
-
} else {
|
|
753
|
-
|
|
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";
|
|
754
930
|
this.chunkedEncoding = true;
|
|
931
|
+
} else {
|
|
932
|
+
this._keepAlive = false; /* neither length nor chunks: the body ends with the connection */
|
|
755
933
|
}
|
|
756
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;
|
|
757
946
|
/* as Node's _storeHeader: only a chunked body can have trailers (the head isn't sent) */
|
|
758
|
-
if (this.chunkedEncoding !== true &&
|
|
947
|
+
if (this.chunkedEncoding !== true && hasTrailer) {
|
|
759
948
|
throw codeError(Error, "ERR_HTTP_TRAILER_INVALID", "Trailers are invalid with this transfer encoding");
|
|
760
949
|
}
|
|
761
950
|
this.headersSent = true;
|
|
762
|
-
|
|
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;
|
|
763
955
|
}
|
|
764
956
|
|
|
765
957
|
write(chunk, encoding, callback) {
|
|
@@ -772,9 +964,11 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
772
964
|
const e = new Error(ended ? "write after end" : "Cannot call write after a stream was destroyed");
|
|
773
965
|
e.code = ended ? "ERR_STREAM_WRITE_AFTER_END" : "ERR_STREAM_DESTROYED";
|
|
774
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) */
|
|
775
969
|
process.nextTick(() => {
|
|
776
970
|
if (typeof callback === "function") callback(e);
|
|
777
|
-
|
|
971
|
+
this.emit("error", e);
|
|
778
972
|
});
|
|
779
973
|
} else if (typeof callback === "function") {
|
|
780
974
|
process.nextTick(callback, e);
|
|
@@ -782,11 +976,13 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
782
976
|
return false;
|
|
783
977
|
}
|
|
784
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);
|
|
785
980
|
/* a writeHead of the response's own (a wrapper) sees the implicit head, as in Node */
|
|
786
981
|
if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(undefined);
|
|
787
982
|
const head = this._takeHead(undefined);
|
|
788
983
|
if (head) this._send(head, true);
|
|
789
|
-
if (
|
|
984
|
+
if (!this._hasBody && this._rejectNonStandardBodyWrites) throw bodyNotAllowed();
|
|
985
|
+
if (chunk !== undefined && chunk !== null && chunkLength(chunk) > 0 && this._hasBody) {
|
|
790
986
|
if (this.chunkedEncoding) {
|
|
791
987
|
this._send(chunkLength(chunk).toString(16) + "\r\n");
|
|
792
988
|
this._send(chunk);
|
|
@@ -803,6 +999,22 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
803
999
|
return this._pendingBytes();
|
|
804
1000
|
}
|
|
805
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
|
+
|
|
806
1018
|
/* bytes written that haven't gone out: corked in C and in libuv's queue, or a TLS socket's */
|
|
807
1019
|
_pendingBytes() {
|
|
808
1020
|
if (this._conn === undefined || this._queued) return 0;
|
|
@@ -842,31 +1054,40 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
842
1054
|
if (chunk !== undefined && chunk !== null) validateChunk(chunk);
|
|
843
1055
|
if (this.writableEnded) return this;
|
|
844
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);
|
|
845
1058
|
if (!this.headersSent && Object.hasOwn(this, "writeHead")) this._implicitHeader(chunk !== undefined && chunk !== null ? chunkLength(chunk) : 0);
|
|
846
1059
|
if (!this.headersSent) {
|
|
847
1060
|
const hasChunk = chunk !== undefined && chunk !== null;
|
|
848
1061
|
const length = hasChunk ? chunkLength(chunk) : 0;
|
|
849
|
-
|
|
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) {
|
|
850
1072
|
/* the head's bytes are latin1, the body's UTF-8: corked into one write by the parser's
|
|
851
1073
|
* connection; a response made by hand writes them as one string, as Node's _send */
|
|
852
1074
|
if (this._conn === undefined) {
|
|
853
|
-
this._send(
|
|
1075
|
+
this._send(head + chunk);
|
|
854
1076
|
} else {
|
|
855
|
-
this._send(
|
|
1077
|
+
this._send(head, true);
|
|
856
1078
|
this._send(chunk);
|
|
857
1079
|
}
|
|
858
1080
|
} else {
|
|
859
|
-
this._send(
|
|
860
|
-
if (hasChunk && length &&
|
|
1081
|
+
this._send(head, true);
|
|
1082
|
+
if (hasChunk && length && this._hasBody) this._send(chunk);
|
|
861
1083
|
}
|
|
862
1084
|
} else {
|
|
863
1085
|
if (chunk !== undefined && chunk !== null) this.write(chunk);
|
|
864
1086
|
const head = this._takeHead(undefined);
|
|
865
1087
|
if (head) this._send(head, true);
|
|
866
|
-
if (this.chunkedEncoding &&
|
|
1088
|
+
if (this.chunkedEncoding && this._hasBody) this._send("0\r\n" + this._trailer + "\r\n", true);
|
|
867
1089
|
}
|
|
868
|
-
this.writableEnded = this.finished = true;
|
|
869
|
-
this.writable = false;
|
|
1090
|
+
this.writableEnded = this.finished = true; /* writable stays true, as Node's (nodejs/node#15029) */
|
|
870
1091
|
if (this._queued) { /* finished once it has gone out, after the responses before it */
|
|
871
1092
|
this._endCallback = callback;
|
|
872
1093
|
return this;
|
|
@@ -878,6 +1099,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
878
1099
|
/* a response that waited behind the connection's earlier ones: its turn (Node's assignSocket) */
|
|
879
1100
|
_activate() {
|
|
880
1101
|
this._queued = false;
|
|
1102
|
+
this.outputSize = 0;
|
|
881
1103
|
const unsent = this._unsent;
|
|
882
1104
|
this._unsent = undefined;
|
|
883
1105
|
if (unsent) for (const [data, latin1] of unsent) httpWrite(this._conn, data, latin1);
|
|
@@ -887,6 +1109,7 @@ export class ServerResponse extends OutgoingMessage {
|
|
|
887
1109
|
/* the response is all written: the socket ends after the last one (not kept alive, or the client
|
|
888
1110
|
* ended its side), then 'finish' and 'close' */
|
|
889
1111
|
_afterEnd(callback) {
|
|
1112
|
+
if (this.destroyed) return this; /* its connection closed: no 'finish', as Node's (the socket's write never completes) */
|
|
890
1113
|
/* the response is all written: sent now, so that by 'finish' it has gone to the socket, as Node's */
|
|
891
1114
|
if (this._conn !== undefined) httpFlush(this._conn);
|
|
892
1115
|
if ((!this._keepAlive || this._last) && this.socket) { /* the response goes out, then the socket ends */
|
|
@@ -955,6 +1178,16 @@ class ServerClass extends NetServer {
|
|
|
955
1178
|
this.insecureHTTPParser = boolean("insecureHTTPParser", undefined);
|
|
956
1179
|
this.requireHostHeader = boolean("requireHostHeader", true);
|
|
957
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
|
+
}
|
|
958
1191
|
/* the classes requests and responses are made of (subclasses of node:http's), as Node's */
|
|
959
1192
|
this._IncomingMessage = options.IncomingMessage || IncomingMessage;
|
|
960
1193
|
this._ServerResponse = options.ServerResponse || ServerResponse;
|
|
@@ -965,6 +1198,7 @@ class ServerClass extends NetServer {
|
|
|
965
1198
|
throw outOfRange("headersTimeout", "<= requestTimeout", this.headersTimeout);
|
|
966
1199
|
}
|
|
967
1200
|
this.keepAliveTimeout = integer("keepAliveTimeout", 5000);
|
|
1201
|
+
this.keepAliveTimeoutBuffer = integer("keepAliveTimeoutBuffer", 1000);
|
|
968
1202
|
this.connectionsCheckingInterval = integer("connectionsCheckingInterval", 30_000);
|
|
969
1203
|
this._parsers = -1;
|
|
970
1204
|
this._states = new Map(); /* connection → { socket, req, res, keepAliveTimeoutSet } */
|
|
@@ -1087,10 +1321,12 @@ class ServerClass extends NetServer {
|
|
|
1087
1321
|
const conn = connOf(socket);
|
|
1088
1322
|
if (conn === null) return;
|
|
1089
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);
|
|
1090
1326
|
/* as Node's connection state: the request being parsed (req); those whose responses aren't
|
|
1091
1327
|
* finished (incoming); the response writing to the connection (res), and those waiting behind it
|
|
1092
1328
|
* (outgoing: pipelined requests are answered in order) */
|
|
1093
|
-
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 };
|
|
1094
1330
|
this._states.set(conn, state);
|
|
1095
1331
|
if (this.timeout && typeof socket.setTimeout === "function") socket.setTimeout(this.timeout);
|
|
1096
1332
|
const onTimeout = () => this._onTimeout(state);
|
|
@@ -1160,8 +1396,11 @@ class ServerClass extends NetServer {
|
|
|
1160
1396
|
req.httpVersionMinor = minor;
|
|
1161
1397
|
req.httpVersion = major + "." + minor;
|
|
1162
1398
|
req.rawHeaders = raw;
|
|
1163
|
-
|
|
1164
|
-
|
|
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));
|
|
1165
1404
|
if (req.upgrade) {
|
|
1166
1405
|
state.upgradeReq = req;
|
|
1167
1406
|
return 2;
|
|
@@ -1176,30 +1415,69 @@ class ServerClass extends NetServer {
|
|
|
1176
1415
|
} else {
|
|
1177
1416
|
state.res = res;
|
|
1178
1417
|
}
|
|
1179
|
-
res.
|
|
1180
|
-
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 */
|
|
1181
1421
|
res.writeHead(400, ["Connection", "close"]);
|
|
1182
1422
|
res.end();
|
|
1183
1423
|
return 0;
|
|
1184
1424
|
}
|
|
1185
|
-
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
|
+
}
|
|
1186
1451
|
return 0;
|
|
1187
1452
|
}
|
|
1188
1453
|
|
|
1189
1454
|
/* Node's resOnFinish: the request is done with; the next response's turn, or the idle keep-alive
|
|
1190
1455
|
* timeout */
|
|
1191
|
-
_resOnFinish(state, req, res) {
|
|
1192
|
-
|
|
1193
|
-
if (
|
|
1456
|
+
_resOnFinish(state, req, res, socket) {
|
|
1457
|
+
if (onResponseFinishChannel.hasSubscribers) onResponseFinishChannel.publish({ request: req, response: res, socket, server: this });
|
|
1458
|
+
if (state.incoming[0] === req) state.incoming.shift();
|
|
1459
|
+
else {
|
|
1460
|
+
const i = state.incoming.indexOf(req);
|
|
1461
|
+
if (i !== -1) state.incoming.splice(i, 1);
|
|
1462
|
+
}
|
|
1194
1463
|
if (state.req === req && req.complete) state.req = null;
|
|
1195
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();
|
|
1196
1471
|
if (res._last || !res._keepAlive) return; /* the socket is ending */
|
|
1197
1472
|
const next = state.outgoing.shift();
|
|
1198
1473
|
if (next) {
|
|
1199
1474
|
state.res = next;
|
|
1200
1475
|
next._activate();
|
|
1201
1476
|
} else if (this.keepAliveTimeout && state.socket.writable) {
|
|
1202
|
-
|
|
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);
|
|
1203
1481
|
state.keepAliveTimeoutSet = true;
|
|
1204
1482
|
}
|
|
1205
1483
|
}
|
|
@@ -1263,6 +1541,10 @@ function unfeed(socket) {
|
|
|
1263
1541
|
httpFedClose(fed.conn);
|
|
1264
1542
|
}
|
|
1265
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
|
+
|
|
1266
1548
|
/* Node's socketOnError: 'clientError', or a 400 (when nothing was written yet) and the end */
|
|
1267
1549
|
function socketOnError(e) {
|
|
1268
1550
|
this.removeListener("error", socketOnError);
|
|
@@ -1271,8 +1553,8 @@ function socketOnError(e) {
|
|
|
1271
1553
|
if (!server.emit("clientError", e, this)) {
|
|
1272
1554
|
const state = server._states.get(connKey(this));
|
|
1273
1555
|
const res = state?.res;
|
|
1556
|
+
if (state) httpFlush(state.conn); /* what the responses wrote goes out first, as Node's socket had it */
|
|
1274
1557
|
if (this.writable && (!res || !res.headersSent)) {
|
|
1275
|
-
if (state) httpFlush(state.conn);
|
|
1276
1558
|
this.write(e.code === "HPE_HEADER_OVERFLOW" ? headersTooLargeResponse : e.code === "ERR_HTTP_REQUEST_TIMEOUT" ? requestTimeoutResponse : badRequestResponse);
|
|
1277
1559
|
}
|
|
1278
1560
|
this.destroy(e);
|
|
@@ -1323,7 +1605,9 @@ function clientParsersId() {
|
|
|
1323
1605
|
res.httpVersionMinor = minor;
|
|
1324
1606
|
res.httpVersion = major + "." + minor;
|
|
1325
1607
|
res.rawHeaders = raw;
|
|
1326
|
-
|
|
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);
|
|
1327
1611
|
res.upgrade = upgrade;
|
|
1328
1612
|
return parserOnIncomingClient(req, socket, res, keepAlive);
|
|
1329
1613
|
},
|
|
@@ -1362,7 +1646,7 @@ function clientParsersId() {
|
|
|
1362
1646
|
}
|
|
1363
1647
|
if (req) {
|
|
1364
1648
|
socket._hadError = true;
|
|
1365
|
-
req
|
|
1649
|
+
emitErrorEvent(req, e);
|
|
1366
1650
|
}
|
|
1367
1651
|
},
|
|
1368
1652
|
].map(inRequestContext));
|
|
@@ -1468,8 +1752,14 @@ function clientUpgrade(req, socket, head) {
|
|
|
1468
1752
|
}
|
|
1469
1753
|
}
|
|
1470
1754
|
|
|
1755
|
+
function emitErrorEvent(request, error) {
|
|
1756
|
+
if (onClientRequestErrorChannel.hasSubscribers) onClientRequestErrorChannel.publish({ request, error });
|
|
1757
|
+
request.emit("error", error);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1471
1760
|
function responseOnEnd() {
|
|
1472
1761
|
const req = this.req;
|
|
1762
|
+
if (onClientResponseFinishChannel.hasSubscribers) onClientResponseFinishChannel.publish({ request: req, response: this });
|
|
1473
1763
|
const socket = req.socket;
|
|
1474
1764
|
if (socket) {
|
|
1475
1765
|
if (req.timeoutCb) socket.removeListener("timeout", emitRequestTimeout);
|
|
@@ -1535,7 +1825,7 @@ function socketCloseListener() {
|
|
|
1535
1825
|
} else {
|
|
1536
1826
|
if (!this._hadError) { /* closed before a response: the request's error */
|
|
1537
1827
|
this._hadError = true;
|
|
1538
|
-
req
|
|
1828
|
+
emitErrorEvent(req, connReset("socket hang up"));
|
|
1539
1829
|
}
|
|
1540
1830
|
req._closed = true;
|
|
1541
1831
|
req.emit("close");
|
|
@@ -1548,7 +1838,7 @@ function socketErrorListener(err) {
|
|
|
1548
1838
|
const req = this._httpMessage;
|
|
1549
1839
|
if (req) {
|
|
1550
1840
|
this._hadError = true;
|
|
1551
|
-
req
|
|
1841
|
+
emitErrorEvent(req, err);
|
|
1552
1842
|
}
|
|
1553
1843
|
freeParser(this);
|
|
1554
1844
|
this.removeListener("end", socketOnEnd);
|
|
@@ -1559,7 +1849,7 @@ function socketOnEnd() {
|
|
|
1559
1849
|
const req = this._httpMessage;
|
|
1560
1850
|
if (!req.res && !this._hadError) {
|
|
1561
1851
|
this._hadError = true;
|
|
1562
|
-
req
|
|
1852
|
+
emitErrorEvent(req, connReset("socket hang up"));
|
|
1563
1853
|
}
|
|
1564
1854
|
freeParser(this);
|
|
1565
1855
|
this.destroy();
|
|
@@ -1612,6 +1902,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1612
1902
|
};
|
|
1613
1903
|
const host = optsWithoutSignal.host = validHost(options.hostname, "hostname") || validHost(options.host, "host") || "localhost";
|
|
1614
1904
|
const setHost = options.setHost !== undefined ? Boolean(options.setHost) : options.setDefaultHeaders !== false;
|
|
1905
|
+
this._removedConnection = this._removedContLen = this._removedTE = options.setDefaultHeaders === false;
|
|
1615
1906
|
this.socketPath = options.socketPath;
|
|
1616
1907
|
if (options.timeout !== undefined) {
|
|
1617
1908
|
if (typeof options.timeout !== "number") throw invalidArgType("timeout", "of type number", options.timeout);
|
|
@@ -1625,7 +1916,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1625
1916
|
let method = options.method;
|
|
1626
1917
|
if (method !== null && method !== undefined && typeof method !== "string") throw invalidArgType("options.method", "of type string", method);
|
|
1627
1918
|
if (method) {
|
|
1628
|
-
if (!
|
|
1919
|
+
if (!isHttpToken(method)) throw codeError(TypeError, "ERR_INVALID_HTTP_TOKEN", "Method must be a valid HTTP token [\"" + method + "\"]");
|
|
1629
1920
|
method = this.method = method.toUpperCase();
|
|
1630
1921
|
} else {
|
|
1631
1922
|
method = this.method = "GET";
|
|
@@ -1653,6 +1944,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1653
1944
|
throw invalidArgType("options.joinDuplicateHeaders", "of type boolean", options.joinDuplicateHeaders);
|
|
1654
1945
|
}
|
|
1655
1946
|
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
|
|
1947
|
+
this._uniqueHeaders = uniqueHeadersOption(options.uniqueHeaders);
|
|
1656
1948
|
this[kPath] = options.path || "/";
|
|
1657
1949
|
if (cb) this.once("response", cb);
|
|
1658
1950
|
this.useChunkedEncodingByDefault = !(method === "GET" || method === "HEAD" || method === "DELETE" || method === "OPTIONS" ||
|
|
@@ -1712,7 +2004,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1712
2004
|
const oncreate = (err, socket) => {
|
|
1713
2005
|
if (done) return;
|
|
1714
2006
|
done = true;
|
|
1715
|
-
if (err) process.nextTick(() => this
|
|
2007
|
+
if (err) process.nextTick(() => emitErrorEvent(this, err));
|
|
1716
2008
|
else this.onSocket(socket);
|
|
1717
2009
|
};
|
|
1718
2010
|
try {
|
|
@@ -1725,6 +2017,25 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1725
2017
|
this.onSocket(createConnection(opts));
|
|
1726
2018
|
}
|
|
1727
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 });
|
|
1728
2039
|
}
|
|
1729
2040
|
|
|
1730
2041
|
_implicitHeader() {
|
|
@@ -1745,7 +2056,7 @@ export class ClientRequest extends OutgoingMessage {
|
|
|
1745
2056
|
this.destroyed = true;
|
|
1746
2057
|
const done = (er) => {
|
|
1747
2058
|
if (!this.aborted && !er) er = connReset("socket hang up");
|
|
1748
|
-
if (er) this
|
|
2059
|
+
if (er) emitErrorEvent(this, er);
|
|
1749
2060
|
this._closed = true;
|
|
1750
2061
|
this.emit("close");
|
|
1751
2062
|
};
|