1
1
import * as net from 'net' ;
2
2
import * as tls from 'tls' ;
3
3
import * as http from 'http' ;
4
+ import { Agent as HttpsAgent } from 'https' ;
4
5
import type { Duplex } from 'stream' ;
5
6
6
7
export * from './helpers' ;
@@ -77,6 +78,65 @@ export abstract class Agent extends http.Agent {
77
78
) ;
78
79
}
79
80
81
+ // In order to support async signatures in `connect()` and Node's native
82
+ // connection pooling in `http.Agent`, the array of sockets for each origin
83
+ // has to be updated synchronously. This is so the length of the array is
84
+ // accurate when `addRequest()` is next called. We achieve this by creating a
85
+ // fake socket and adding it to `sockets[origin]` and incrementing
86
+ // `totalSocketCount`.
87
+ private incrementSockets ( name : string ) {
88
+ // If `maxSockets` and `maxTotalSockets` are both Infinity then there is no
89
+ // need to create a fake socket because Node.js native connection pooling
90
+ // will never be invoked.
91
+ if ( this . maxSockets === Infinity && this . maxTotalSockets === Infinity ) {
92
+ return null ;
93
+ }
94
+ // All instances of `sockets` are expected TypeScript errors. The
95
+ // alternative is to add it as a private property of this class but that
96
+ // will break TypeScript subclassing.
97
+ if ( ! this . sockets [ name ] ) {
98
+ // @ts -expect-error `sockets` is readonly in `@types/node`
99
+ this . sockets [ name ] = [ ] ;
100
+ }
101
+ const fakeSocket = new net . Socket ( { writable : false } ) ;
102
+ ( this . sockets [ name ] as net . Socket [ ] ) . push ( fakeSocket ) ;
103
+ // @ts -expect-error `totalSocketCount` isn't defined in `@types/node`
104
+ this . totalSocketCount ++ ;
105
+ return fakeSocket ;
106
+ }
107
+
108
+ private decrementSockets ( name : string , socket : null | net . Socket ) {
109
+ if ( ! this . sockets [ name ] || socket === null ) {
110
+ return ;
111
+ }
112
+ const sockets = this . sockets [ name ] as net . Socket [ ] ;
113
+ const index = sockets . indexOf ( socket ) ;
114
+ if ( index !== - 1 ) {
115
+ sockets . splice ( index , 1 ) ;
116
+ // @ts -expect-error `totalSocketCount` isn't defined in `@types/node`
117
+ this . totalSocketCount -- ;
118
+ if ( sockets . length === 0 ) {
119
+ // @ts -expect-error `sockets` is readonly in `@types/node`
120
+ delete this . sockets [ name ] ;
121
+ }
122
+ }
123
+ }
124
+
125
+ // In order to properly update the socket pool, we need to call `getName()` on
126
+ // the core `https.Agent` if it is a secureEndpoint.
127
+ getName ( options : AgentConnectOpts ) : string {
128
+ const secureEndpoint =
129
+ typeof options . secureEndpoint === 'boolean'
130
+ ? options . secureEndpoint
131
+ : this . isSecureEndpoint ( options ) ;
132
+ if ( secureEndpoint ) {
133
+ // @ts -expect-error `getName()` isn't defined in `@types/node`
134
+ return HttpsAgent . prototype . getName . call ( this , options ) ;
135
+ }
136
+ // @ts -expect-error `getName()` isn't defined in `@types/node`
137
+ return super . getName ( options ) ;
138
+ }
139
+
80
140
createSocket (
81
141
req : http . ClientRequest ,
82
142
options : AgentConnectOpts ,
@@ -86,17 +146,26 @@ export abstract class Agent extends http.Agent {
86
146
...options ,
87
147
secureEndpoint : this . isSecureEndpoint ( options ) ,
88
148
} ;
149
+ const name = this . getName ( connectOpts ) ;
150
+ const fakeSocket = this . incrementSockets ( name ) ;
89
151
Promise . resolve ( )
90
152
. then ( ( ) => this . connect ( req , connectOpts ) )
91
- . then ( ( socket ) => {
92
- if ( socket instanceof http . Agent ) {
93
- // @ts -expect-error `addRequest()` isn't defined in `@types/node`
94
- return socket . addRequest ( req , connectOpts ) ;
153
+ . then (
154
+ ( socket ) => {
155
+ this . decrementSockets ( name , fakeSocket ) ;
156
+ if ( socket instanceof http . Agent ) {
157
+ // @ts -expect-error `addRequest()` isn't defined in `@types/node`
158
+ return socket . addRequest ( req , connectOpts ) ;
159
+ }
160
+ this [ INTERNAL ] . currentSocket = socket ;
161
+ // @ts -expect-error `createSocket()` isn't defined in `@types/node`
162
+ super . createSocket ( req , options , cb ) ;
163
+ } ,
164
+ ( err ) => {
165
+ this . decrementSockets ( name , fakeSocket ) ;
166
+ cb ( err ) ;
95
167
}
96
- this [ INTERNAL ] . currentSocket = socket ;
97
- // @ts -expect-error `createSocket()` isn't defined in `@types/node`
98
- super . createSocket ( req , options , cb ) ;
99
- } , cb ) ;
168
+ ) ;
100
169
}
101
170
102
171
createConnection ( ) : Duplex {
0 commit comments