undici 7.0.0-alpha.2 → 7.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/docs/api/CacheStore.md +116 -0
- package/docs/docs/api/Dispatcher.md +10 -0
- package/index.js +6 -1
- package/lib/api/api-request.js +1 -1
- package/lib/api/readable.js +6 -6
- package/lib/cache/memory-cache-store.js +417 -0
- package/lib/core/constants.js +24 -1
- package/lib/core/util.js +41 -2
- package/lib/dispatcher/client-h1.js +100 -87
- package/lib/dispatcher/client-h2.js +127 -75
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/handler/cache-handler.js +359 -0
- package/lib/handler/cache-revalidation-handler.js +119 -0
- package/lib/interceptor/cache.js +171 -0
- package/lib/util/cache.js +224 -0
- package/lib/web/cache/cache.js +1 -0
- package/lib/web/cache/cachestorage.js +2 -0
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/constants.js +12 -5
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata.js +3 -1
- package/lib/web/fetch/headers.js +2 -0
- package/lib/web/fetch/request.js +3 -1
- package/lib/web/fetch/response.js +3 -1
- package/lib/web/fetch/util.js +171 -47
- package/lib/web/fetch/webidl.js +16 -12
- package/lib/web/websocket/constants.js +67 -6
- package/lib/web/websocket/events.js +4 -0
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/websocket.js +2 -0
- package/package.json +7 -3
- package/types/cache-interceptor.d.ts +97 -0
- package/types/fetch.d.ts +9 -8
- package/types/index.d.ts +3 -0
- package/types/interceptors.d.ts +4 -0
- package/types/webidl.d.ts +7 -1
package/lib/core/util.js
CHANGED
|
@@ -10,7 +10,7 @@ const nodeUtil = require('node:util')
|
|
|
10
10
|
const { stringify } = require('node:querystring')
|
|
11
11
|
const { EventEmitter: EE } = require('node:events')
|
|
12
12
|
const { InvalidArgumentError } = require('./errors')
|
|
13
|
-
const { headerNameLowerCasedRecord } = require('./constants')
|
|
13
|
+
const { headerNameLowerCasedRecord, getHeaderNameAsBuffer } = require('./constants')
|
|
14
14
|
const { tree } = require('./tree')
|
|
15
15
|
|
|
16
16
|
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
|
@@ -436,6 +436,44 @@ function parseHeaders (headers, obj) {
|
|
|
436
436
|
return obj
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
/**
|
|
440
|
+
* @param {Record<string, string | string[]>} headers
|
|
441
|
+
* @returns {(Buffer | Buffer[])[]}
|
|
442
|
+
*/
|
|
443
|
+
function encodeHeaders (headers) {
|
|
444
|
+
const headerNames = Object.keys(headers)
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @type {Buffer[]|Buffer[][]}
|
|
448
|
+
*/
|
|
449
|
+
const rawHeaders = new Array(headerNames.length * 2)
|
|
450
|
+
|
|
451
|
+
let rawHeadersIndex = 0
|
|
452
|
+
for (const header of headerNames) {
|
|
453
|
+
let rawValue
|
|
454
|
+
const value = headers[header]
|
|
455
|
+
if (Array.isArray(value)) {
|
|
456
|
+
rawValue = new Array(value.length)
|
|
457
|
+
|
|
458
|
+
for (let i = 0; i < value.length; i++) {
|
|
459
|
+
rawValue[i] = Buffer.from(value[i])
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
rawValue = Buffer.from(value)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const headerBuffer = getHeaderNameAsBuffer(header)
|
|
466
|
+
|
|
467
|
+
rawHeaders[rawHeadersIndex] = headerBuffer
|
|
468
|
+
rawHeadersIndex++
|
|
469
|
+
|
|
470
|
+
rawHeaders[rawHeadersIndex] = rawValue
|
|
471
|
+
rawHeadersIndex++
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return rawHeaders
|
|
475
|
+
}
|
|
476
|
+
|
|
439
477
|
/**
|
|
440
478
|
* @param {Buffer[]} headers
|
|
441
479
|
* @returns {string[]}
|
|
@@ -864,6 +902,7 @@ module.exports = {
|
|
|
864
902
|
errorRequest,
|
|
865
903
|
parseRawHeaders,
|
|
866
904
|
parseHeaders,
|
|
905
|
+
encodeHeaders,
|
|
867
906
|
parseKeepAliveTimeout,
|
|
868
907
|
destroy,
|
|
869
908
|
bodyLength,
|
|
@@ -885,6 +924,6 @@ module.exports = {
|
|
|
885
924
|
isHttpOrHttpsPrefixed,
|
|
886
925
|
nodeMajor,
|
|
887
926
|
nodeMinor,
|
|
888
|
-
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
|
|
927
|
+
safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
|
|
889
928
|
wrapRequestBody
|
|
890
929
|
}
|
|
@@ -49,13 +49,13 @@ const {
|
|
|
49
49
|
kMaxResponseSize,
|
|
50
50
|
kOnError,
|
|
51
51
|
kResume,
|
|
52
|
-
kHTTPContext
|
|
52
|
+
kHTTPContext,
|
|
53
|
+
kClosed
|
|
53
54
|
} = require('../core/symbols.js')
|
|
54
55
|
|
|
55
56
|
const constants = require('../llhttp/constants.js')
|
|
56
57
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
57
58
|
const FastBuffer = Buffer[Symbol.species]
|
|
58
|
-
const addListener = util.addListener
|
|
59
59
|
const removeAllListeners = util.removeAllListeners
|
|
60
60
|
|
|
61
61
|
let extractBody
|
|
@@ -779,87 +779,13 @@ async function connectH1 (client, socket) {
|
|
|
779
779
|
socket[kBlocking] = false
|
|
780
780
|
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
|
781
781
|
|
|
782
|
-
addListener(socket, 'error',
|
|
783
|
-
|
|
782
|
+
util.addListener(socket, 'error', onHttpSocketError)
|
|
783
|
+
util.addListener(socket, 'readable', onHttpSocketReadable)
|
|
784
|
+
util.addListener(socket, 'end', onHttpSocketEnd)
|
|
785
|
+
util.addListener(socket, 'close', onHttpSocketClose)
|
|
784
786
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
|
788
|
-
// to the user.
|
|
789
|
-
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
|
790
|
-
// We treat all incoming data so for as a valid response.
|
|
791
|
-
parser.onMessageComplete()
|
|
792
|
-
return
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
this[kError] = err
|
|
796
|
-
|
|
797
|
-
this[kClient][kOnError](err)
|
|
798
|
-
})
|
|
799
|
-
addListener(socket, 'readable', function () {
|
|
800
|
-
this[kParser]?.readMore()
|
|
801
|
-
})
|
|
802
|
-
addListener(socket, 'end', function () {
|
|
803
|
-
const parser = this[kParser]
|
|
804
|
-
|
|
805
|
-
if (parser.statusCode && !parser.shouldKeepAlive) {
|
|
806
|
-
// We treat all incoming data so far as a valid response.
|
|
807
|
-
parser.onMessageComplete()
|
|
808
|
-
return
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
|
|
812
|
-
})
|
|
813
|
-
addListener(socket, 'close', function () {
|
|
814
|
-
const parser = this[kParser]
|
|
815
|
-
|
|
816
|
-
if (parser) {
|
|
817
|
-
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
|
818
|
-
// We treat all incoming data so far as a valid response.
|
|
819
|
-
parser.onMessageComplete()
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
this[kParser].destroy()
|
|
823
|
-
this[kParser] = null
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
827
|
-
|
|
828
|
-
const client = this[kClient]
|
|
829
|
-
|
|
830
|
-
client[kSocket] = null
|
|
831
|
-
client[kHTTPContext] = null // TODO (fix): This is hacky...
|
|
832
|
-
|
|
833
|
-
if (client.destroyed) {
|
|
834
|
-
assert(client[kPending] === 0)
|
|
835
|
-
|
|
836
|
-
// Fail entire queue.
|
|
837
|
-
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
838
|
-
for (let i = 0; i < requests.length; i++) {
|
|
839
|
-
const request = requests[i]
|
|
840
|
-
util.errorRequest(client, request, err)
|
|
841
|
-
}
|
|
842
|
-
} else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') {
|
|
843
|
-
// Fail head of pipeline.
|
|
844
|
-
const request = client[kQueue][client[kRunningIdx]]
|
|
845
|
-
client[kQueue][client[kRunningIdx]++] = null
|
|
846
|
-
|
|
847
|
-
util.errorRequest(client, request, err)
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
851
|
-
|
|
852
|
-
assert(client[kRunning] === 0)
|
|
853
|
-
|
|
854
|
-
client.emit('disconnect', client[kUrl], [client], err)
|
|
855
|
-
|
|
856
|
-
client[kResume]()
|
|
857
|
-
})
|
|
858
|
-
|
|
859
|
-
let closed = false
|
|
860
|
-
socket.on('close', () => {
|
|
861
|
-
closed = true
|
|
862
|
-
})
|
|
787
|
+
socket[kClosed] = false
|
|
788
|
+
socket.on('close', onSocketClose)
|
|
863
789
|
|
|
864
790
|
return {
|
|
865
791
|
version: 'h1',
|
|
@@ -875,7 +801,7 @@ async function connectH1 (client, socket) {
|
|
|
875
801
|
* @param {() => void} callback
|
|
876
802
|
*/
|
|
877
803
|
destroy (err, callback) {
|
|
878
|
-
if (
|
|
804
|
+
if (socket[kClosed]) {
|
|
879
805
|
queueMicrotask(callback)
|
|
880
806
|
} else {
|
|
881
807
|
socket.on('close', callback)
|
|
@@ -931,6 +857,90 @@ async function connectH1 (client, socket) {
|
|
|
931
857
|
}
|
|
932
858
|
}
|
|
933
859
|
|
|
860
|
+
function onHttpSocketError (err) {
|
|
861
|
+
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
862
|
+
|
|
863
|
+
const parser = this[kParser]
|
|
864
|
+
|
|
865
|
+
// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
|
|
866
|
+
// to the user.
|
|
867
|
+
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
|
|
868
|
+
// We treat all incoming data so for as a valid response.
|
|
869
|
+
parser.onMessageComplete()
|
|
870
|
+
return
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
this[kError] = err
|
|
874
|
+
|
|
875
|
+
this[kClient][kOnError](err)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function onHttpSocketReadable () {
|
|
879
|
+
this[kParser]?.readMore()
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function onHttpSocketEnd () {
|
|
883
|
+
const parser = this[kParser]
|
|
884
|
+
|
|
885
|
+
if (parser.statusCode && !parser.shouldKeepAlive) {
|
|
886
|
+
// We treat all incoming data so far as a valid response.
|
|
887
|
+
parser.onMessageComplete()
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function onHttpSocketClose () {
|
|
895
|
+
const parser = this[kParser]
|
|
896
|
+
|
|
897
|
+
if (parser) {
|
|
898
|
+
if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) {
|
|
899
|
+
// We treat all incoming data so far as a valid response.
|
|
900
|
+
parser.onMessageComplete()
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
this[kParser].destroy()
|
|
904
|
+
this[kParser] = null
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
908
|
+
|
|
909
|
+
const client = this[kClient]
|
|
910
|
+
|
|
911
|
+
client[kSocket] = null
|
|
912
|
+
client[kHTTPContext] = null // TODO (fix): This is hacky...
|
|
913
|
+
|
|
914
|
+
if (client.destroyed) {
|
|
915
|
+
assert(client[kPending] === 0)
|
|
916
|
+
|
|
917
|
+
// Fail entire queue.
|
|
918
|
+
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
919
|
+
for (let i = 0; i < requests.length; i++) {
|
|
920
|
+
const request = requests[i]
|
|
921
|
+
util.errorRequest(client, request, err)
|
|
922
|
+
}
|
|
923
|
+
} else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') {
|
|
924
|
+
// Fail head of pipeline.
|
|
925
|
+
const request = client[kQueue][client[kRunningIdx]]
|
|
926
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
927
|
+
|
|
928
|
+
util.errorRequest(client, request, err)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
932
|
+
|
|
933
|
+
assert(client[kRunning] === 0)
|
|
934
|
+
|
|
935
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
936
|
+
|
|
937
|
+
client[kResume]()
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function onSocketClose () {
|
|
941
|
+
this[kClosed] = true
|
|
942
|
+
}
|
|
943
|
+
|
|
934
944
|
/**
|
|
935
945
|
* @param {import('./client.js')} client
|
|
936
946
|
*/
|
|
@@ -991,7 +1001,10 @@ function writeH1 (client, request) {
|
|
|
991
1001
|
const expectsPayload = (
|
|
992
1002
|
method === 'PUT' ||
|
|
993
1003
|
method === 'POST' ||
|
|
994
|
-
method === 'PATCH'
|
|
1004
|
+
method === 'PATCH' ||
|
|
1005
|
+
method === 'QUERY' ||
|
|
1006
|
+
method === 'PROPFIND' ||
|
|
1007
|
+
method === 'PROPPATCH'
|
|
995
1008
|
)
|
|
996
1009
|
|
|
997
1010
|
if (util.isFormDataLike(body)) {
|
|
@@ -1319,7 +1332,7 @@ function writeBuffer (abort, body, client, request, socket, contentLength, heade
|
|
|
1319
1332
|
socket.uncork()
|
|
1320
1333
|
request.onBodySent(body)
|
|
1321
1334
|
|
|
1322
|
-
if (!expectsPayload) {
|
|
1335
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1323
1336
|
socket[kReset] = true
|
|
1324
1337
|
}
|
|
1325
1338
|
}
|
|
@@ -1360,7 +1373,7 @@ async function writeBlob (abort, body, client, request, socket, contentLength, h
|
|
|
1360
1373
|
request.onBodySent(buffer)
|
|
1361
1374
|
request.onRequestSent()
|
|
1362
1375
|
|
|
1363
|
-
if (!expectsPayload) {
|
|
1376
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1364
1377
|
socket[kReset] = true
|
|
1365
1378
|
}
|
|
1366
1379
|
|
|
@@ -1487,7 +1500,7 @@ class AsyncWriter {
|
|
|
1487
1500
|
socket.cork()
|
|
1488
1501
|
|
|
1489
1502
|
if (bytesWritten === 0) {
|
|
1490
|
-
if (!expectsPayload) {
|
|
1503
|
+
if (!expectsPayload && request.reset !== false) {
|
|
1491
1504
|
socket[kReset] = true
|
|
1492
1505
|
}
|
|
1493
1506
|
|
|
@@ -24,7 +24,10 @@ const {
|
|
|
24
24
|
kOnError,
|
|
25
25
|
kMaxConcurrentStreams,
|
|
26
26
|
kHTTP2Session,
|
|
27
|
-
kResume
|
|
27
|
+
kResume,
|
|
28
|
+
kSize,
|
|
29
|
+
kHTTPContext,
|
|
30
|
+
kClosed
|
|
28
31
|
} = require('../core/symbols.js')
|
|
29
32
|
|
|
30
33
|
const kOpenStreams = Symbol('open streams')
|
|
@@ -91,83 +94,37 @@ async function connectH2 (client, socket) {
|
|
|
91
94
|
session[kOpenStreams] = 0
|
|
92
95
|
session[kClient] = client
|
|
93
96
|
session[kSocket] = socket
|
|
97
|
+
session[kHTTP2Session] = null
|
|
94
98
|
|
|
95
99
|
util.addListener(session, 'error', onHttp2SessionError)
|
|
96
100
|
util.addListener(session, 'frameError', onHttp2FrameError)
|
|
97
101
|
util.addListener(session, 'end', onHttp2SessionEnd)
|
|
98
|
-
util.addListener(session, 'goaway',
|
|
99
|
-
util.addListener(session, 'close',
|
|
100
|
-
const { [kClient]: client } = this
|
|
101
|
-
const { [kSocket]: socket } = client
|
|
102
|
-
|
|
103
|
-
const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
|
|
104
|
-
|
|
105
|
-
client[kHTTP2Session] = null
|
|
106
|
-
|
|
107
|
-
if (client.destroyed) {
|
|
108
|
-
assert(client[kPending] === 0)
|
|
109
|
-
|
|
110
|
-
// Fail entire queue.
|
|
111
|
-
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
112
|
-
for (let i = 0; i < requests.length; i++) {
|
|
113
|
-
const request = requests[i]
|
|
114
|
-
util.errorRequest(client, request, err)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
})
|
|
102
|
+
util.addListener(session, 'goaway', onHttp2SessionGoAway)
|
|
103
|
+
util.addListener(session, 'close', onHttp2SessionClose)
|
|
118
104
|
|
|
119
105
|
session.unref()
|
|
120
106
|
|
|
121
107
|
client[kHTTP2Session] = session
|
|
122
108
|
socket[kHTTP2Session] = session
|
|
123
109
|
|
|
124
|
-
util.addListener(socket, 'error',
|
|
125
|
-
|
|
110
|
+
util.addListener(socket, 'error', onHttp2SocketError)
|
|
111
|
+
util.addListener(socket, 'end', onHttp2SocketEnd)
|
|
112
|
+
util.addListener(socket, 'close', onHttp2SocketClose)
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this[kClient][kOnError](err)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
util.addListener(socket, 'end', function () {
|
|
133
|
-
util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
util.addListener(socket, 'close', function () {
|
|
137
|
-
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
138
|
-
|
|
139
|
-
client[kSocket] = null
|
|
140
|
-
|
|
141
|
-
if (this[kHTTP2Session] != null) {
|
|
142
|
-
this[kHTTP2Session].destroy(err)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
client[kPendingIdx] = client[kRunningIdx]
|
|
146
|
-
|
|
147
|
-
assert(client[kRunning] === 0)
|
|
148
|
-
|
|
149
|
-
client.emit('disconnect', client[kUrl], [client], err)
|
|
150
|
-
|
|
151
|
-
client[kResume]()
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
let closed = false
|
|
155
|
-
socket.on('close', () => {
|
|
156
|
-
closed = true
|
|
157
|
-
})
|
|
114
|
+
socket[kClosed] = false
|
|
115
|
+
socket.on('close', onSocketClose)
|
|
158
116
|
|
|
159
117
|
return {
|
|
160
118
|
version: 'h2',
|
|
161
119
|
defaultPipelining: Infinity,
|
|
162
|
-
write (
|
|
163
|
-
|
|
164
|
-
writeH2(client, ...args)
|
|
120
|
+
write (request) {
|
|
121
|
+
return writeH2(client, request)
|
|
165
122
|
},
|
|
166
123
|
resume () {
|
|
167
|
-
|
|
124
|
+
resumeH2(client)
|
|
168
125
|
},
|
|
169
126
|
destroy (err, callback) {
|
|
170
|
-
if (
|
|
127
|
+
if (socket[kClosed]) {
|
|
171
128
|
queueMicrotask(callback)
|
|
172
129
|
} else {
|
|
173
130
|
// Destroying the socket will trigger the session close
|
|
@@ -183,6 +140,20 @@ async function connectH2 (client, socket) {
|
|
|
183
140
|
}
|
|
184
141
|
}
|
|
185
142
|
|
|
143
|
+
function resumeH2 (client) {
|
|
144
|
+
const socket = client[kSocket]
|
|
145
|
+
|
|
146
|
+
if (socket?.destroyed === false) {
|
|
147
|
+
if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) {
|
|
148
|
+
socket.unref()
|
|
149
|
+
client[kHTTP2Session].unref()
|
|
150
|
+
} else {
|
|
151
|
+
socket.ref()
|
|
152
|
+
client[kHTTP2Session].ref()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
186
157
|
function onHttp2SessionError (err) {
|
|
187
158
|
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
188
159
|
|
|
@@ -208,19 +179,93 @@ function onHttp2SessionEnd () {
|
|
|
208
179
|
* This is the root cause of #3011
|
|
209
180
|
* We need to handle GOAWAY frames properly, and trigger the session close
|
|
210
181
|
* along with the socket right away
|
|
182
|
+
*
|
|
183
|
+
* @this {import('http2').ClientHttp2Session}
|
|
184
|
+
* @param {number} errorCode
|
|
211
185
|
*/
|
|
212
|
-
function
|
|
213
|
-
|
|
186
|
+
function onHttp2SessionGoAway (errorCode) {
|
|
187
|
+
// We cannot recover, so best to close the session and the socket
|
|
188
|
+
const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(this[kSocket]))
|
|
189
|
+
const client = this[kClient]
|
|
214
190
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Requests should be failed with the error after the current one is handled
|
|
218
|
-
this[kSocket][kError] = err
|
|
219
|
-
this[kClient][kOnError](err)
|
|
191
|
+
client[kSocket] = null
|
|
192
|
+
client[kHTTPContext] = null
|
|
220
193
|
|
|
221
|
-
this
|
|
194
|
+
if (this[kHTTP2Session] !== null) {
|
|
195
|
+
this[kHTTP2Session].destroy(err)
|
|
196
|
+
this[kHTTP2Session] = null
|
|
197
|
+
}
|
|
222
198
|
|
|
223
199
|
util.destroy(this[kSocket], err)
|
|
200
|
+
|
|
201
|
+
// Fail head of pipeline.
|
|
202
|
+
const request = client[kQueue][client[kRunningIdx]]
|
|
203
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
204
|
+
util.errorRequest(client, request, err)
|
|
205
|
+
|
|
206
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
207
|
+
|
|
208
|
+
assert(client[kRunning] === 0)
|
|
209
|
+
|
|
210
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
211
|
+
|
|
212
|
+
client[kResume]()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onHttp2SessionClose () {
|
|
216
|
+
const { [kClient]: client } = this
|
|
217
|
+
const { [kSocket]: socket } = client
|
|
218
|
+
|
|
219
|
+
const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket))
|
|
220
|
+
|
|
221
|
+
client[kHTTP2Session] = null
|
|
222
|
+
|
|
223
|
+
if (client.destroyed) {
|
|
224
|
+
assert(client[kPending] === 0)
|
|
225
|
+
|
|
226
|
+
// Fail entire queue.
|
|
227
|
+
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
228
|
+
for (let i = 0; i < requests.length; i++) {
|
|
229
|
+
const request = requests[i]
|
|
230
|
+
util.errorRequest(client, request, err)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function onHttp2SocketClose () {
|
|
236
|
+
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
|
|
237
|
+
|
|
238
|
+
const client = this[kHTTP2Session][kClient]
|
|
239
|
+
|
|
240
|
+
client[kSocket] = null
|
|
241
|
+
|
|
242
|
+
if (this[kHTTP2Session] !== null) {
|
|
243
|
+
this[kHTTP2Session].destroy(err)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
247
|
+
|
|
248
|
+
assert(client[kRunning] === 0)
|
|
249
|
+
|
|
250
|
+
client.emit('disconnect', client[kUrl], [client], err)
|
|
251
|
+
|
|
252
|
+
client[kResume]()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function onHttp2SocketError (err) {
|
|
256
|
+
assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')
|
|
257
|
+
|
|
258
|
+
this[kError] = err
|
|
259
|
+
|
|
260
|
+
this[kClient][kOnError](err)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function onHttp2SocketEnd () {
|
|
264
|
+
util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this)))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function onSocketClose () {
|
|
268
|
+
this[kClosed] = true
|
|
224
269
|
}
|
|
225
270
|
|
|
226
271
|
// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2
|
|
@@ -237,10 +282,6 @@ function writeH2 (client, request) {
|
|
|
237
282
|
return false
|
|
238
283
|
}
|
|
239
284
|
|
|
240
|
-
if (request.aborted) {
|
|
241
|
-
return false
|
|
242
|
-
}
|
|
243
|
-
|
|
244
285
|
const headers = {}
|
|
245
286
|
for (let n = 0; n < reqHeaders.length; n += 2) {
|
|
246
287
|
const key = reqHeaders[n + 0]
|
|
@@ -283,6 +324,8 @@ function writeH2 (client, request) {
|
|
|
283
324
|
// We do not destroy the socket as we can continue using the session
|
|
284
325
|
// the stream gets destroyed and the session remains to create new streams
|
|
285
326
|
util.destroy(body, err)
|
|
327
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
328
|
+
client[kResume]()
|
|
286
329
|
}
|
|
287
330
|
|
|
288
331
|
try {
|
|
@@ -293,6 +336,10 @@ function writeH2 (client, request) {
|
|
|
293
336
|
util.errorRequest(client, request, err)
|
|
294
337
|
}
|
|
295
338
|
|
|
339
|
+
if (request.aborted) {
|
|
340
|
+
return false
|
|
341
|
+
}
|
|
342
|
+
|
|
296
343
|
if (method === 'CONNECT') {
|
|
297
344
|
session.ref()
|
|
298
345
|
// We are already connected, streams are pending, first request
|
|
@@ -304,10 +351,12 @@ function writeH2 (client, request) {
|
|
|
304
351
|
if (stream.id && !stream.pending) {
|
|
305
352
|
request.onUpgrade(null, null, stream)
|
|
306
353
|
++session[kOpenStreams]
|
|
354
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
307
355
|
} else {
|
|
308
356
|
stream.once('ready', () => {
|
|
309
357
|
request.onUpgrade(null, null, stream)
|
|
310
358
|
++session[kOpenStreams]
|
|
359
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
311
360
|
})
|
|
312
361
|
}
|
|
313
362
|
|
|
@@ -428,17 +477,20 @@ function writeH2 (client, request) {
|
|
|
428
477
|
// Present specially when using pipeline or stream
|
|
429
478
|
if (stream.state?.state == null || stream.state.state < 6) {
|
|
430
479
|
request.onComplete([])
|
|
431
|
-
return
|
|
432
480
|
}
|
|
433
481
|
|
|
434
|
-
// Stream is closed or half-closed-remote (6), decrement counter and cleanup
|
|
435
|
-
// It does not have sense to continue working with the stream as we do not
|
|
436
|
-
// have yet RST_STREAM support on client-side
|
|
437
482
|
if (session[kOpenStreams] === 0) {
|
|
483
|
+
// Stream is closed or half-closed-remote (6), decrement counter and cleanup
|
|
484
|
+
// It does not have sense to continue working with the stream as we do not
|
|
485
|
+
// have yet RST_STREAM support on client-side
|
|
486
|
+
|
|
438
487
|
session.unref()
|
|
439
488
|
}
|
|
440
489
|
|
|
441
490
|
abort(new InformationalError('HTTP/2: stream half-closed (remote)'))
|
|
491
|
+
client[kQueue][client[kRunningIdx]++] = null
|
|
492
|
+
client[kPendingIdx] = client[kRunningIdx]
|
|
493
|
+
client[kResume]()
|
|
442
494
|
})
|
|
443
495
|
|
|
444
496
|
stream.once('close', () => {
|
|
@@ -113,9 +113,9 @@ class PoolBase extends DispatcherBase {
|
|
|
113
113
|
|
|
114
114
|
async [kClose] () {
|
|
115
115
|
if (this[kQueue].isEmpty()) {
|
|
116
|
-
|
|
116
|
+
await Promise.all(this[kClients].map(c => c.close()))
|
|
117
117
|
} else {
|
|
118
|
-
|
|
118
|
+
await new Promise((resolve) => {
|
|
119
119
|
this[kClosedResolve] = resolve
|
|
120
120
|
})
|
|
121
121
|
}
|
|
@@ -130,7 +130,7 @@ class PoolBase extends DispatcherBase {
|
|
|
130
130
|
item.handler.onError(err)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
await Promise.all(this[kClients].map(c => c.destroy(err)))
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
[kDispatch] (opts, handler) {
|