@nocobase/plugin-idp-oauth 2.1.0-alpha.17 → 2.1.0-alpha.19
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/build.config.ts +1 -1
- package/dist/externalVersion.js +6 -4
- package/dist/node_modules/light-my-request/package.json +1 -1
- package/dist/node_modules/undici/LICENSE +21 -0
- package/dist/node_modules/undici/README.md +741 -0
- package/dist/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/dist/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/dist/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/dist/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/dist/node_modules/undici/docs/docs/api/Client.md +285 -0
- package/dist/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/dist/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/dist/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/dist/node_modules/undici/docs/docs/api/Cookies.md +101 -0
- package/dist/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/dist/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +315 -0
- package/dist/node_modules/undici/docs/docs/api/Dispatcher.md +1392 -0
- package/dist/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/dist/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/dist/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/dist/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/dist/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/dist/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/dist/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/dist/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/dist/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/dist/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/dist/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/dist/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/dist/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/dist/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/dist/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/dist/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/dist/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/dist/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/dist/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/dist/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/dist/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +274 -0
- package/dist/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/dist/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/dist/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/dist/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/dist/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/dist/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/dist/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/dist/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/dist/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/dist/node_modules/undici/index-fetch.js +65 -0
- package/dist/node_modules/undici/index.d.ts +3 -0
- package/dist/node_modules/undici/index.js +234 -0
- package/dist/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/dist/node_modules/undici/lib/api/api-connect.js +110 -0
- package/dist/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/dist/node_modules/undici/lib/api/api-request.js +214 -0
- package/dist/node_modules/undici/lib/api/api-stream.js +209 -0
- package/dist/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/dist/node_modules/undici/lib/api/index.js +7 -0
- package/dist/node_modules/undici/lib/api/readable.js +580 -0
- package/dist/node_modules/undici/lib/cache/memory-cache-store.js +234 -0
- package/dist/node_modules/undici/lib/cache/sqlite-cache-store.js +461 -0
- package/dist/node_modules/undici/lib/core/connect.js +137 -0
- package/dist/node_modules/undici/lib/core/constants.js +143 -0
- package/dist/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/dist/node_modules/undici/lib/core/errors.js +477 -0
- package/dist/node_modules/undici/lib/core/request.js +438 -0
- package/dist/node_modules/undici/lib/core/socks5-client.js +407 -0
- package/dist/node_modules/undici/lib/core/socks5-utils.js +203 -0
- package/dist/node_modules/undici/lib/core/symbols.js +75 -0
- package/dist/node_modules/undici/lib/core/tree.js +160 -0
- package/dist/node_modules/undici/lib/core/util.js +992 -0
- package/dist/node_modules/undici/lib/dispatcher/agent.js +158 -0
- package/dist/node_modules/undici/lib/dispatcher/balanced-pool.js +219 -0
- package/dist/node_modules/undici/lib/dispatcher/client-h1.js +1610 -0
- package/dist/node_modules/undici/lib/dispatcher/client-h2.js +995 -0
- package/dist/node_modules/undici/lib/dispatcher/client.js +659 -0
- package/dist/node_modules/undici/lib/dispatcher/dispatcher-base.js +165 -0
- package/dist/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/dist/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/dist/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/dist/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/dist/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/dist/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/dist/node_modules/undici/lib/dispatcher/proxy-agent.js +318 -0
- package/dist/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/dist/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/dist/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +249 -0
- package/dist/node_modules/undici/lib/encoding/index.js +33 -0
- package/dist/node_modules/undici/lib/global.js +50 -0
- package/dist/node_modules/undici/lib/handler/cache-handler.js +578 -0
- package/dist/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/dist/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/dist/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
- package/dist/node_modules/undici/lib/handler/redirect-handler.js +238 -0
- package/dist/node_modules/undici/lib/handler/retry-handler.js +394 -0
- package/dist/node_modules/undici/lib/handler/unwrap-handler.js +100 -0
- package/dist/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/dist/node_modules/undici/lib/interceptor/cache.js +495 -0
- package/dist/node_modules/undici/lib/interceptor/decompress.js +259 -0
- package/dist/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/dist/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/dist/node_modules/undici/lib/interceptor/dump.js +112 -0
- package/dist/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/dist/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/dist/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/dist/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/dist/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/dist/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/dist/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/dist/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/dist/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/dist/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/dist/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/dist/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/dist/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/dist/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/dist/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/dist/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/dist/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/dist/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/dist/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/dist/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/dist/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/dist/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/dist/node_modules/undici/lib/util/cache.js +407 -0
- package/dist/node_modules/undici/lib/util/date.js +653 -0
- package/dist/node_modules/undici/lib/util/promise.js +28 -0
- package/dist/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/dist/node_modules/undici/lib/util/stats.js +32 -0
- package/dist/node_modules/undici/lib/util/timers.js +425 -0
- package/dist/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/dist/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/dist/node_modules/undici/lib/web/cache/util.js +45 -0
- package/dist/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/dist/node_modules/undici/lib/web/cookies/index.js +199 -0
- package/dist/node_modules/undici/lib/web/cookies/parse.js +322 -0
- package/dist/node_modules/undici/lib/web/cookies/util.js +282 -0
- package/dist/node_modules/undici/lib/web/eventsource/eventsource-stream.js +399 -0
- package/dist/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
- package/dist/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/dist/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/dist/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/dist/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/dist/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/dist/node_modules/undici/lib/web/fetch/formdata-parser.js +575 -0
- package/dist/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/dist/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/dist/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/dist/node_modules/undici/lib/web/fetch/index.js +2397 -0
- package/dist/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/dist/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/dist/node_modules/undici/lib/web/fetch/util.js +1520 -0
- package/dist/node_modules/undici/lib/web/infra/index.js +229 -0
- package/dist/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/dist/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/dist/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/dist/node_modules/undici/lib/web/websocket/connection.js +329 -0
- package/dist/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/dist/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/dist/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/dist/node_modules/undici/lib/web/websocket/permessage-deflate.js +118 -0
- package/dist/node_modules/undici/lib/web/websocket/receiver.js +450 -0
- package/dist/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/dist/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/dist/node_modules/undici/lib/web/websocket/stream/websocketstream.js +497 -0
- package/dist/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/dist/node_modules/undici/lib/web/websocket/websocket.js +751 -0
- package/dist/node_modules/undici/package.json +152 -0
- package/dist/node_modules/undici/scripts/strip-comments.js +10 -0
- package/dist/node_modules/undici/types/README.md +6 -0
- package/dist/node_modules/undici/types/agent.d.ts +32 -0
- package/dist/node_modules/undici/types/api.d.ts +43 -0
- package/dist/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/dist/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/dist/node_modules/undici/types/cache.d.ts +36 -0
- package/dist/node_modules/undici/types/client-stats.d.ts +15 -0
- package/dist/node_modules/undici/types/client.d.ts +123 -0
- package/dist/node_modules/undici/types/connector.d.ts +36 -0
- package/dist/node_modules/undici/types/content-type.d.ts +21 -0
- package/dist/node_modules/undici/types/cookies.d.ts +30 -0
- package/dist/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/dist/node_modules/undici/types/dispatcher.d.ts +273 -0
- package/dist/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/dist/node_modules/undici/types/errors.d.ts +177 -0
- package/dist/node_modules/undici/types/eventsource.d.ts +66 -0
- package/dist/node_modules/undici/types/fetch.d.ts +231 -0
- package/dist/node_modules/undici/types/formdata.d.ts +114 -0
- package/dist/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/dist/node_modules/undici/types/global-origin.d.ts +7 -0
- package/dist/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/dist/node_modules/undici/types/handlers.d.ts +14 -0
- package/dist/node_modules/undici/types/header.d.ts +160 -0
- package/dist/node_modules/undici/types/index.d.ts +91 -0
- package/dist/node_modules/undici/types/interceptors.d.ts +80 -0
- package/dist/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/dist/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/dist/node_modules/undici/types/mock-client.d.ts +27 -0
- package/dist/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/dist/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/dist/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/dist/node_modules/undici/types/patch.d.ts +29 -0
- package/dist/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/dist/node_modules/undici/types/pool.d.ts +41 -0
- package/dist/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/dist/node_modules/undici/types/readable.d.ts +68 -0
- package/dist/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/dist/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/dist/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/dist/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/dist/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/dist/node_modules/undici/types/util.d.ts +18 -0
- package/dist/node_modules/undici/types/utility.d.ts +7 -0
- package/dist/node_modules/undici/types/webidl.d.ts +347 -0
- package/dist/node_modules/undici/types/websocket.d.ts +188 -0
- package/dist/server/collections/oidcStates.d.ts +10 -0
- package/dist/server/collections/oidcStates.js +96 -0
- package/dist/server/db-adapter.d.ts +25 -0
- package/dist/server/db-adapter.js +156 -0
- package/dist/server/service.js +11 -10
- package/package.json +2 -2
- package/dist/server/cache-adapter.d.ts +0 -33
- package/dist/server/cache-adapter.js +0 -159
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { pipeline } = require('node:stream')
|
|
5
|
+
const util = require('../core/util.js')
|
|
6
|
+
const {
|
|
7
|
+
RequestContentLengthMismatchError,
|
|
8
|
+
RequestAbortedError,
|
|
9
|
+
SocketError,
|
|
10
|
+
InformationalError,
|
|
11
|
+
InvalidArgumentError
|
|
12
|
+
} = require('../core/errors.js')
|
|
13
|
+
const {
|
|
14
|
+
kUrl,
|
|
15
|
+
kReset,
|
|
16
|
+
kClient,
|
|
17
|
+
kRunning,
|
|
18
|
+
kPending,
|
|
19
|
+
kQueue,
|
|
20
|
+
kPendingIdx,
|
|
21
|
+
kRunningIdx,
|
|
22
|
+
kError,
|
|
23
|
+
kSocket,
|
|
24
|
+
kStrictContentLength,
|
|
25
|
+
kOnError,
|
|
26
|
+
kMaxConcurrentStreams,
|
|
27
|
+
kPingInterval,
|
|
28
|
+
kHTTP2Session,
|
|
29
|
+
kHTTP2InitialWindowSize,
|
|
30
|
+
kHTTP2ConnectionWindowSize,
|
|
31
|
+
kResume,
|
|
32
|
+
kSize,
|
|
33
|
+
kHTTPContext,
|
|
34
|
+
kClosed,
|
|
35
|
+
kBodyTimeout,
|
|
36
|
+
kEnableConnectProtocol,
|
|
37
|
+
kRemoteSettings,
|
|
38
|
+
kHTTP2Stream,
|
|
39
|
+
kHTTP2SessionState
|
|
40
|
+
} = require('../core/symbols.js')
|
|
41
|
+
const { channels } = require('../core/diagnostics.js')
|
|
42
|
+
|
|
43
|
+
const kOpenStreams = Symbol('open streams')
|
|
44
|
+
|
|
45
|
+
let extractBody
|
|
46
|
+
|
|
47
|
+
/** @type {import('http2')} */
|
|
48
|
+
let http2
|
|
49
|
+
try {
|
|
50
|
+
http2 = require('node:http2')
|
|
51
|
+
} catch {
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
http2 = { constants: {} }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
constants: {
|
|
58
|
+
HTTP2_HEADER_AUTHORITY,
|
|
59
|
+
HTTP2_HEADER_METHOD,
|
|
60
|
+
HTTP2_HEADER_PATH,
|
|
61
|
+
HTTP2_HEADER_SCHEME,
|
|
62
|
+
HTTP2_HEADER_CONTENT_LENGTH,
|
|
63
|
+
HTTP2_HEADER_EXPECT,
|
|
64
|
+
HTTP2_HEADER_STATUS,
|
|
65
|
+
HTTP2_HEADER_PROTOCOL,
|
|
66
|
+
NGHTTP2_REFUSED_STREAM,
|
|
67
|
+
NGHTTP2_CANCEL
|
|
68
|
+
}
|
|
69
|
+
} = http2
|
|
70
|
+
|
|
71
|
+
function parseH2Headers (headers) {
|
|
72
|
+
const result = []
|
|
73
|
+
|
|
74
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
75
|
+
// h2 may concat the header value by array
|
|
76
|
+
// e.g. Set-Cookie
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
for (const subvalue of value) {
|
|
79
|
+
// we need to provide each header value of header name
|
|
80
|
+
// because the headers handler expect name-value pair
|
|
81
|
+
result.push(Buffer.from(name), Buffer.from(subvalue))
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
result.push(Buffer.from(name), Buffer.from(value))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function connectH2 (client, socket) {
|
|
92
|
+
client[kSocket] = socket
|
|
93
|
+
|
|
94
|
+
const http2InitialWindowSize = client[kHTTP2InitialWindowSize]
|
|
95
|
+
const http2ConnectionWindowSize = client[kHTTP2ConnectionWindowSize]
|
|
96
|
+
|
|
97
|
+
const session = http2.connect(client[kUrl], {
|
|
98
|
+
createConnection: () => socket,
|
|
99
|
+
peerMaxConcurrentStreams: client[kMaxConcurrentStreams],
|
|
100
|
+
settings: {
|
|
101
|
+
// TODO(metcoder95): add support for PUSH
|
|
102
|
+
enablePush: false,
|
|
103
|
+
...(http2InitialWindowSize != null ? { initialWindowSize: http2InitialWindowSize } : null)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
client[kSocket] = socket
|
|
108
|
+
session[kOpenStreams] = 0
|
|
109
|
+
session[kClient] = client
|
|
110
|
+
session[kSocket] = socket
|
|
111
|
+
session[kHTTP2SessionState] = {
|
|
112
|
+
ping: {
|
|
113
|
+
interval: client[kPingInterval] === 0 ? null : setInterval(onHttp2SendPing, client[kPingInterval], session).unref()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// We set it to true by default in a best-effort; however once connected to an H2 server
|
|
117
|
+
// we will check if extended CONNECT protocol is supported or not
|
|
118
|
+
// and set this value accordingly.
|
|
119
|
+
session[kEnableConnectProtocol] = false
|
|
120
|
+
// States whether or not we have received the remote settings from the server
|
|
121
|
+
session[kRemoteSettings] = false
|
|
122
|
+
|
|
123
|
+
// Apply connection-level flow control once connected (if supported).
|
|
124
|
+
if (http2ConnectionWindowSize) {
|
|
125
|
+
util.addListener(session, 'connect', applyConnectionWindowSize.bind(session, http2ConnectionWindowSize))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
util.addListener(session, 'error', onHttp2SessionError)
|
|
129
|
+
util.addListener(session, 'frameError', onHttp2FrameError)
|
|
130
|
+
util.addListener(session, 'end', onHttp2SessionEnd)
|
|
131
|
+
util.addListener(session, 'goaway', onHttp2SessionGoAway)
|
|
132
|
+
util.addListener(session, 'close', onHttp2SessionClose)
|
|
133
|
+
util.addListener(session, 'remoteSettings', onHttp2RemoteSettings)
|
|
134
|
+
// TODO (@metcoder95): implement SETTINGS support
|
|
135
|
+
// util.addListener(session, 'localSettings', onHttp2RemoteSettings)
|
|
136
|
+
|
|
137
|
+
session.unref()
|
|
138
|
+
|
|
139
|
+
client[kHTTP2Session] = session
|
|
140
|
+
socket[kHTTP2Session] = session
|
|
141
|
+
|
|
142
|
+
util.addListener(socket, 'error', onHttp2SocketError)
|
|
143
|
+
util.addListener(socket, 'end', onHttp2SocketEnd)
|
|
144
|
+
util.addListener(socket, 'close', onHttp2SocketClose)
|
|
145
|
+
|
|
146
|
+
socket[kClosed] = false
|
|
147
|
+
socket.on('close', onSocketClose)
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
version: 'h2',
|
|
151
|
+
defaultPipelining: Infinity,
|
|
152
|
+
/**
|
|
153
|
+
* @param {import('../core/request.js')} request
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
write (request) {
|
|
157
|
+
return writeH2(client, request)
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* @returns {void}
|
|
161
|
+
*/
|
|
162
|
+
resume () {
|
|
163
|
+
resumeH2(client)
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* @param {Error | null} err
|
|
167
|
+
* @param {() => void} callback
|
|
168
|
+
*/
|
|
169
|
+
destroy (err, callback) {
|
|
170
|
+
if (socket[kClosed]) {
|
|
171
|
+
queueMicrotask(callback)
|
|
172
|
+
} else {
|
|
173
|
+
socket.destroy(err).on('close', callback)
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* @type {boolean}
|
|
178
|
+
*/
|
|
179
|
+
get destroyed () {
|
|
180
|
+
return socket.destroyed
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* @param {import('../core/request.js')} request
|
|
184
|
+
* @returns {boolean}
|
|
185
|
+
*/
|
|
186
|
+
busy (request) {
|
|
187
|
+
if (request != null) {
|
|
188
|
+
if (client[kRunning] > 0) {
|
|
189
|
+
// We are already processing requests
|
|
190
|
+
|
|
191
|
+
// Non-idempotent request cannot be retried.
|
|
192
|
+
// Ensure that no other requests are inflight and
|
|
193
|
+
// could cause failure.
|
|
194
|
+
if (request.idempotent === false) return true
|
|
195
|
+
// Don't dispatch an upgrade until all preceding requests have completed.
|
|
196
|
+
// Possibly, we do not have remote settings confirmed yet.
|
|
197
|
+
if ((request.upgrade === 'websocket' || request.method === 'CONNECT') && session[kRemoteSettings] === false) return true
|
|
198
|
+
// Request with stream or iterator body can error while other requests
|
|
199
|
+
// are inflight and indirectly error those as well.
|
|
200
|
+
// Ensure this doesn't happen by waiting for inflight
|
|
201
|
+
// to complete before dispatching.
|
|
202
|
+
|
|
203
|
+
// Request with stream or iterator body cannot be retried.
|
|
204
|
+
// Ensure that no other requests are inflight and
|
|
205
|
+
// could cause failure.
|
|
206
|
+
if (util.bodyLength(request.body) !== 0 &&
|
|
207
|
+
(util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) return true
|
|
208
|
+
} else {
|
|
209
|
+
return (request.upgrade === 'websocket' || request.method === 'CONNECT') && session[kRemoteSettings] === false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resumeH2 (client) {
|
|
219
|
+
const socket = client[kSocket]
|
|
220
|
+
|
|
221
|
+
if (socket?.destroyed === false) {
|
|
222
|
+
if (client[kSize] === 0 || client[kMaxConcurrentStreams] === 0) {
|
|
223
|
+
socket.unref()
|
|
224
|
+
client[kHTTP2Session].unref()
|
|
225
|
+
} else {
|
|
226
|
+
socket.ref()
|
|
227
|
+
client[kHTTP2Session].ref()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function applyConnectionWindowSize (connectionWindowSize) {
|
|
233
|
+
try {
|
|
234
|
+
if (typeof this.setLocalWindowSize === 'function') {
|
|
235
|
+
this.setLocalWindowSize(connectionWindowSize)
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// Best-effort only.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onHttp2RemoteSettings (settings) {
|
|
243
|
+
// Fallbacks are a safe bet, remote setting will always override
|
|
244
|
+
this[kClient][kMaxConcurrentStreams] = settings.maxConcurrentStreams ?? this[kClient][kMaxConcurrentStreams]
|
|
245
|
+
/**
|
|
246
|
+
* From RFC-8441
|
|
247
|
+
* A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter
|
|
248
|
+
* with the value of 0 after previously sending a value of 1.
|
|
249
|
+
*/
|
|
250
|
+
// Note: Cannot be tested in Node, it does not supports disabling the extended CONNECT protocol once enabled
|
|
251
|
+
if (this[kRemoteSettings] === true && this[kEnableConnectProtocol] === true && settings.enableConnectProtocol === false) {
|
|
252
|
+
const err = new InformationalError('HTTP/2: Server disabled extended CONNECT protocol against RFC-8441')
|
|
253
|
+
this[kSocket][kError] = err
|
|
254
|
+
this[kClient][kOnError](err)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this[kEnableConnectProtocol] = settings.enableConnectProtocol ?? this[kEnableConnectProtocol]
|
|
259
|
+
this[kRemoteSettings] = true
|
|
260
|
+
this[kClient][kResume]()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function onHttp2SendPing (session) {
|
|
264
|
+
const state = session[kHTTP2SessionState]
|
|
265
|
+
if ((session.closed || session.destroyed) && state.ping.interval != null) {
|
|
266
|
+
clearInterval(state.ping.interval)
|
|
267
|
+
state.ping.interval = null
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// If no ping sent, do nothing
|
|
272
|
+
session.ping(onPing.bind(session))
|
|
273
|
+
|
|
274
|
+
function onPing (err, duration) {
|
|
275
|
+
const client = this[kClient]
|
|
276
|
+
const socket = this[kClient]
|
|
277
|
+
|
|
278
|
+
if (err != null) {
|
|
279
|
+
const error = new InformationalError(`HTTP/2: "PING" errored - type ${err.message}`)
|
|
280
|
+
socket[kError] = error
|
|
281
|
+
client[kOnError](error)
|
|
282
|
+
} else {
|
|
283
|
+
client.emit('ping', duration)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function onHttp2SessionError (err) {
|
|
289
|
+
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
290
|
+
|
|
291
|
+
this[kSocket][kError] = err
|
|
292
|
+
this[kClient][kOnError](err)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function onHttp2FrameError (type, code, id) {
|
|
296
|
+
if (id === 0) {
|
|
297
|
+
const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)
|
|
298
|
+
this[kSocket][kError] = err
|
|
299
|
+
this[kClient][kOnError](err)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function onHttp2SessionEnd () {
|
|
304
|
+
const err = new SocketError('other side closed', util.getSocketInfo(this[kSocket]))
|
|
305
|
+
this.destroy(err)
|
|
306
|
+
util.destroy(this[kSocket], err)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* This is the root cause of #3011
|
|
311
|
+
* We need to handle GOAWAY frames properly, and trigger the session close
|
|
312
|
+
* along with the socket right away
|
|
313
|
+
*
|
|
314
|
+
* @this {import('http2').ClientHttp2Session}
|
|
315
|
+
* @param {number} errorCode
|
|
316
|
+
*/
|
|
317
|
+
function onHttp2SessionGoAway (errorCode) {
|
|
318
|
+
// TODO(mcollina): Verify if GOAWAY implements the spec correctly:
|
|
319
|
+
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.8
|
|
320
|
+
// Specifically, we do not verify the "valid" stream id.
|
|
321
|
+
|
|
322
|
+
const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(this[kSocket]))
|
|
323
|
+
const client = this[kClient]
|
|
324
|
+
|
|
325
|
+
client[kSocket] = null
|
|
326
|
+
client[kHTTPContext] = null
|
|
327
|
+
|
|
328
|
+
// this is an HTTP2 session
|
|
329
|
+
this.close()
|
|
330
|
+
this[kHTTP2Session] = null
|
|
331
|
+
|
|
332
|
+
util.destroy(this[kSocket], err)
|
|
333
|
+
|
|
334
|
+
// Fail head of pipeline.
|
|
335
|
+
if (client[kRunningIdx] < client[kQueue].length) {
|
|
336
|
+
const request = client[kQueue][client[kRunningIdx]]
|
|
337
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
338
|
+
util.errorRequest(client, request, err)
|
|
339
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
assert(client[kRunning] === 0)
|
|
343
|
+
|
|
344
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
345
|
+
client.emit('connectionError', client[kUrl], [client], err)
|
|
346
|
+
|
|
347
|
+
client[kResume]()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function onHttp2SessionClose () {
|
|
351
|
+
const { [kClient]: client, [kHTTP2SessionState]: state } = this
|
|
352
|
+
const { [kSocket]: socket } = client
|
|
353
|
+
|
|
354
|
+
const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
|
|
355
|
+
|
|
356
|
+
client[kSocket] = null
|
|
357
|
+
client[kHTTPContext] = null
|
|
358
|
+
|
|
359
|
+
if (state.ping.interval != null) {
|
|
360
|
+
clearInterval(state.ping.interval)
|
|
361
|
+
state.ping.interval = null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (client.destroyed) {
|
|
365
|
+
assert(client[kPending] === 0)
|
|
366
|
+
|
|
367
|
+
// Fail entire queue.
|
|
368
|
+
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
369
|
+
for (let i = 0; i < requests.length; i++) {
|
|
370
|
+
const request = requests[i]
|
|
371
|
+
util.errorRequest(client, request, err)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function onHttp2SocketClose () {
|
|
377
|
+
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
378
|
+
|
|
379
|
+
const client = this[kHTTP2Session][kClient]
|
|
380
|
+
|
|
381
|
+
client[kSocket] = null
|
|
382
|
+
client[kHTTPContext] = null
|
|
383
|
+
|
|
384
|
+
if (this[kHTTP2Session] !== null) {
|
|
385
|
+
this[kHTTP2Session].destroy(err)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
389
|
+
|
|
390
|
+
assert(client[kRunning] === 0)
|
|
391
|
+
|
|
392
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
393
|
+
|
|
394
|
+
client[kResume]()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function onHttp2SocketError (err) {
|
|
398
|
+
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
399
|
+
|
|
400
|
+
this[kError] = err
|
|
401
|
+
|
|
402
|
+
this[kClient][kOnError](err)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function onHttp2SocketEnd () {
|
|
406
|
+
util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function onSocketClose () {
|
|
410
|
+
this[kClosed] = true
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
414
|
+
function shouldSendContentLength (method) {
|
|
415
|
+
return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT'
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function writeH2 (client, request) {
|
|
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
|
+
|
|
429
|
+
const headers = {}
|
|
430
|
+
for (let n = 0; n < reqHeaders.length; n += 2) {
|
|
431
|
+
const key = reqHeaders[n + 0]
|
|
432
|
+
const val = reqHeaders[n + 1]
|
|
433
|
+
|
|
434
|
+
if (key === 'cookie') {
|
|
435
|
+
if (headers[key] != null) {
|
|
436
|
+
headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]
|
|
437
|
+
} else {
|
|
438
|
+
headers[key] = val
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
continue
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (Array.isArray(val)) {
|
|
445
|
+
for (let i = 0; i < val.length; i++) {
|
|
446
|
+
if (headers[key]) {
|
|
447
|
+
headers[key] += `, ${val[i]}`
|
|
448
|
+
} else {
|
|
449
|
+
headers[key] = val[i]
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} else if (headers[key]) {
|
|
453
|
+
headers[key] += `, ${val}`
|
|
454
|
+
} else {
|
|
455
|
+
headers[key] = val
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** @type {import('node:http2').ClientHttp2Stream} */
|
|
460
|
+
let stream = null
|
|
461
|
+
|
|
462
|
+
const { hostname, port } = client[kUrl]
|
|
463
|
+
|
|
464
|
+
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
|
465
|
+
headers[HTTP2_HEADER_METHOD] = method
|
|
466
|
+
|
|
467
|
+
const abort = (err) => {
|
|
468
|
+
if (request.aborted || request.completed) {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
err = err || new RequestAbortedError()
|
|
473
|
+
|
|
474
|
+
util.errorRequest(client, request, err)
|
|
475
|
+
|
|
476
|
+
if (stream != null) {
|
|
477
|
+
// Some chunks might still come after abort,
|
|
478
|
+
// let's ignore them
|
|
479
|
+
stream.removeAllListeners('data')
|
|
480
|
+
|
|
481
|
+
// On Abort, we close the stream to send RST_STREAM frame
|
|
482
|
+
stream.close()
|
|
483
|
+
|
|
484
|
+
// We move the running index to the next request
|
|
485
|
+
client[kOnError](err)
|
|
486
|
+
client[kResume]()
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// We do not destroy the socket as we can continue using the session
|
|
490
|
+
// the stream gets destroyed and the session remains to create new streams
|
|
491
|
+
util.destroy(body, err)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
// We are already connected, streams are pending.
|
|
496
|
+
// We can call on connect, and wait for abort
|
|
497
|
+
request.onConnect(abort)
|
|
498
|
+
} catch (err) {
|
|
499
|
+
util.errorRequest(client, request, err)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (request.aborted) {
|
|
503
|
+
return false
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (upgrade || method === 'CONNECT') {
|
|
507
|
+
session.ref()
|
|
508
|
+
|
|
509
|
+
if (upgrade === 'websocket') {
|
|
510
|
+
// We cannot upgrade to websocket if extended CONNECT protocol is not supported
|
|
511
|
+
if (session[kEnableConnectProtocol] === false) {
|
|
512
|
+
util.errorRequest(client, request, new InformationalError('HTTP/2: Extended CONNECT protocol not supported by server'))
|
|
513
|
+
session.unref()
|
|
514
|
+
return false
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// We force the method to CONNECT
|
|
518
|
+
// as per RFC-8441
|
|
519
|
+
// https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
|
520
|
+
headers[HTTP2_HEADER_METHOD] = 'CONNECT'
|
|
521
|
+
headers[HTTP2_HEADER_PROTOCOL] = 'websocket'
|
|
522
|
+
// :path and :scheme headers must be omitted when sending CONNECT but set if extended-CONNECT
|
|
523
|
+
headers[HTTP2_HEADER_PATH] = path
|
|
524
|
+
|
|
525
|
+
if (protocol === 'ws:' || protocol === 'wss:') {
|
|
526
|
+
headers[HTTP2_HEADER_SCHEME] = protocol === 'ws:' ? 'http' : 'https'
|
|
527
|
+
} else {
|
|
528
|
+
headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
stream = session.request(headers, { endStream: false, signal })
|
|
532
|
+
stream[kHTTP2Stream] = true
|
|
533
|
+
|
|
534
|
+
stream.once('response', (headers, _flags) => {
|
|
535
|
+
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
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)
|
|
558
|
+
return true
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// TODO: consolidate once we support CONNECT properly
|
|
562
|
+
// NOTE: We are already connected, streams are pending, first request
|
|
563
|
+
// will create a new stream. We trigger a request to create the stream and wait until
|
|
564
|
+
// `ready` event is triggered
|
|
565
|
+
// We disabled endStream to allow the user to write to the stream
|
|
566
|
+
stream = session.request(headers, { endStream: false, signal })
|
|
567
|
+
stream[kHTTP2Stream] = true
|
|
568
|
+
stream.on('response', headers => {
|
|
569
|
+
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
|
|
570
|
+
|
|
571
|
+
request.onUpgrade(statusCode, parseH2Headers(realHeaders), stream)
|
|
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)
|
|
580
|
+
|
|
581
|
+
return true
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// https://tools.ietf.org/html/rfc7540#section-8.3
|
|
585
|
+
// :path and :scheme headers must be omitted when sending CONNECT
|
|
586
|
+
headers[HTTP2_HEADER_PATH] = path
|
|
587
|
+
headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
|
|
588
|
+
|
|
589
|
+
// https://tools.ietf.org/html/rfc7231#section-4.3.1
|
|
590
|
+
// https://tools.ietf.org/html/rfc7231#section-4.3.2
|
|
591
|
+
// https://tools.ietf.org/html/rfc7231#section-4.3.5
|
|
592
|
+
|
|
593
|
+
// Sending a payload body on a request that does not
|
|
594
|
+
// expect it can cause undefined behavior on some
|
|
595
|
+
// servers and corrupt connection state. Do not
|
|
596
|
+
// re-use the connection for further requests.
|
|
597
|
+
|
|
598
|
+
const expectsPayload = (
|
|
599
|
+
method === 'PUT' ||
|
|
600
|
+
method === 'POST' ||
|
|
601
|
+
method === 'PATCH'
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if (body && typeof body.read === 'function') {
|
|
605
|
+
// Try to read EOF in order to get length.
|
|
606
|
+
body.read(0)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let contentLength = util.bodyLength(body)
|
|
610
|
+
|
|
611
|
+
if (util.isFormDataLike(body)) {
|
|
612
|
+
extractBody ??= require('../web/fetch/body.js').extractBody
|
|
613
|
+
|
|
614
|
+
const [bodyStream, contentType] = extractBody(body)
|
|
615
|
+
headers['content-type'] = contentType
|
|
616
|
+
|
|
617
|
+
body = bodyStream.stream
|
|
618
|
+
contentLength = bodyStream.length
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (contentLength == null) {
|
|
622
|
+
contentLength = request.contentLength
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!expectsPayload) {
|
|
626
|
+
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
627
|
+
// A user agent SHOULD NOT send a Content-Length header field when
|
|
628
|
+
// the request message does not contain a payload body and the method
|
|
629
|
+
// semantics do not anticipate such a body.
|
|
630
|
+
// And for methods that don't expect a payload, omit Content-Length.
|
|
631
|
+
contentLength = null
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// https://github.com/nodejs/undici/issues/2046
|
|
635
|
+
// A user agent may send a Content-Length header with 0 value, this should be allowed.
|
|
636
|
+
if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) {
|
|
637
|
+
if (client[kStrictContentLength]) {
|
|
638
|
+
util.errorRequest(client, request, new RequestContentLengthMismatchError())
|
|
639
|
+
return false
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
process.emitWarning(new RequestContentLengthMismatchError())
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (contentLength != null) {
|
|
646
|
+
assert(body || contentLength === 0, 'no body must not have content length')
|
|
647
|
+
headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}`
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
session.ref()
|
|
651
|
+
|
|
652
|
+
if (channels.sendHeaders.hasSubscribers) {
|
|
653
|
+
let header = ''
|
|
654
|
+
for (const key in headers) {
|
|
655
|
+
header += `${key}: ${headers[key]}\r\n`
|
|
656
|
+
}
|
|
657
|
+
channels.sendHeaders.publish({ request, headers: header, socket: session[kSocket] })
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// TODO(metcoder95): add support for sending trailers
|
|
661
|
+
const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null
|
|
662
|
+
if (expectContinue) {
|
|
663
|
+
headers[HTTP2_HEADER_EXPECT] = '100-continue'
|
|
664
|
+
stream = session.request(headers, { endStream: shouldEndStream, signal })
|
|
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
|
|
674
|
+
|
|
675
|
+
writeBodyH2()
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Increment counter as we have new streams open
|
|
679
|
+
++session[kOpenStreams]
|
|
680
|
+
stream.setTimeout(requestTimeout)
|
|
681
|
+
|
|
682
|
+
// Track whether we received a response (headers)
|
|
683
|
+
let responseReceived = false
|
|
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
|
+
}
|
|
699
|
+
|
|
700
|
+
if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) {
|
|
701
|
+
stream.pause()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
stream.on('data', (chunk) => {
|
|
705
|
+
if (request.aborted || request.completed) {
|
|
706
|
+
return
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (request.onData(chunk) === false) {
|
|
710
|
+
stream.pause()
|
|
711
|
+
}
|
|
712
|
+
})
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
stream.once('end', () => {
|
|
716
|
+
stream.removeAllListeners('data')
|
|
717
|
+
// If we received a response, this is a normal completion
|
|
718
|
+
if (responseReceived) {
|
|
719
|
+
if (!request.aborted && !request.completed) {
|
|
720
|
+
request.onComplete({})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
724
|
+
client[kResume]()
|
|
725
|
+
} else {
|
|
726
|
+
// Stream ended without receiving a response - this is an error
|
|
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
|
+
})
|
|
734
|
+
|
|
735
|
+
stream.once('close', () => {
|
|
736
|
+
stream.removeAllListeners('data')
|
|
737
|
+
session[kOpenStreams] -= 1
|
|
738
|
+
if (session[kOpenStreams] === 0) {
|
|
739
|
+
session.unref()
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
stream.once('error', function (err) {
|
|
744
|
+
stream.removeAllListeners('data')
|
|
745
|
+
abort(err)
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
stream.once('frameError', (type, code) => {
|
|
749
|
+
stream.removeAllListeners('data')
|
|
750
|
+
abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`))
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
stream.on('aborted', () => {
|
|
754
|
+
stream.removeAllListeners('data')
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
stream.on('timeout', () => {
|
|
758
|
+
const err = new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`)
|
|
759
|
+
stream.removeAllListeners('data')
|
|
760
|
+
session[kOpenStreams] -= 1
|
|
761
|
+
|
|
762
|
+
if (session[kOpenStreams] === 0) {
|
|
763
|
+
session.unref()
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
abort(err)
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
stream.once('trailers', trailers => {
|
|
770
|
+
if (request.aborted || request.completed) {
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
stream.removeAllListeners('data')
|
|
775
|
+
request.onComplete(trailers)
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
return true
|
|
779
|
+
|
|
780
|
+
function writeBodyH2 () {
|
|
781
|
+
if (!body || contentLength === 0) {
|
|
782
|
+
writeBuffer(
|
|
783
|
+
abort,
|
|
784
|
+
stream,
|
|
785
|
+
null,
|
|
786
|
+
client,
|
|
787
|
+
request,
|
|
788
|
+
client[kSocket],
|
|
789
|
+
contentLength,
|
|
790
|
+
expectsPayload
|
|
791
|
+
)
|
|
792
|
+
} else if (util.isBuffer(body)) {
|
|
793
|
+
writeBuffer(
|
|
794
|
+
abort,
|
|
795
|
+
stream,
|
|
796
|
+
body,
|
|
797
|
+
client,
|
|
798
|
+
request,
|
|
799
|
+
client[kSocket],
|
|
800
|
+
contentLength,
|
|
801
|
+
expectsPayload
|
|
802
|
+
)
|
|
803
|
+
} else if (util.isBlobLike(body)) {
|
|
804
|
+
if (typeof body.stream === 'function') {
|
|
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(
|
|
840
|
+
abort,
|
|
841
|
+
stream,
|
|
842
|
+
body,
|
|
843
|
+
client,
|
|
844
|
+
request,
|
|
845
|
+
client[kSocket],
|
|
846
|
+
contentLength,
|
|
847
|
+
expectsPayload
|
|
848
|
+
)
|
|
849
|
+
} else {
|
|
850
|
+
assert(false)
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function writeBuffer (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
856
|
+
try {
|
|
857
|
+
if (body != null && util.isBuffer(body)) {
|
|
858
|
+
assert(contentLength === body.byteLength, 'buffer body must have content length')
|
|
859
|
+
h2stream.cork()
|
|
860
|
+
h2stream.write(body)
|
|
861
|
+
h2stream.uncork()
|
|
862
|
+
h2stream.end()
|
|
863
|
+
|
|
864
|
+
request.onBodySent(body)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (!expectsPayload) {
|
|
868
|
+
socket[kReset] = true
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
request.onRequestSent()
|
|
872
|
+
client[kResume]()
|
|
873
|
+
} catch (error) {
|
|
874
|
+
abort(error)
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function writeStream (abort, socket, expectsPayload, h2stream, body, client, request, contentLength) {
|
|
879
|
+
assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined')
|
|
880
|
+
|
|
881
|
+
// For HTTP/2, is enough to pipe the stream
|
|
882
|
+
const pipe = pipeline(
|
|
883
|
+
body,
|
|
884
|
+
h2stream,
|
|
885
|
+
(err) => {
|
|
886
|
+
if (err) {
|
|
887
|
+
util.destroy(pipe, err)
|
|
888
|
+
abort(err)
|
|
889
|
+
} else {
|
|
890
|
+
util.removeAllListeners(pipe)
|
|
891
|
+
request.onRequestSent()
|
|
892
|
+
|
|
893
|
+
if (!expectsPayload) {
|
|
894
|
+
socket[kReset] = true
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
client[kResume]()
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
util.addListener(pipe, 'data', onPipeData)
|
|
903
|
+
|
|
904
|
+
function onPipeData (chunk) {
|
|
905
|
+
request.onBodySent(chunk)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
910
|
+
assert(contentLength === body.size, 'blob body must have content length')
|
|
911
|
+
|
|
912
|
+
try {
|
|
913
|
+
if (contentLength != null && contentLength !== body.size) {
|
|
914
|
+
throw new RequestContentLengthMismatchError()
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const buffer = Buffer.from(await body.arrayBuffer())
|
|
918
|
+
|
|
919
|
+
h2stream.cork()
|
|
920
|
+
h2stream.write(buffer)
|
|
921
|
+
h2stream.uncork()
|
|
922
|
+
h2stream.end()
|
|
923
|
+
|
|
924
|
+
request.onBodySent(buffer)
|
|
925
|
+
request.onRequestSent()
|
|
926
|
+
|
|
927
|
+
if (!expectsPayload) {
|
|
928
|
+
socket[kReset] = true
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
client[kResume]()
|
|
932
|
+
} catch (err) {
|
|
933
|
+
abort(err)
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function writeIterable (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) {
|
|
938
|
+
assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined')
|
|
939
|
+
|
|
940
|
+
let callback = null
|
|
941
|
+
function onDrain () {
|
|
942
|
+
if (callback) {
|
|
943
|
+
const cb = callback
|
|
944
|
+
callback = null
|
|
945
|
+
cb()
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const waitForDrain = () => new Promise((resolve, reject) => {
|
|
950
|
+
assert(callback === null)
|
|
951
|
+
|
|
952
|
+
if (socket[kError]) {
|
|
953
|
+
reject(socket[kError])
|
|
954
|
+
} else {
|
|
955
|
+
callback = resolve
|
|
956
|
+
}
|
|
957
|
+
})
|
|
958
|
+
|
|
959
|
+
h2stream
|
|
960
|
+
.on('close', onDrain)
|
|
961
|
+
.on('drain', onDrain)
|
|
962
|
+
|
|
963
|
+
try {
|
|
964
|
+
// It's up to the user to somehow abort the async iterable.
|
|
965
|
+
for await (const chunk of body) {
|
|
966
|
+
if (socket[kError]) {
|
|
967
|
+
throw socket[kError]
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const res = h2stream.write(chunk)
|
|
971
|
+
request.onBodySent(chunk)
|
|
972
|
+
if (!res) {
|
|
973
|
+
await waitForDrain()
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
h2stream.end()
|
|
978
|
+
|
|
979
|
+
request.onRequestSent()
|
|
980
|
+
|
|
981
|
+
if (!expectsPayload) {
|
|
982
|
+
socket[kReset] = true
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
client[kResume]()
|
|
986
|
+
} catch (err) {
|
|
987
|
+
abort(err)
|
|
988
|
+
} finally {
|
|
989
|
+
h2stream
|
|
990
|
+
.off('close', onDrain)
|
|
991
|
+
.off('drain', onDrain)
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
module.exports = connectH2
|