@entelligentsia/forgecli 0.7.10 → 0.8.4
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/CHANGELOG.md +74 -0
- package/dist/CHANGELOG-forge-plugin.md +70 -0
- package/dist/CHANGELOG-pi.md +63 -0
- package/dist/bin/argv.d.ts +2 -2
- package/dist/bin/argv.js +10 -0
- package/dist/bin/argv.js.map +1 -1
- package/dist/bin/env-defaults.d.ts +1 -0
- package/dist/bin/env-defaults.js +13 -0
- package/dist/bin/env-defaults.js.map +1 -0
- package/dist/bin/forge.js +9 -0
- package/dist/bin/forge.js.map +1 -1
- package/dist/bin/update-cli.d.ts +9 -0
- package/dist/bin/update-cli.js +120 -0
- package/dist/bin/update-cli.js.map +1 -0
- package/dist/extensions/forgecli/index.js +3 -3
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/update-check.js +1 -1
- package/dist/extensions/forgecli/update-check.js.map +1 -1
- package/dist/extensions/forgecli/whats-new-widget.d.ts +5 -5
- package/dist/extensions/forgecli/whats-new-widget.js +11 -11
- package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
- package/dist/extensions/forgecli/whats-new.js +6 -5
- package/dist/extensions/forgecli/whats-new.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +27 -98
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +62 -132
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +25 -15
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +17 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +8 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +17 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +8 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +63 -0
- package/node_modules/@earendil-works/pi-coding-agent/README.md +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js +6 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +12 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +30 -15
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.d.ts +3 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +23 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +58 -38
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js +0 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +3 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +7 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +6 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +3 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.d.ts +7 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.js +60 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.js.map +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/docs/packages.md +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +1 -3
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/package.json +6 -6
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@protobufjs/fetch/CHANGELOG.md +8 -0
- package/node_modules/@protobufjs/fetch/index.d.ts +7 -7
- package/node_modules/@protobufjs/fetch/index.js +4 -7
- package/node_modules/@protobufjs/fetch/package.json +7 -5
- package/node_modules/@protobufjs/fetch/tests/data/file.txt +1 -0
- package/node_modules/@protobufjs/fetch/tests/index.js +150 -8
- package/node_modules/@protobufjs/fetch/util/fs.js +11 -0
- package/node_modules/@protobufjs/inquire/CHANGELOG.md +8 -0
- package/node_modules/@protobufjs/inquire/index.d.ts +1 -0
- package/node_modules/@protobufjs/inquire/index.js +1 -0
- package/node_modules/@protobufjs/inquire/package.json +1 -1
- package/node_modules/protobufjs/dist/light/protobuf.js +187 -153
- package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.js +14 -5
- package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.js +207 -173
- package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/package.json +6 -3
- package/node_modules/protobufjs/src/util/fs.js +11 -0
- package/node_modules/protobufjs/src/util/minimal.js +10 -2
- package/node_modules/protobufjs/src/util.js +1 -1
- package/node_modules/undici/README.md +14 -5
- package/node_modules/undici/docs/docs/api/Client.md +4 -2
- package/node_modules/undici/docs/docs/api/Dispatcher.md +62 -27
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +7 -5
- package/node_modules/undici/docs/docs/api/H2CClient.md +1 -1
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +14 -9
- package/node_modules/undici/docs/docs/api/RetryAgent.md +0 -1
- package/node_modules/undici/docs/docs/api/RetryHandler.md +12 -14
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +23 -0
- package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +231 -0
- package/node_modules/undici/index.js +4 -2
- package/node_modules/undici/lib/api/api-connect.js +13 -11
- package/node_modules/undici/lib/api/api-pipeline.js +26 -13
- package/node_modules/undici/lib/api/api-request.js +45 -21
- package/node_modules/undici/lib/api/api-stream.js +81 -20
- package/node_modules/undici/lib/api/api-upgrade.js +21 -11
- package/node_modules/undici/lib/api/readable.js +3 -2
- package/node_modules/undici/lib/cache/memory-cache-store.js +1 -1
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +6 -4
- package/node_modules/undici/lib/core/connect.js +17 -1
- package/node_modules/undici/lib/core/constants.js +1 -24
- package/node_modules/undici/lib/core/errors.js +2 -2
- package/node_modules/undici/lib/core/request.js +115 -18
- package/node_modules/undici/lib/core/socks5-client.js +24 -9
- package/node_modules/undici/lib/core/socks5-utils.js +32 -23
- package/node_modules/undici/lib/core/symbols.js +1 -0
- package/node_modules/undici/lib/core/util.js +70 -43
- package/node_modules/undici/lib/dispatcher/agent.js +47 -33
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +21 -26
- package/node_modules/undici/lib/dispatcher/client-h1.js +98 -39
- package/node_modules/undici/lib/dispatcher/client-h2.js +603 -272
- package/node_modules/undici/lib/dispatcher/client.js +12 -5
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +24 -5
- package/node_modules/undici/lib/dispatcher/dispatcher.js +0 -4
- package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +107 -0
- package/node_modules/undici/lib/dispatcher/h2c-client.js +5 -5
- package/node_modules/undici/lib/dispatcher/pool-base.js +28 -10
- package/node_modules/undici/lib/dispatcher/pool.js +31 -6
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +38 -13
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +31 -9
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +95 -80
- package/node_modules/undici/lib/global.js +13 -1
- package/node_modules/undici/lib/handler/cache-handler.js +16 -8
- package/node_modules/undici/lib/handler/decorator-handler.js +1 -2
- package/node_modules/undici/lib/handler/redirect-handler.js +5 -51
- package/node_modules/undici/lib/handler/retry-handler.js +15 -2
- package/node_modules/undici/lib/interceptor/cache.js +30 -17
- package/node_modules/undici/lib/interceptor/decompress.js +28 -2
- package/node_modules/undici/lib/interceptor/dns.js +1 -1
- package/node_modules/undici/lib/interceptor/redirect.js +3 -3
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +1 -1
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +1 -1
- package/node_modules/undici/lib/mock/mock-agent.js +8 -8
- package/node_modules/undici/lib/mock/mock-call-history.js +15 -15
- package/node_modules/undici/lib/mock/mock-utils.js +37 -22
- package/node_modules/undici/lib/mock/snapshot-agent.js +16 -6
- package/node_modules/undici/lib/mock/snapshot-recorder.js +38 -3
- package/node_modules/undici/lib/util/cache.js +8 -7
- package/node_modules/undici/lib/util/runtime-features.js +3 -34
- package/node_modules/undici/lib/web/cache/cache.js +6 -8
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +245 -150
- package/node_modules/undici/lib/web/fetch/body.js +3 -9
- package/node_modules/undici/lib/web/fetch/formdata-parser.js +17 -6
- package/node_modules/undici/lib/web/fetch/formdata.js +21 -2
- package/node_modules/undici/lib/web/fetch/index.js +214 -221
- package/node_modules/undici/lib/web/webidl/index.js +7 -9
- package/node_modules/undici/lib/web/websocket/frame.js +1 -7
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +13 -31
- package/node_modules/undici/lib/web/websocket/receiver.js +62 -22
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +11 -17
- package/node_modules/undici/lib/web/websocket/websocket.js +6 -1
- package/node_modules/undici/package.json +9 -9
- package/node_modules/undici/types/agent.d.ts +0 -2
- package/node_modules/undici/types/client.d.ts +25 -19
- package/node_modules/undici/types/dispatcher.d.ts +7 -27
- package/node_modules/undici/types/dispatcher1-wrapper.d.ts +7 -0
- package/node_modules/undici/types/formdata.d.ts +0 -6
- package/node_modules/undici/types/h2c-client.d.ts +6 -6
- package/node_modules/undici/types/header.d.ts +5 -0
- package/node_modules/undici/types/index.d.ts +3 -1
- package/node_modules/undici/types/interceptors.d.ts +1 -1
- package/node_modules/undici/types/pool.d.ts +0 -2
- package/node_modules/undici/types/proxy-agent.d.ts +2 -2
- package/node_modules/undici/types/round-robin-pool.d.ts +0 -2
- package/node_modules/undici/types/snapshot-agent.d.ts +4 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -2
- package/node_modules/undici/types/webidl.d.ts +0 -1
- package/package.json +7 -8
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package-lock.json +0 -92
- package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package-lock.json +0 -31
- package/node_modules/undici/lib/handler/unwrap-handler.js +0 -100
- package/node_modules/undici/lib/handler/wrap-handler.js +0 -105
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/util/promise.js +0 -28
- package/skills/refresh-kb-links/SKILL.md +0 -217
- package/skills/store-custodian/SKILL.md +0 -163
- package/skills/store-query-grammar/SKILL.md +0 -145
- package/skills/store-query-nlp/SKILL.md +0 -110
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
kHTTP2Session,
|
|
29
29
|
kHTTP2InitialWindowSize,
|
|
30
30
|
kHTTP2ConnectionWindowSize,
|
|
31
|
+
kHostAuthority,
|
|
31
32
|
kResume,
|
|
32
33
|
kSize,
|
|
33
34
|
kHTTPContext,
|
|
@@ -41,6 +42,11 @@ const {
|
|
|
41
42
|
const { channels } = require('../core/diagnostics.js')
|
|
42
43
|
|
|
43
44
|
const kOpenStreams = Symbol('open streams')
|
|
45
|
+
const kRequestStreamId = Symbol('request stream id')
|
|
46
|
+
const kRequestStream = Symbol('request stream')
|
|
47
|
+
const kRequestStreamCleanup = Symbol('request stream cleanup')
|
|
48
|
+
const kRequestStreamState = Symbol('request stream state')
|
|
49
|
+
const kReceivedGoAway = Symbol('received goaway')
|
|
44
50
|
|
|
45
51
|
let extractBody
|
|
46
52
|
|
|
@@ -63,29 +69,75 @@ const {
|
|
|
63
69
|
HTTP2_HEADER_EXPECT,
|
|
64
70
|
HTTP2_HEADER_STATUS,
|
|
65
71
|
HTTP2_HEADER_PROTOCOL,
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
NGHTTP2_NO_ERROR,
|
|
73
|
+
NGHTTP2_REFUSED_STREAM
|
|
68
74
|
}
|
|
69
75
|
} = http2
|
|
70
76
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
function getGoAwayError (session, errorCode) {
|
|
78
|
+
return session[kError] ||
|
|
79
|
+
(errorCode === NGHTTP2_NO_ERROR
|
|
80
|
+
? new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`)
|
|
81
|
+
: new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(session[kSocket])))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getGoAwayPendingIdx (client, lastStreamID) {
|
|
85
|
+
const maxAcceptedStreamID = Number.isInteger(lastStreamID) ? lastStreamID : Number.MAX_SAFE_INTEGER
|
|
86
|
+
|
|
87
|
+
for (let i = client[kRunningIdx]; i < client[kPendingIdx]; i++) {
|
|
88
|
+
const request = client[kQueue][i]
|
|
89
|
+
|
|
90
|
+
if (request == null) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof request[kRequestStreamId] !== 'number' || request[kRequestStreamId] > maxAcceptedStreamID) {
|
|
95
|
+
return i
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
return
|
|
99
|
+
return client[kPendingIdx]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function detachRequestFromStream (request) {
|
|
103
|
+
request[kRequestStreamId] = null
|
|
104
|
+
request[kRequestStream] = null
|
|
105
|
+
request[kRequestStreamCleanup] = null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function bindRequestToStream (request, stream, cleanup) {
|
|
109
|
+
const previousCleanup = request[kRequestStreamCleanup]
|
|
110
|
+
const previousStream = request[kRequestStream]
|
|
111
|
+
detachRequestFromStream(request)
|
|
112
|
+
previousCleanup?.(previousStream)
|
|
113
|
+
request[kRequestStreamId] = stream.id
|
|
114
|
+
request[kRequestStream] = stream
|
|
115
|
+
request[kRequestStreamCleanup] = cleanup
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function clearRequestStream (request) {
|
|
119
|
+
const cleanup = request[kRequestStreamCleanup]
|
|
120
|
+
const stream = request[kRequestStream]
|
|
121
|
+
detachRequestFromStream(request)
|
|
122
|
+
cleanup?.(stream)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function canRetryRequestAfterGoAway (request) {
|
|
126
|
+
const { body } = request
|
|
127
|
+
|
|
128
|
+
return body == null || util.isBuffer(body) || util.isBlobLike(body)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function closeRequestStream (request, code = NGHTTP2_REFUSED_STREAM) {
|
|
132
|
+
const stream = request[kRequestStream]
|
|
133
|
+
|
|
134
|
+
clearRequestStream(request)
|
|
135
|
+
|
|
136
|
+
if (stream != null && !stream.destroyed && !stream.closed) {
|
|
137
|
+
try {
|
|
138
|
+
stream.close(code)
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
89
141
|
}
|
|
90
142
|
|
|
91
143
|
function connectH2 (client, socket) {
|
|
@@ -113,6 +165,7 @@ function connectH2 (client, socket) {
|
|
|
113
165
|
interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref()
|
|
114
166
|
}
|
|
115
167
|
}
|
|
168
|
+
session[kReceivedGoAway] = false
|
|
116
169
|
// We set it to true by default in a best-effort; however once connected to an H2 server
|
|
117
170
|
// we will check if extended CONNECT protocol is supported or not
|
|
118
171
|
// and set this value accordingly.
|
|
@@ -184,6 +237,14 @@ function connectH2 (client, socket) {
|
|
|
184
237
|
* @returns {boolean}
|
|
185
238
|
*/
|
|
186
239
|
busy (request) {
|
|
240
|
+
if (session[kRemoteSettings] === false && client[kRunning] > 0) {
|
|
241
|
+
return true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (client[kRunning] >= client[kMaxConcurrentStreams]) {
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
|
|
187
248
|
if (request != null) {
|
|
188
249
|
if (client[kRunning] > 0) {
|
|
189
250
|
// We are already processing requests
|
|
@@ -273,7 +334,7 @@ function onHttp2SendPing (session) {
|
|
|
273
334
|
|
|
274
335
|
function onPing (err, duration) {
|
|
275
336
|
const client = this[kClient]
|
|
276
|
-
const socket = this[
|
|
337
|
+
const socket = this[kSocket]
|
|
277
338
|
|
|
278
339
|
if (err != null) {
|
|
279
340
|
const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`)
|
|
@@ -313,48 +374,68 @@ function onHttp2SessionEnd () {
|
|
|
313
374
|
*
|
|
314
375
|
* @this {import('http2').ClientHttp2Session}
|
|
315
376
|
* @param {number} errorCode
|
|
377
|
+
* @param {number} lastStreamID
|
|
316
378
|
*/
|
|
317
|
-
function onHttp2SessionGoAway (errorCode) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
379
|
+
function onHttp2SessionGoAway (errorCode, lastStreamID) {
|
|
380
|
+
if (this[kReceivedGoAway]) {
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this[kReceivedGoAway] = true
|
|
321
385
|
|
|
322
|
-
const err = this
|
|
386
|
+
const err = getGoAwayError(this, errorCode)
|
|
323
387
|
const client = this[kClient]
|
|
388
|
+
const previousPendingIdx = client[kPendingIdx]
|
|
389
|
+
const pendingIdx = getGoAwayPendingIdx(client, lastStreamID)
|
|
390
|
+
const retriableRequests = []
|
|
324
391
|
|
|
325
|
-
|
|
326
|
-
|
|
392
|
+
for (let i = pendingIdx; i < previousPendingIdx; i++) {
|
|
393
|
+
const request = client[kQueue][i]
|
|
327
394
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this[kHTTP2Session] = null
|
|
395
|
+
if (request != null) {
|
|
396
|
+
closeRequestStream(request)
|
|
331
397
|
|
|
332
|
-
|
|
398
|
+
if (canRetryRequestAfterGoAway(request)) {
|
|
399
|
+
retriableRequests.push(request)
|
|
400
|
+
} else {
|
|
401
|
+
util.errorRequest(client, request, err)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
333
405
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
client[kQueue]
|
|
338
|
-
util.errorRequest(client, request, err)
|
|
339
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
406
|
+
if (pendingIdx !== previousPendingIdx) {
|
|
407
|
+
const remainingPendingRequests = client[kQueue].slice(previousPendingIdx)
|
|
408
|
+
client[kQueue].length = pendingIdx
|
|
409
|
+
client[kQueue].push(...retriableRequests, ...remainingPendingRequests)
|
|
340
410
|
}
|
|
341
411
|
|
|
342
|
-
|
|
412
|
+
if (client[kHTTP2Session] === this) {
|
|
413
|
+
client[kSocket] = null
|
|
414
|
+
client[kHTTPContext] = null
|
|
415
|
+
client[kHTTP2Session] = null
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!this.closed && !this.destroyed) {
|
|
419
|
+
this.close()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
client[kPendingIdx] = pendingIdx
|
|
343
423
|
|
|
344
424
|
client.emit('disconnect', client[kUrl], [client], err)
|
|
345
|
-
client.emit('connectionError', client[kUrl], [client], err)
|
|
346
425
|
|
|
347
426
|
client[kResume]()
|
|
348
427
|
}
|
|
349
428
|
|
|
350
429
|
function onHttp2SessionClose () {
|
|
351
|
-
const { [kClient]: client, [kHTTP2SessionState]: state } = this
|
|
352
|
-
const { [kSocket]: socket } = client
|
|
430
|
+
const { [kClient]: client, [kHTTP2SessionState]: state, [kSocket]: socket } = this
|
|
353
431
|
|
|
354
|
-
const err =
|
|
432
|
+
const err = socket[kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
|
|
355
433
|
|
|
356
|
-
client[
|
|
357
|
-
|
|
434
|
+
if (client[kHTTP2Session] === this) {
|
|
435
|
+
client[kSocket] = null
|
|
436
|
+
client[kHTTPContext] = null
|
|
437
|
+
client[kHTTP2Session] = null
|
|
438
|
+
}
|
|
358
439
|
|
|
359
440
|
if (state.ping.interval != null) {
|
|
360
441
|
clearInterval(state.ping.interval)
|
|
@@ -376,15 +457,27 @@ function onHttp2SessionClose () {
|
|
|
376
457
|
function onHttp2SocketClose () {
|
|
377
458
|
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
378
459
|
|
|
379
|
-
const
|
|
460
|
+
const session = this[kHTTP2Session]
|
|
461
|
+
const client = session[kClient]
|
|
462
|
+
|
|
463
|
+
if (client[kSocket] !== this) {
|
|
464
|
+
// Ignore stale socket closes from a detached GOAWAY session and from any
|
|
465
|
+
// session that has already been replaced. If the session was detached
|
|
466
|
+
// without a GOAWAY and there is no replacement yet, we still need the
|
|
467
|
+
// close event to flush the client state.
|
|
468
|
+
if (session[kReceivedGoAway] || (client[kHTTP2Session] != null && client[kHTTP2Session] !== session)) {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
}
|
|
380
472
|
|
|
381
473
|
client[kSocket] = null
|
|
382
474
|
client[kHTTPContext] = null
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this[kHTTP2Session].destroy(err)
|
|
475
|
+
if (client[kHTTP2Session] === session) {
|
|
476
|
+
client[kHTTP2Session] = null
|
|
386
477
|
}
|
|
387
478
|
|
|
479
|
+
session.destroy(err)
|
|
480
|
+
|
|
388
481
|
client[kPendingIdx] = client[kRunningIdx]
|
|
389
482
|
|
|
390
483
|
assert(client[kRunning] === 0)
|
|
@@ -410,30 +503,51 @@ function onSocketClose () {
|
|
|
410
503
|
this[kClosed] = true
|
|
411
504
|
}
|
|
412
505
|
|
|
506
|
+
function noop () {}
|
|
507
|
+
|
|
508
|
+
function closeStreamSession (stream) {
|
|
509
|
+
const session = stream[kHTTP2Session]
|
|
510
|
+
|
|
511
|
+
stream[kHTTP2Session] = null
|
|
512
|
+
session[kOpenStreams] -= 1
|
|
513
|
+
if (session[kOpenStreams] === 0) {
|
|
514
|
+
session.unref()
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function onUpgradeStreamClose () {
|
|
519
|
+
this.off('error', noop)
|
|
520
|
+
|
|
521
|
+
const state = this[kRequestStreamState]
|
|
522
|
+
this[kRequestStreamState] = null
|
|
523
|
+
|
|
524
|
+
failUpgradeStream(state, new InformationalError('HTTP/2: stream closed before response headers'))
|
|
525
|
+
closeStreamSession(this)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function onRequestStreamClose () {
|
|
529
|
+
this.off('data', onData)
|
|
530
|
+
this.off('error', noop)
|
|
531
|
+
closeStreamSession(this)
|
|
532
|
+
this[kRequestStreamState] = null
|
|
533
|
+
}
|
|
534
|
+
|
|
413
535
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
414
536
|
function shouldSendContentLength (method) {
|
|
415
537
|
return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
|
|
416
538
|
}
|
|
417
539
|
|
|
418
|
-
function
|
|
419
|
-
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
|
|
420
|
-
const session = client[kHTTP2Session]
|
|
421
|
-
const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
|
|
422
|
-
let { body } = request
|
|
423
|
-
|
|
424
|
-
if (upgrade != null && upgrade !== 'websocket') {
|
|
425
|
-
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`))
|
|
426
|
-
return false
|
|
427
|
-
}
|
|
428
|
-
|
|
540
|
+
function buildRequestHeaders (reqHeaders) {
|
|
429
541
|
const headers = {}
|
|
542
|
+
|
|
430
543
|
for (let n = 0; n < reqHeaders.length; n += 2) {
|
|
431
544
|
const key = reqHeaders[n + 0]
|
|
432
545
|
const val = reqHeaders[n + 1]
|
|
546
|
+
const current = headers[key]
|
|
433
547
|
|
|
434
548
|
if (key === 'cookie') {
|
|
435
|
-
if (
|
|
436
|
-
headers[key] = Array.isArray(
|
|
549
|
+
if (current != null) {
|
|
550
|
+
headers[key] = Array.isArray(current) ? (current.push(val), current) : [current, val]
|
|
437
551
|
} else {
|
|
438
552
|
headers[key] = val
|
|
439
553
|
}
|
|
@@ -441,30 +555,159 @@ function writeH2 (client, request) {
|
|
|
441
555
|
continue
|
|
442
556
|
}
|
|
443
557
|
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
} else if (headers[key]) {
|
|
453
|
-
headers[key] += `, ${val}`
|
|
454
|
-
} else {
|
|
455
|
-
headers[key] = val
|
|
558
|
+
if (typeof val === 'string') {
|
|
559
|
+
headers[key] = current ? `${current}, ${val}` : val
|
|
560
|
+
continue
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
for (let i = 0; i < val.length; i++) {
|
|
564
|
+
headers[key] = headers[key] ? `${headers[key]}, ${val[i]}` : val[i]
|
|
456
565
|
}
|
|
457
566
|
}
|
|
458
567
|
|
|
568
|
+
return headers
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function removeUpgradeStreamListeners (stream) {
|
|
572
|
+
stream.off('response', onUpgradeResponse)
|
|
573
|
+
stream.off('error', onUpgradeStreamError)
|
|
574
|
+
stream.off('end', onUpgradeStreamEnd)
|
|
575
|
+
stream.off('timeout', onUpgradeStreamTimeout)
|
|
576
|
+
stream.off('error', noop)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function releaseUpgradeStream (stream) {
|
|
580
|
+
if (stream == null) {
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const state = stream[kRequestStreamState]
|
|
585
|
+
if (state == null) {
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const { request } = state
|
|
590
|
+
|
|
591
|
+
if (request[kRequestStream] === stream) {
|
|
592
|
+
detachRequestFromStream(request)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
removeUpgradeStreamListeners(stream)
|
|
596
|
+
|
|
597
|
+
if (!stream.destroyed && !stream.closed) {
|
|
598
|
+
stream.once('error', noop)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function failUpgradeStream (state, err) {
|
|
603
|
+
if (state == null) {
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const { request } = state
|
|
608
|
+
if (state.responseReceived || request.aborted || request.completed) {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
releaseUpgradeStream(state.stream)
|
|
613
|
+
state.abort(err, true)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function onUpgradeStreamError () {
|
|
617
|
+
const state = this[kRequestStreamState]
|
|
618
|
+
|
|
619
|
+
if (typeof this.rstCode === 'number' && this.rstCode !== 0) {
|
|
620
|
+
failUpgradeStream(state, new InformationalError(`HTTP/2: "stream error" received - code ${this.rstCode}`))
|
|
621
|
+
} else {
|
|
622
|
+
failUpgradeStream(state, new InformationalError('HTTP/2: stream errored before response headers'))
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function onUpgradeStreamEnd () {
|
|
627
|
+
failUpgradeStream(this[kRequestStreamState], new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function onUpgradeStreamTimeout () {
|
|
631
|
+
const state = this[kRequestStreamState]
|
|
632
|
+
failUpgradeStream(state, new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`))
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function onUpgradeResponse (headers, _flags) {
|
|
636
|
+
const stream = this
|
|
637
|
+
const state = stream[kRequestStreamState]
|
|
638
|
+
const { request } = state
|
|
639
|
+
|
|
640
|
+
state.responseReceived = true
|
|
641
|
+
|
|
642
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
643
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
644
|
+
|
|
645
|
+
request.onRequestUpgrade(statusCode, headers, stream)
|
|
646
|
+
|
|
647
|
+
if (request.aborted || request.completed) {
|
|
648
|
+
return
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
removeUpgradeStreamListeners(stream)
|
|
652
|
+
detachRequestFromStream(request)
|
|
653
|
+
state.finalizeRequest()
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function setupUpgradeStream (stream, state) {
|
|
657
|
+
const { request, requestTimeout, session } = state
|
|
658
|
+
|
|
659
|
+
stream[kHTTP2Stream] = true
|
|
660
|
+
stream[kHTTP2Session] = session
|
|
661
|
+
stream[kRequestStreamState] = state
|
|
662
|
+
state.stream = stream
|
|
663
|
+
|
|
664
|
+
bindRequestToStream(request, stream, releaseUpgradeStream)
|
|
665
|
+
stream.once('response', onUpgradeResponse)
|
|
666
|
+
stream.on('error', onUpgradeStreamError)
|
|
667
|
+
stream.once('end', onUpgradeStreamEnd)
|
|
668
|
+
stream.on('timeout', onUpgradeStreamTimeout)
|
|
669
|
+
stream.once('close', onUpgradeStreamClose)
|
|
670
|
+
|
|
671
|
+
++session[kOpenStreams]
|
|
672
|
+
stream.setTimeout(requestTimeout)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function writeH2 (client, request) {
|
|
676
|
+
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
|
|
677
|
+
const session = client[kHTTP2Session]
|
|
678
|
+
const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
|
|
679
|
+
let { body } = request
|
|
680
|
+
|
|
681
|
+
if (upgrade != null && upgrade !== 'websocket') {
|
|
682
|
+
util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`))
|
|
683
|
+
return false
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const headers = buildRequestHeaders(reqHeaders)
|
|
687
|
+
|
|
459
688
|
/** @type {import('node:http2').ClientHttp2Stream} */
|
|
460
689
|
let stream = null
|
|
461
690
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
|
691
|
+
headers[HTTP2_HEADER_AUTHORITY] = host || client[kHostAuthority]
|
|
465
692
|
headers[HTTP2_HEADER_METHOD] = method
|
|
466
693
|
|
|
467
|
-
|
|
694
|
+
let requestFinalized = false
|
|
695
|
+
const finalizeRequest = (resetPendingIdx = false) => {
|
|
696
|
+
if (requestFinalized) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
requestFinalized = true
|
|
701
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
702
|
+
|
|
703
|
+
if (resetPendingIdx) {
|
|
704
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
client[kResume]()
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const abort = (err, resetPendingIdx = false) => {
|
|
468
711
|
if (request.aborted || request.completed) {
|
|
469
712
|
return
|
|
470
713
|
}
|
|
@@ -474,16 +717,14 @@ function writeH2 (client, request) {
|
|
|
474
717
|
util.errorRequest(client, request, err)
|
|
475
718
|
|
|
476
719
|
if (stream != null) {
|
|
477
|
-
|
|
478
|
-
// let's ignore them
|
|
479
|
-
stream.removeAllListeners('data')
|
|
720
|
+
clearRequestStream(request)
|
|
480
721
|
|
|
481
722
|
// On Abort, we close the stream to send RST_STREAM frame
|
|
482
723
|
stream.close()
|
|
483
724
|
|
|
484
725
|
// We move the running index to the next request
|
|
485
726
|
client[kOnError](err)
|
|
486
|
-
|
|
727
|
+
finalizeRequest(resetPendingIdx)
|
|
487
728
|
}
|
|
488
729
|
|
|
489
730
|
// We do not destroy the socket as we can continue using the session
|
|
@@ -491,10 +732,30 @@ function writeH2 (client, request) {
|
|
|
491
732
|
util.destroy(body, err)
|
|
492
733
|
}
|
|
493
734
|
|
|
735
|
+
const requestStream = (headers, options) => {
|
|
736
|
+
try {
|
|
737
|
+
return session.request(headers, options)
|
|
738
|
+
} catch (err) {
|
|
739
|
+
if (err?.code !== 'ERR_HTTP2_INVALID_CONNECTION_HEADERS') {
|
|
740
|
+
throw err
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const wrappedErr = new InformationalError(err.message, { cause: err })
|
|
744
|
+
session[kError] = wrappedErr
|
|
745
|
+
session[kSocket][kError] = wrappedErr
|
|
746
|
+
|
|
747
|
+
session.destroy(wrappedErr)
|
|
748
|
+
util.destroy(session[kSocket], wrappedErr)
|
|
749
|
+
abort(wrappedErr)
|
|
750
|
+
|
|
751
|
+
return null
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
494
755
|
try {
|
|
495
756
|
// We are already connected, streams are pending.
|
|
496
757
|
// We can call on connect, and wait for abort
|
|
497
|
-
request.
|
|
758
|
+
request.onRequestStart(abort, null)
|
|
498
759
|
} catch (err) {
|
|
499
760
|
util.errorRequest(client, request, err)
|
|
500
761
|
}
|
|
@@ -506,6 +767,16 @@ function writeH2 (client, request) {
|
|
|
506
767
|
if (upgrade || method === 'CONNECT') {
|
|
507
768
|
session.ref()
|
|
508
769
|
|
|
770
|
+
const upgradeState = {
|
|
771
|
+
abort,
|
|
772
|
+
finalizeRequest,
|
|
773
|
+
request,
|
|
774
|
+
requestTimeout,
|
|
775
|
+
responseReceived: false,
|
|
776
|
+
session,
|
|
777
|
+
stream: null
|
|
778
|
+
}
|
|
779
|
+
|
|
509
780
|
if (upgrade === 'websocket') {
|
|
510
781
|
// We cannot upgrade to websocket if extended CONNECT protocol is not supported
|
|
511
782
|
if (session[kEnableConnectProtocol] === false) {
|
|
@@ -528,33 +799,12 @@ function writeH2 (client, request) {
|
|
|
528
799
|
headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
|
|
529
800
|
}
|
|
530
801
|
|
|
531
|
-
stream =
|
|
532
|
-
stream
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
request.onUpgrade(statusCode, parseH2Headers(realHeaders), stream)
|
|
538
|
-
|
|
539
|
-
++session[kOpenStreams]
|
|
540
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
stream.on('error', () => {
|
|
544
|
-
if (stream.rstCode === NGHTTP2_REFUSED_STREAM || stream.rstCode === NGHTTP2_CANCEL) {
|
|
545
|
-
// NGHTTP2_REFUSED_STREAM (7) or NGHTTP2_CANCEL (8)
|
|
546
|
-
// We do not treat those as errors as the server might
|
|
547
|
-
// not support websockets and refuse the stream
|
|
548
|
-
abort(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
|
|
549
|
-
}
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
stream.once('close', () => {
|
|
553
|
-
session[kOpenStreams] -= 1
|
|
554
|
-
if (session[kOpenStreams] === 0) session.unref()
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
stream.setTimeout(requestTimeout)
|
|
802
|
+
stream = requestStream(headers, { endStream: false, signal })
|
|
803
|
+
if (stream == null) {
|
|
804
|
+
session.unref()
|
|
805
|
+
return false
|
|
806
|
+
}
|
|
807
|
+
setupUpgradeStream(stream, upgradeState)
|
|
558
808
|
return true
|
|
559
809
|
}
|
|
560
810
|
|
|
@@ -563,20 +813,12 @@ function writeH2 (client, request) {
|
|
|
563
813
|
// will create a new stream. We trigger a request to create the stream and wait until
|
|
564
814
|
// `ready` event is triggered
|
|
565
815
|
// We disabled endStream to allow the user to write to the stream
|
|
566
|
-
stream =
|
|
567
|
-
stream
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
++session[kOpenStreams]
|
|
573
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
574
|
-
})
|
|
575
|
-
stream.once('close', () => {
|
|
576
|
-
session[kOpenStreams] -= 1
|
|
577
|
-
if (session[kOpenStreams] === 0) session.unref()
|
|
578
|
-
})
|
|
579
|
-
stream.setTimeout(requestTimeout)
|
|
816
|
+
stream = requestStream(headers, { endStream: false, signal })
|
|
817
|
+
if (stream == null) {
|
|
818
|
+
session.unref()
|
|
819
|
+
return false
|
|
820
|
+
}
|
|
821
|
+
setupUpgradeStream(stream, upgradeState)
|
|
580
822
|
|
|
581
823
|
return true
|
|
582
824
|
}
|
|
@@ -598,7 +840,10 @@ function writeH2 (client, request) {
|
|
|
598
840
|
const expectsPayload = (
|
|
599
841
|
method === 'PUT' ||
|
|
600
842
|
method === 'POST' ||
|
|
601
|
-
method === 'PATCH'
|
|
843
|
+
method === 'PATCH' ||
|
|
844
|
+
method === 'QUERY' ||
|
|
845
|
+
method === 'PROPFIND' ||
|
|
846
|
+
method === 'PROPPATCH'
|
|
602
847
|
)
|
|
603
848
|
|
|
604
849
|
if (body && typeof body.read === 'function') {
|
|
@@ -622,7 +867,7 @@ function writeH2 (client, request) {
|
|
|
622
867
|
contentLength = request.contentLength
|
|
623
868
|
}
|
|
624
869
|
|
|
625
|
-
if (!expectsPayload) {
|
|
870
|
+
if (contentLength === 0 && !expectsPayload) {
|
|
626
871
|
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
627
872
|
// A user agent SHOULD NOT send a Content-Length header field when
|
|
628
873
|
// the request message does not contain a payload body and the method
|
|
@@ -658,185 +903,251 @@ function writeH2 (client, request) {
|
|
|
658
903
|
}
|
|
659
904
|
|
|
660
905
|
// TODO(metcoder95): add support for sending trailers
|
|
661
|
-
const shouldEndStream =
|
|
906
|
+
const shouldEndStream = body === null || contentLength === 0
|
|
907
|
+
const state = {
|
|
908
|
+
abort,
|
|
909
|
+
body,
|
|
910
|
+
client,
|
|
911
|
+
contentLength,
|
|
912
|
+
expectsPayload,
|
|
913
|
+
finalizeRequest,
|
|
914
|
+
request,
|
|
915
|
+
requestTimeout,
|
|
916
|
+
responseReceived: false,
|
|
917
|
+
session,
|
|
918
|
+
stream: null
|
|
919
|
+
}
|
|
920
|
+
|
|
662
921
|
if (expectContinue) {
|
|
663
922
|
headers[HTTP2_HEADER_EXPECT] = '100-continue'
|
|
664
|
-
|
|
665
|
-
stream[kHTTP2Stream] = true
|
|
666
|
-
|
|
667
|
-
stream.once('continue', writeBodyH2)
|
|
668
|
-
} else {
|
|
669
|
-
stream = session.request(headers, {
|
|
670
|
-
endStream: shouldEndStream,
|
|
671
|
-
signal
|
|
672
|
-
})
|
|
673
|
-
stream[kHTTP2Stream] = true
|
|
923
|
+
}
|
|
674
924
|
|
|
675
|
-
|
|
925
|
+
stream = requestStream(headers, { endStream: shouldEndStream, signal })
|
|
926
|
+
if (stream == null) {
|
|
927
|
+
return false
|
|
676
928
|
}
|
|
929
|
+
stream[kHTTP2Stream] = true
|
|
930
|
+
stream[kRequestStreamState] = state
|
|
931
|
+
state.stream = stream
|
|
932
|
+
bindRequestToStream(request, stream, null)
|
|
677
933
|
|
|
678
934
|
// Increment counter as we have new streams open
|
|
679
935
|
++session[kOpenStreams]
|
|
680
936
|
stream.setTimeout(requestTimeout)
|
|
681
937
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
stream.once('response', headers => {
|
|
686
|
-
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
687
|
-
request.onResponseStarted()
|
|
688
|
-
responseReceived = true
|
|
689
|
-
|
|
690
|
-
// Due to the stream nature, it is possible we face a race condition
|
|
691
|
-
// where the stream has been assigned, but the request has been aborted
|
|
692
|
-
// the request remains in-flight and headers hasn't been received yet
|
|
693
|
-
// for those scenarios, best effort is to destroy the stream immediately
|
|
694
|
-
// as there's no value to keep it open.
|
|
695
|
-
if (request.aborted) {
|
|
696
|
-
stream.removeAllListeners('data')
|
|
697
|
-
return
|
|
698
|
-
}
|
|
938
|
+
stream[kHTTP2Session] = session
|
|
939
|
+
stream.once('close', onRequestStreamClose)
|
|
699
940
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
941
|
+
bindRequestToStream(request, stream, releaseRequestStream)
|
|
942
|
+
if (expectContinue) {
|
|
943
|
+
stream.once('continue', writeBodyH2)
|
|
944
|
+
}
|
|
945
|
+
stream.once('response', onResponse)
|
|
946
|
+
stream.once('end', onEnd)
|
|
947
|
+
stream.once('error', onError)
|
|
948
|
+
stream.once('frameError', onFrameError)
|
|
949
|
+
stream.on('aborted', onAborted)
|
|
950
|
+
stream.on('timeout', onTimeout)
|
|
951
|
+
stream.once('trailers', onTrailers)
|
|
952
|
+
|
|
953
|
+
if (!expectContinue) {
|
|
954
|
+
writeBodyH2.call(stream)
|
|
955
|
+
}
|
|
703
956
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return
|
|
707
|
-
}
|
|
957
|
+
return true
|
|
958
|
+
}
|
|
708
959
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
960
|
+
function removeRequestStreamListeners (stream) {
|
|
961
|
+
stream.off('error', noop)
|
|
962
|
+
stream.off('continue', writeBodyH2)
|
|
963
|
+
stream.off('response', onResponse)
|
|
964
|
+
stream.off('end', onEnd)
|
|
965
|
+
stream.off('error', onError)
|
|
966
|
+
stream.off('frameError', onFrameError)
|
|
967
|
+
stream.off('aborted', onAborted)
|
|
968
|
+
stream.off('timeout', onTimeout)
|
|
969
|
+
stream.off('trailers', onTrailers)
|
|
970
|
+
stream.off('data', onData)
|
|
971
|
+
}
|
|
714
972
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (!request.aborted && !request.completed) {
|
|
720
|
-
request.onComplete({})
|
|
721
|
-
}
|
|
973
|
+
function releaseRequestStream (stream) {
|
|
974
|
+
if (stream == null) {
|
|
975
|
+
return
|
|
976
|
+
}
|
|
722
977
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// (e.g., server destroyed the stream before sending headers)
|
|
728
|
-
abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
729
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
730
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
731
|
-
client[kResume]()
|
|
732
|
-
}
|
|
733
|
-
})
|
|
978
|
+
const state = stream[kRequestStreamState]
|
|
979
|
+
if (state == null) {
|
|
980
|
+
return
|
|
981
|
+
}
|
|
734
982
|
|
|
735
|
-
|
|
736
|
-
stream.removeAllListeners('data')
|
|
737
|
-
session[kOpenStreams] -= 1
|
|
738
|
-
if (session[kOpenStreams] === 0) {
|
|
739
|
-
session.unref()
|
|
740
|
-
}
|
|
741
|
-
})
|
|
983
|
+
const { request } = state
|
|
742
984
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
})
|
|
985
|
+
if (request[kRequestStream] === stream) {
|
|
986
|
+
detachRequestFromStream(request)
|
|
987
|
+
}
|
|
747
988
|
|
|
748
|
-
stream
|
|
749
|
-
stream.removeAllListeners('data')
|
|
750
|
-
abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
751
|
-
})
|
|
989
|
+
removeRequestStreamListeners(stream)
|
|
752
990
|
|
|
753
|
-
stream.
|
|
754
|
-
stream.
|
|
755
|
-
}
|
|
991
|
+
if (!stream.destroyed && !stream.closed) {
|
|
992
|
+
stream.once('error', noop)
|
|
993
|
+
}
|
|
994
|
+
}
|
|
756
995
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
session[kOpenStreams] -= 1
|
|
996
|
+
function onData (chunk) {
|
|
997
|
+
const stream = this
|
|
998
|
+
const { request } = stream[kRequestStreamState]
|
|
761
999
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1000
|
+
if (request.aborted || request.completed) {
|
|
1001
|
+
return
|
|
1002
|
+
}
|
|
765
1003
|
|
|
766
|
-
|
|
767
|
-
|
|
1004
|
+
if (request.onResponseData(chunk) === false) {
|
|
1005
|
+
stream.pause()
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
768
1008
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1009
|
+
function onResponse (headers) {
|
|
1010
|
+
const stream = this
|
|
1011
|
+
const state = stream[kRequestStreamState]
|
|
1012
|
+
const { request } = state
|
|
1013
|
+
|
|
1014
|
+
stream.off('response', onResponse)
|
|
1015
|
+
|
|
1016
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
1017
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
1018
|
+
request.onResponseStarted()
|
|
1019
|
+
state.responseReceived = true
|
|
1020
|
+
|
|
1021
|
+
// Due to the stream nature, it is possible we face a race condition
|
|
1022
|
+
// where the stream has been assigned, but the request has been aborted
|
|
1023
|
+
// the request remains in-flight and headers hasn't been received yet
|
|
1024
|
+
// for those scenarios, best effort is to destroy the stream immediately
|
|
1025
|
+
// as there's no value to keep it open.
|
|
1026
|
+
if (request.aborted) {
|
|
1027
|
+
releaseRequestStream(stream)
|
|
1028
|
+
return
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) {
|
|
1032
|
+
stream.pause()
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
stream.on('data', onData)
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function onEnd () {
|
|
1039
|
+
const stream = this
|
|
1040
|
+
const state = stream[kRequestStreamState]
|
|
1041
|
+
const { request } = state
|
|
1042
|
+
|
|
1043
|
+
stream.off('end', onEnd)
|
|
1044
|
+
|
|
1045
|
+
releaseRequestStream(stream)
|
|
1046
|
+
// If we received a response, this is a normal completion
|
|
1047
|
+
if (state.responseReceived) {
|
|
1048
|
+
if (!request.aborted && !request.completed) {
|
|
1049
|
+
request.onResponseEnd({})
|
|
772
1050
|
}
|
|
773
1051
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1052
|
+
state.finalizeRequest()
|
|
1053
|
+
} else {
|
|
1054
|
+
// Stream ended without receiving a response - this is an error
|
|
1055
|
+
// (e.g., server destroyed the stream before sending headers)
|
|
1056
|
+
state.abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true)
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
777
1059
|
|
|
778
|
-
|
|
1060
|
+
function onError (err) {
|
|
1061
|
+
const stream = this
|
|
1062
|
+
const state = stream[kRequestStreamState]
|
|
779
1063
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1064
|
+
stream.off('error', onError)
|
|
1065
|
+
|
|
1066
|
+
releaseRequestStream(stream)
|
|
1067
|
+
state.abort(err)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function onFrameError (type, code) {
|
|
1071
|
+
const stream = this
|
|
1072
|
+
const state = stream[kRequestStreamState]
|
|
1073
|
+
|
|
1074
|
+
stream.off('frameError', onFrameError)
|
|
1075
|
+
|
|
1076
|
+
releaseRequestStream(stream)
|
|
1077
|
+
state.abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function onAborted () {
|
|
1081
|
+
this.off('data', onData)
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function onTimeout () {
|
|
1085
|
+
const stream = this
|
|
1086
|
+
const state = stream[kRequestStreamState]
|
|
1087
|
+
|
|
1088
|
+
releaseRequestStream(stream)
|
|
1089
|
+
|
|
1090
|
+
const err = new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`)
|
|
1091
|
+
state.abort(err)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function onTrailers (trailers) {
|
|
1095
|
+
const stream = this
|
|
1096
|
+
const state = stream[kRequestStreamState]
|
|
1097
|
+
const { request } = state
|
|
1098
|
+
|
|
1099
|
+
stream.off('trailers', onTrailers)
|
|
1100
|
+
|
|
1101
|
+
if (request.aborted || request.completed) {
|
|
1102
|
+
return
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
releaseRequestStream(stream)
|
|
1106
|
+
request.onResponseEnd(trailers)
|
|
1107
|
+
state.finalizeRequest()
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function writeBodyH2 () {
|
|
1111
|
+
const stream = this
|
|
1112
|
+
const state = stream[kRequestStreamState]
|
|
1113
|
+
const { abort, body, client, contentLength, expectsPayload, request } = state
|
|
1114
|
+
|
|
1115
|
+
if (!body || contentLength === 0) {
|
|
1116
|
+
writeBuffer(
|
|
1117
|
+
abort,
|
|
1118
|
+
stream,
|
|
1119
|
+
null,
|
|
1120
|
+
client,
|
|
1121
|
+
request,
|
|
1122
|
+
client[kSocket],
|
|
1123
|
+
contentLength,
|
|
1124
|
+
expectsPayload
|
|
1125
|
+
)
|
|
1126
|
+
} else if (util.isBuffer(body)) {
|
|
1127
|
+
writeBuffer(
|
|
1128
|
+
abort,
|
|
1129
|
+
stream,
|
|
1130
|
+
body,
|
|
1131
|
+
client,
|
|
1132
|
+
request,
|
|
1133
|
+
client[kSocket],
|
|
1134
|
+
contentLength,
|
|
1135
|
+
expectsPayload
|
|
1136
|
+
)
|
|
1137
|
+
} else if (util.isBlobLike(body)) {
|
|
1138
|
+
if (typeof body.stream === 'function') {
|
|
1139
|
+
writeIterable(
|
|
794
1140
|
abort,
|
|
795
1141
|
stream,
|
|
796
|
-
body,
|
|
1142
|
+
body.stream(),
|
|
797
1143
|
client,
|
|
798
1144
|
request,
|
|
799
1145
|
client[kSocket],
|
|
800
1146
|
contentLength,
|
|
801
1147
|
expectsPayload
|
|
802
1148
|
)
|
|
803
|
-
} else
|
|
804
|
-
|
|
805
|
-
writeIterable(
|
|
806
|
-
abort,
|
|
807
|
-
stream,
|
|
808
|
-
body.stream(),
|
|
809
|
-
client,
|
|
810
|
-
request,
|
|
811
|
-
client[kSocket],
|
|
812
|
-
contentLength,
|
|
813
|
-
expectsPayload
|
|
814
|
-
)
|
|
815
|
-
} else {
|
|
816
|
-
writeBlob(
|
|
817
|
-
abort,
|
|
818
|
-
stream,
|
|
819
|
-
body,
|
|
820
|
-
client,
|
|
821
|
-
request,
|
|
822
|
-
client[kSocket],
|
|
823
|
-
contentLength,
|
|
824
|
-
expectsPayload
|
|
825
|
-
)
|
|
826
|
-
}
|
|
827
|
-
} else if (util.isStream(body)) {
|
|
828
|
-
writeStream(
|
|
829
|
-
abort,
|
|
830
|
-
client[kSocket],
|
|
831
|
-
expectsPayload,
|
|
832
|
-
stream,
|
|
833
|
-
body,
|
|
834
|
-
client,
|
|
835
|
-
request,
|
|
836
|
-
contentLength
|
|
837
|
-
)
|
|
838
|
-
} else if (util.isIterable(body)) {
|
|
839
|
-
writeIterable(
|
|
1149
|
+
} else {
|
|
1150
|
+
writeBlob(
|
|
840
1151
|
abort,
|
|
841
1152
|
stream,
|
|
842
1153
|
body,
|
|
@@ -846,9 +1157,31 @@ function writeH2 (client, request) {
|
|
|
846
1157
|
contentLength,
|
|
847
1158
|
expectsPayload
|
|
848
1159
|
)
|
|
849
|
-
} else {
|
|
850
|
-
assert(false)
|
|
851
1160
|
}
|
|
1161
|
+
} else if (util.isStream(body)) {
|
|
1162
|
+
writeStream(
|
|
1163
|
+
abort,
|
|
1164
|
+
client[kSocket],
|
|
1165
|
+
expectsPayload,
|
|
1166
|
+
stream,
|
|
1167
|
+
body,
|
|
1168
|
+
client,
|
|
1169
|
+
request,
|
|
1170
|
+
contentLength
|
|
1171
|
+
)
|
|
1172
|
+
} else if (util.isIterable(body)) {
|
|
1173
|
+
writeIterable(
|
|
1174
|
+
abort,
|
|
1175
|
+
stream,
|
|
1176
|
+
body,
|
|
1177
|
+
client,
|
|
1178
|
+
request,
|
|
1179
|
+
client[kSocket],
|
|
1180
|
+
contentLength,
|
|
1181
|
+
expectsPayload
|
|
1182
|
+
)
|
|
1183
|
+
} else {
|
|
1184
|
+
assert(false)
|
|
852
1185
|
}
|
|
853
1186
|
}
|
|
854
1187
|
|
|
@@ -907,8 +1240,6 @@ function writeStream (abort, socket, expectsPayload, h2stream, body, client, req
|
|
|
907
1240
|
}
|
|
908
1241
|
|
|
909
1242
|
async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
910
|
-
assert(contentLength === body.size, 'blob body must have content length')
|
|
911
|
-
|
|
912
1243
|
try {
|
|
913
1244
|
if (contentLength != null && contentLength !== body.size) {
|
|
914
1245
|
throw new RequestContentLengthMismatchError()
|