urllib 3.0.0-alpha.1 → 3.0.2
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/History.md +844 -0
- package/README.md +63 -100
- package/package.json +13 -9
- package/src/HttpAgent.ts +72 -0
- package/src/HttpClient.ts +304 -85
- package/src/Request.ts +51 -54
- package/src/Response.ts +11 -10
- package/src/cjs/HttpAgent.d.ts +16 -0
- package/src/cjs/HttpAgent.js +62 -0
- package/src/cjs/HttpAgent.js.map +1 -0
- package/src/cjs/HttpClient.d.ts +29 -1
- package/src/cjs/HttpClient.js +373 -200
- package/src/cjs/HttpClient.js.map +1 -1
- package/src/cjs/Request.d.ts +47 -54
- package/src/cjs/Response.d.ts +9 -8
- package/src/cjs/index.d.ts +3 -1
- package/src/cjs/index.js +6 -1
- package/src/cjs/index.js.map +1 -1
- package/src/cjs/utils.d.ts +1 -0
- package/src/cjs/utils.js +7 -1
- package/src/cjs/utils.js.map +1 -1
- package/src/esm/HttpAgent.d.ts +16 -0
- package/src/esm/HttpAgent.js +58 -0
- package/src/esm/HttpAgent.js.map +1 -0
- package/src/esm/HttpClient.d.ts +29 -1
- package/src/esm/HttpClient.js +373 -200
- package/src/esm/HttpClient.js.map +1 -1
- package/src/esm/Request.d.ts +47 -54
- package/src/esm/Response.d.ts +9 -8
- package/src/esm/index.d.ts +3 -1
- package/src/esm/index.js +4 -2
- package/src/esm/index.js.map +1 -1
- package/src/esm/utils.d.ts +1 -0
- package/src/esm/utils.js +5 -0
- package/src/esm/utils.js.map +1 -1
- package/src/index.ts +4 -3
- package/src/utils.ts +6 -0
package/README.md
CHANGED
@@ -79,58 +79,27 @@ console.log('status: %s, body size: %d, headers: %j', res.statusCode, data.lengt
|
|
79
79
|
- ***options*** Object - Optional
|
80
80
|
- ***method*** String - Request method, defaults to `GET`. Could be `GET`, `POST`, `DELETE` or `PUT`. Alias 'type'.
|
81
81
|
- ***data*** Object - Data to be sent. Will be stringify automatically.
|
82
|
-
- ***dataAsQueryString*** Boolean - Force convert `data` to query string.
|
83
82
|
- ***content*** String | [Buffer](http://nodejs.org/api/buffer.html) - Manually set the content of payload. If set, `data` will be ignored.
|
84
83
|
- ***stream*** [stream.Readable](http://nodejs.org/api/stream.html#stream_class_stream_readable) - Stream to be pipe to the remote. If set, `data` and `content` will be ignored.
|
85
84
|
- ***writeStream*** [stream.Writable](http://nodejs.org/api/stream.html#stream_class_stream_writable) - A writable stream to be piped by the response stream. Responding data will be write to this stream and `callback` will be called with `data` set `null` after finished writing.
|
86
85
|
- ***files*** {Array<ReadStream|Buffer|String> | Object | ReadStream | Buffer | String - The files will send with `multipart/form-data` format, base on `formstream`. If `method` not set, will use `POST` method by default.
|
87
|
-
- ***consumeWriteStream*** [true] - consume the writeStream, invoke the callback after writeStream close.
|
88
86
|
- ***contentType*** String - Type of request data. Could be `json` (**Notes**: not use `application/json` here). If it's `json`, will auto set `Content-Type: application/json` header.
|
89
|
-
- ***nestedQuerystring*** Boolean - urllib default use querystring to stringify form data which don't support nested object, will use [qs](https://github.com/ljharb/qs) instead of querystring to support nested object by set this option to true.
|
90
87
|
- ***dataType*** String - Type of response data. Could be `text` or `json`. If it's `text`, the `callback`ed `data` would be a String. If it's `json`, the `data` of callback would be a parsed JSON Object and will auto set `Accept: application/json` header. Default `callback`ed `data` would be a `Buffer`.
|
91
88
|
- **fixJSONCtlChars** Boolean - Fix the control characters (U+0000 through U+001F) before JSON parse response. Default is `false`.
|
92
89
|
- ***headers*** Object - Request headers.
|
93
|
-
- ***keepHeaderCase*** Boolean - by default will convert header keys to lowercase
|
94
90
|
- ***timeout*** Number | Array - Request timeout in milliseconds for connecting phase and response receiving phase. Defaults to `exports.TIMEOUT`, both are 5s. You can use `timeout: 5000` to tell urllib use same timeout on two phase or set them seperately such as `timeout: [3000, 5000]`, which will set connecting timeout to 3s and response 5s.
|
95
91
|
- ***auth*** String - `username:password` used in HTTP Basic Authorization.
|
96
|
-
- ***digestAuth*** String - `username:password` used in HTTP [Digest Authorization](http://en.wikipedia.org/wiki/Digest_access_authentication).
|
97
92
|
- ***agent*** [http.Agent](http://nodejs.org/api/http.html#http_class_http_agent) - HTTP Agent object.
|
98
93
|
Set `false` if you does not use agent.
|
99
94
|
- ***httpsAgent*** [https.Agent](http://nodejs.org/api/https.html#https_class_https_agent) - HTTPS Agent object.
|
100
95
|
Set `false` if you does not use agent.
|
101
|
-
- ***ca*** String | Buffer | Array - An array of strings or Buffers of trusted certificates.
|
102
|
-
If this is omitted several well known "root" CAs will be used, like VeriSign.
|
103
|
-
These are used to authorize connections.
|
104
|
-
**Notes**: This is necessary only if the server uses the self-signed certificate
|
105
|
-
- ***rejectUnauthorized*** Boolean - If true, the server certificate is verified against the list of supplied CAs.
|
106
|
-
An 'error' event is emitted if verification fails. Default: true.
|
107
|
-
- ***pfx*** String | Buffer - A string or Buffer containing the private key,
|
108
|
-
certificate and CA certs of the server in PFX or PKCS12 format.
|
109
|
-
- ***key*** String | Buffer - A string or Buffer containing the private key of the client in PEM format.
|
110
|
-
**Notes**: This is necessary only if using the client certificate authentication
|
111
|
-
- ***cert*** String | Buffer - A string or Buffer containing the certificate key of the client in PEM format.
|
112
|
-
**Notes**: This is necessary only if using the client certificate authentication
|
113
|
-
- ***passphrase*** String - A string of passphrase for the private key or pfx.
|
114
|
-
- ***ciphers*** String - A string describing the ciphers to use or exclude.
|
115
|
-
- ***secureProtocol*** String - The SSL method to use, e.g. SSLv3_method to force SSL version 3.
|
116
96
|
- ***followRedirect*** Boolean - follow HTTP 3xx responses as redirects. defaults to false.
|
117
97
|
- ***maxRedirects*** Number - The maximum number of redirects to follow, defaults to 10.
|
118
98
|
- ***formatRedirectUrl*** Function - Format the redirect url by your self. Default is `url.resolve(from, to)`.
|
119
99
|
- ***beforeRequest*** Function - Before request hook, you can change every thing here.
|
120
100
|
- ***streaming*** Boolean - let you get the `res` object when request connected, default `false`. alias `customResponse`
|
121
|
-
- ***gzip*** Boolean - Accept gzip response content and auto decode it, default is `false`.
|
101
|
+
- ***gzip*** Boolean - Accept `gzip, br` response content and auto decode it, default is `false`.
|
122
102
|
- ***timing*** Boolean - Enable timing or not, default is `false`.
|
123
|
-
- ***enableProxy*** Boolean - Enable proxy request, default is `false`.
|
124
|
-
- ***proxy*** String | Object - proxy agent uri or options, default is `null`.
|
125
|
-
- ***lookup*** Function - Custom DNS lookup function, default is `dns.lookup`. Require node >= 4.0.0(for http protocol) and node >=8(for https protocol)
|
126
|
-
- ***checkAddress*** Function: optional, check request address to protect from SSRF and similar attacks. It receive tow arguments(`ip` and `family`) and should return true or false to identified the address is legal or not. It rely on `lookup` and have the same version requirement.
|
127
|
-
- ***trace*** Boolean - Enable capture stack include call site of library entrance, default is `false`.
|
128
|
-
- ***socketPath*** String - optional Unix Domain Socket. (Refer to [Node.js Document](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_request_options_callback))
|
129
|
-
|
130
|
-
#### Returns
|
131
|
-
|
132
|
-
- **data** Buffer | Object - The data responsed. Would be a Buffer if `dataType` is set to `text` or an JSON parsed into Object if it's set to `json`.
|
133
|
-
- **res** [http.IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) - The response.
|
134
103
|
|
135
104
|
#### Options: `options.data`
|
136
105
|
|
@@ -150,14 +119,14 @@ For `GET` request, `data` will be stringify to query string, e.g. `http://exampl
|
|
150
119
|
|
151
120
|
For others like `POST`, `PATCH` or `PUT` request,
|
152
121
|
in defaults, the `data` will be stringify into `application/x-www-form-urlencoded` format
|
153
|
-
if `
|
122
|
+
if `content-type` header is not set.
|
154
123
|
|
155
|
-
If `
|
124
|
+
If `content-type` is `application/json`, the `data` will be `JSON.stringify` to JSON data format.
|
156
125
|
|
157
126
|
#### Options: `options.content`
|
158
127
|
|
159
128
|
`options.content` is useful when you wish to construct the request body by yourself,
|
160
|
-
for example making a `
|
129
|
+
for example making a `content-type: application/json` request.
|
161
130
|
|
162
131
|
Notes that if you want to send a JSON body, you should stringify it yourself:
|
163
132
|
|
@@ -178,8 +147,8 @@ It would make a HTTP request like:
|
|
178
147
|
|
179
148
|
```http
|
180
149
|
POST / HTTP/1.1
|
181
|
-
|
182
|
-
|
150
|
+
host: example.com
|
151
|
+
content-type: application/json
|
183
152
|
|
184
153
|
{
|
185
154
|
"a": "hello",
|
@@ -193,7 +162,7 @@ This exmaple can use `options.data` with `application/json` content type:
|
|
193
162
|
await request('https://example.com', {
|
194
163
|
method: 'POST',
|
195
164
|
headers: {
|
196
|
-
'
|
165
|
+
'content-type': 'application/json'
|
197
166
|
},
|
198
167
|
data: {
|
199
168
|
a: 'hello',
|
@@ -268,7 +237,6 @@ Response is normal object, it contains:
|
|
268
237
|
- `status` or `statusCode`: response status code.
|
269
238
|
- `-1` meaning some network error like `ENOTFOUND`
|
270
239
|
- `-2` meaning ConnectionTimeoutError
|
271
|
-
- `statusMessage`: response status message.
|
272
240
|
- `headers`: response http headers, default is `{}`
|
273
241
|
- `size`: response size
|
274
242
|
- `aborted`: response was aborted or not
|
@@ -279,75 +247,72 @@ Response is normal object, it contains:
|
|
279
247
|
- `socketHandledRequests`: socket already handled request count
|
280
248
|
- `socketHandledResponses`: socket already handled response count
|
281
249
|
|
282
|
-
|
283
|
-
|
284
|
-
If the underlaying connection was terminated before `response.end()` was called,
|
285
|
-
`res.aborted` should be `true`.
|
250
|
+
## Run test with debug log
|
286
251
|
|
287
|
-
```
|
288
|
-
|
289
|
-
|
290
|
-
createServer((req, res) => {
|
291
|
-
req.resume();
|
292
|
-
req.on('end', () => {
|
293
|
-
res.write('foo haha\n');
|
294
|
-
setTimeout(() => {
|
295
|
-
res.write('foo haha 2');
|
296
|
-
setTimeout(() => {
|
297
|
-
res.socket.end();
|
298
|
-
}, 300);
|
299
|
-
}, 200);
|
300
|
-
return;
|
301
|
-
});
|
302
|
-
}).listen(2022);
|
303
|
-
|
304
|
-
const { data, res } = await request('http://127.0.0.1:2022/socket.end');
|
305
|
-
assert.equal(data.toString(), 'foo haha\nfoo haha 2');
|
306
|
-
assert(res.aborted);
|
252
|
+
```bash
|
253
|
+
NODE_DEBUG=urllib npm test
|
307
254
|
```
|
308
255
|
|
309
|
-
|
256
|
+
## Mocking Request
|
310
257
|
|
311
|
-
|
258
|
+
export from [undici](https://undici.nodejs.org/#/docs/best-practices/mocking-request)
|
312
259
|
|
313
|
-
|
260
|
+
```ts
|
261
|
+
import { strict as assert } from 'assert';
|
262
|
+
import { MockAgent, setGlobalDispatcher, request } from 'urllib';
|
314
263
|
|
315
|
-
|
264
|
+
const mockAgent = new MockAgent();
|
265
|
+
setGlobalDispatcher(mockAgent);
|
316
266
|
|
317
|
-
|
318
|
-
- ***retryDelay*** Number - wait a delay(ms) between retries.
|
319
|
-
- ***isRetry*** Function - determine whether retry, a response object as the first argument. it will retry when status >= 500 by default. Request error is not included.
|
267
|
+
const mockPool = mockAgent.get('http://localhost:7001');
|
320
268
|
|
321
|
-
|
269
|
+
mockPool.intercept({
|
270
|
+
path: '/foo',
|
271
|
+
method: 'POST',
|
272
|
+
}).reply(400, {
|
273
|
+
message: 'mock 400 bad request',
|
274
|
+
});
|
322
275
|
|
323
|
-
|
276
|
+
const response = await request('http://localhost:7001/foo', {
|
277
|
+
method: 'POST',
|
278
|
+
dataType: 'json',
|
279
|
+
});
|
280
|
+
assert.equal(response.status, 400);
|
281
|
+
assert.deepEqual(response.data, { message: 'mock 400 bad request' });
|
282
|
+
```
|
324
283
|
|
325
|
-
|
284
|
+
## Benchmarks
|
326
285
|
|
327
|
-
|
286
|
+
Fork [undici benchmarks script](https://github.com/fengmk2/undici/blob/urllib-benchmark/benchmarks/benchmark.js)
|
328
287
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
288
|
+
### Connections 1
|
289
|
+
|
290
|
+
| Tests | Samples | Result | Tolerance | Difference with slowest |
|
291
|
+
|---------------------|---------|---------------|-----------|-------------------------|
|
292
|
+
| http - no keepalive | 15 | 6.38 req/sec | ± 2.44 % | - |
|
293
|
+
| http - keepalive | 10 | 6.77 req/sec | ± 2.35 % | + 6.13 % |
|
294
|
+
| urllib2 - request | 45 | 40.13 req/sec | ± 2.88 % | + 528.66 % |
|
295
|
+
| urllib3 - request | 10 | 58.51 req/sec | ± 2.52 % | + 816.64 % |
|
296
|
+
| undici - pipeline | 5 | 59.12 req/sec | ± 2.47 % | + 826.18 % |
|
297
|
+
| undici - fetch | 15 | 60.42 req/sec | ± 3.00 % | + 846.60 % |
|
298
|
+
| undici - dispatch | 5 | 60.58 req/sec | ± 1.39 % | + 848.99 % |
|
299
|
+
| undici - stream | 5 | 61.30 req/sec | ± 1.31 % | + 860.39 % |
|
300
|
+
| undici - request | 5 | 61.74 req/sec | ± 2.03 % | + 867.20 % |
|
339
301
|
|
340
|
-
|
302
|
+
### Connections 50
|
341
303
|
|
342
|
-
|
304
|
+
| Tests | Samples | Result | Tolerance | Difference with slowest |
|
305
|
+
|---------------------|---------|------------------|-----------|-------------------------|
|
306
|
+
| urllib2 - request | 51 | 1465.40 req/sec | ± 14.40 % | - |
|
307
|
+
| undici - fetch | 40 | 3121.10 req/sec | ± 2.82 % | + 112.99 % |
|
308
|
+
| http - no keepalive | 45 | 3355.42 req/sec | ± 2.84 % | + 128.98 % |
|
309
|
+
| http - keepalive | 51 | 5179.55 req/sec | ± 36.61 % | + 253.46 % |
|
310
|
+
| urllib3 - request | 30 | 7045.86 req/sec | ± 2.93 % | + 380.82 % |
|
311
|
+
| undici - pipeline | 50 | 8306.92 req/sec | ± 2.99 % | + 466.87 % |
|
312
|
+
| undici - request | 51 | 9552.59 req/sec | ± 13.13 % | + 551.88 % |
|
313
|
+
| undici - stream | 45 | 12523.45 req/sec | ± 2.97 % | + 754.61 % |
|
314
|
+
| undici - dispatch | 51 | 12970.18 req/sec | ± 3.15 % | + 785.10 % |
|
343
315
|
|
344
|
-
- ❎ Support Proxy
|
345
|
-
- ✅ Upload file like form upload
|
346
|
-
- ✅ Auto redirect handle
|
347
|
-
- ✅ https & self-signed certificate
|
348
|
-
- ✅ Connection timeout & Response timeout
|
349
|
-
- ✅ Support `Accept-Encoding=gzip` by default
|
350
|
-
- ✅ Support [Digest access authentication](http://en.wikipedia.org/wiki/Digest_access_authentication)
|
351
316
|
|
352
317
|
<!-- GITCONTRIBUTOR_START -->
|
353
318
|
|
@@ -356,18 +321,16 @@ When open the trace, urllib may have poor perfomance, please consider carefully.
|
|
356
321
|
|[<img src="https://avatars.githubusercontent.com/u/156269?v=4" width="100px;"/><br/><sub><b>fengmk2</b></sub>](https://github.com/fengmk2)<br/>|[<img src="https://avatars.githubusercontent.com/u/985607?v=4" width="100px;"/><br/><sub><b>dead-horse</b></sub>](https://github.com/dead-horse)<br/>|[<img src="https://avatars.githubusercontent.com/u/288288?v=4" width="100px;"/><br/><sub><b>xingrz</b></sub>](https://github.com/xingrz)<br/>|[<img src="https://avatars.githubusercontent.com/u/360661?v=4" width="100px;"/><br/><sub><b>popomore</b></sub>](https://github.com/popomore)<br/>|[<img src="https://avatars.githubusercontent.com/u/327019?v=4" width="100px;"/><br/><sub><b>JacksonTian</b></sub>](https://github.com/JacksonTian)<br/>|[<img src="https://avatars.githubusercontent.com/u/543405?v=4" width="100px;"/><br/><sub><b>ibigbug</b></sub>](https://github.com/ibigbug)<br/>|
|
357
322
|
| :---: | :---: | :---: | :---: | :---: | :---: |
|
358
323
|
|[<img src="https://avatars.githubusercontent.com/u/14790466?v=4" width="100px;"/><br/><sub><b>greenkeeperio-bot</b></sub>](https://github.com/greenkeeperio-bot)<br/>|[<img src="https://avatars.githubusercontent.com/u/227713?v=4" width="100px;"/><br/><sub><b>atian25</b></sub>](https://github.com/atian25)<br/>|[<img src="https://avatars.githubusercontent.com/u/5381764?v=4" width="100px;"/><br/><sub><b>paambaati</b></sub>](https://github.com/paambaati)<br/>|[<img src="https://avatars.githubusercontent.com/u/1433247?v=4" width="100px;"/><br/><sub><b>denghongcai</b></sub>](https://github.com/denghongcai)<br/>|[<img src="https://avatars.githubusercontent.com/u/2842176?v=4" width="100px;"/><br/><sub><b>XadillaX</b></sub>](https://github.com/XadillaX)<br/>|[<img src="https://avatars.githubusercontent.com/u/1147375?v=4" width="100px;"/><br/><sub><b>alsotang</b></sub>](https://github.com/alsotang)<br/>|
|
359
|
-
|[<img src="https://avatars.githubusercontent.com/u/546535?v=4" width="100px;"/><br/><sub><b>leoner</b></sub>](https://github.com/leoner)<br/>|[<img src="https://avatars.githubusercontent.com/u/19908330?v=4" width="100px;"/><br/><sub><b>hyj1991</b></sub>](https://github.com/hyj1991)<br/>|[<img src="https://avatars.githubusercontent.com/u/1747852?v=4" width="100px;"/><br/><sub><b>isayme</b></sub>](https://github.com/isayme)<br/>|[<img src="https://avatars.githubusercontent.com/u/
|
360
|
-
|[<img src="https://avatars.githubusercontent.com/u/
|
361
|
-
|[<img src="https://avatars.githubusercontent.com/u/
|
362
|
-
|[<img src="https://avatars.githubusercontent.com/u/
|
324
|
+
|[<img src="https://avatars.githubusercontent.com/u/546535?v=4" width="100px;"/><br/><sub><b>leoner</b></sub>](https://github.com/leoner)<br/>|[<img src="https://avatars.githubusercontent.com/u/19908330?v=4" width="100px;"/><br/><sub><b>hyj1991</b></sub>](https://github.com/hyj1991)<br/>|[<img src="https://avatars.githubusercontent.com/u/1747852?v=4" width="100px;"/><br/><sub><b>isayme</b></sub>](https://github.com/isayme)<br/>|[<img src="https://avatars.githubusercontent.com/u/6897780?v=4" width="100px;"/><br/><sub><b>killagu</b></sub>](https://github.com/killagu)<br/>|[<img src="https://avatars.githubusercontent.com/u/252317?v=4" width="100px;"/><br/><sub><b>cyjake</b></sub>](https://github.com/cyjake)<br/>|[<img src="https://avatars.githubusercontent.com/u/5856440?v=4" width="100px;"/><br/><sub><b>whxaxes</b></sub>](https://github.com/whxaxes)<br/>|
|
325
|
+
|[<img src="https://avatars.githubusercontent.com/u/309219?v=4" width="100px;"/><br/><sub><b>chadxz</b></sub>](https://github.com/chadxz)<br/>|[<img src="https://avatars.githubusercontent.com/u/5139554?v=4" width="100px;"/><br/><sub><b>danielwpz</b></sub>](https://github.com/danielwpz)<br/>|[<img src="https://avatars.githubusercontent.com/u/5127897?v=4" width="100px;"/><br/><sub><b>danielsss</b></sub>](https://github.com/danielsss)<br/>|[<img src="https://avatars.githubusercontent.com/u/3367820?v=4" width="100px;"/><br/><sub><b>Jeff-Tian</b></sub>](https://github.com/Jeff-Tian)<br/>|[<img src="https://avatars.githubusercontent.com/u/32407?v=4" width="100px;"/><br/><sub><b>jedahan</b></sub>](https://github.com/jedahan)<br/>|[<img src="https://avatars.githubusercontent.com/u/17075261?v=4" width="100px;"/><br/><sub><b>nick-ng</b></sub>](https://github.com/nick-ng)<br/>|
|
326
|
+
|[<img src="https://avatars.githubusercontent.com/u/1706595?v=4" width="100px;"/><br/><sub><b>rishavsharan</b></sub>](https://github.com/rishavsharan)<br/>|[<img src="https://avatars.githubusercontent.com/u/1886161?v=4" width="100px;"/><br/><sub><b>willizm</b></sub>](https://github.com/willizm)<br/>|[<img src="https://avatars.githubusercontent.com/u/7227589?v=4" width="100px;"/><br/><sub><b>davidkhala</b></sub>](https://github.com/davidkhala)<br/>|[<img src="https://avatars.githubusercontent.com/u/535479?v=4" width="100px;"/><br/><sub><b>aleafs</b></sub>](https://github.com/aleafs)<br/>|[<img src="https://avatars.githubusercontent.com/u/3689968?v=4" width="100px;"/><br/><sub><b>Amunu</b></sub>](https://github.com/Amunu)<br/>|[<img src="https://avatars.githubusercontent.com/in/9426?v=4" width="100px;"/><br/><sub><b>azure-pipelines[bot]</b></sub>](https://github.com/apps/azure-pipelines)<br/>|
|
327
|
+
|[<img src="https://avatars.githubusercontent.com/u/1281323?v=4" width="100px;"/><br/><sub><b>changzhiwin</b></sub>](https://github.com/changzhiwin)<br/>|[<img src="https://avatars.githubusercontent.com/u/929503?v=4" width="100px;"/><br/><sub><b>yuzhigang33</b></sub>](https://github.com/yuzhigang33)<br/>|[<img src="https://avatars.githubusercontent.com/u/981128?v=4" width="100px;"/><br/><sub><b>fishbar</b></sub>](https://github.com/fishbar)<br/>|[<img src="https://avatars.githubusercontent.com/u/1207064?v=4" width="100px;"/><br/><sub><b>gxcsoccer</b></sub>](https://github.com/gxcsoccer)<br/>|[<img src="https://avatars.githubusercontent.com/u/17476119?v=4" width="100px;"/><br/><sub><b>mars-coder</b></sub>](https://github.com/mars-coder)<br/>|[<img src="https://avatars.githubusercontent.com/u/929179?v=4" width="100px;"/><br/><sub><b>rockdai</b></sub>](https://github.com/rockdai)<br/>|
|
363
328
|
[<img src="https://avatars.githubusercontent.com/u/2196373?v=4" width="100px;"/><br/><sub><b>dickeylth</b></sub>](https://github.com/dickeylth)<br/>|[<img src="https://avatars.githubusercontent.com/u/13050025?v=4" width="100px;"/><br/><sub><b>aladdin-add</b></sub>](https://github.com/aladdin-add)<br/>
|
364
329
|
|
365
|
-
This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `
|
330
|
+
This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Jul 05 2022 16:17:31 GMT+0800`.
|
366
331
|
|
367
332
|
<!-- GITCONTRIBUTOR_END -->
|
368
333
|
|
369
334
|
## License
|
370
335
|
|
371
336
|
[MIT](LICENSE)
|
372
|
-
|
373
|
-
[bluebird]: https://github.com/petkaantonov/bluebird
|
package/package.json
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
{
|
2
2
|
"name": "urllib",
|
3
|
-
"version": "3.0.
|
3
|
+
"version": "3.0.2",
|
4
|
+
"publishConfig": {
|
5
|
+
"tag": "latest"
|
6
|
+
},
|
4
7
|
"description": "Help in opening URLs (mostly HTTP) in a complex world — basic and digest authentication, redirections, cookies and more. Base undici fetch API.",
|
5
8
|
"keywords": [
|
6
9
|
"urllib",
|
@@ -54,15 +57,19 @@
|
|
54
57
|
"dependencies": {
|
55
58
|
"default-user-agent": "^1.0.0",
|
56
59
|
"digest-header": "^0.0.1",
|
57
|
-
"
|
60
|
+
"form-data-encoder": "^1.7.2",
|
61
|
+
"formdata-node": "^4.3.3",
|
58
62
|
"mime-types": "^2.1.35",
|
59
|
-
"
|
63
|
+
"pump": "^3.0.0",
|
64
|
+
"undici": "^5.6.0"
|
60
65
|
},
|
61
66
|
"devDependencies": {
|
62
67
|
"@types/busboy": "^1.5.0",
|
63
68
|
"@types/default-user-agent": "^1.0.0",
|
64
69
|
"@types/jest": "28",
|
65
70
|
"@types/mime-types": "^2.1.1",
|
71
|
+
"@types/pump": "^1.1.1",
|
72
|
+
"@types/selfsigned": "^2.0.1",
|
66
73
|
"busboy": "^1.6.0",
|
67
74
|
"coffee": "5",
|
68
75
|
"egg-ci": "2",
|
@@ -72,19 +79,16 @@
|
|
72
79
|
"iconv-lite": "^0.6.3",
|
73
80
|
"jest": "28",
|
74
81
|
"jest-summary-reporter": "^0.0.2",
|
75
|
-
"
|
82
|
+
"selfsigned": "^2.0.1",
|
76
83
|
"ts-jest": "28",
|
77
84
|
"tslib": "^2.4.0",
|
78
85
|
"typescript": "4"
|
79
86
|
},
|
80
87
|
"engines": {
|
81
|
-
"node": ">=
|
88
|
+
"node": ">= 14.0.0"
|
82
89
|
},
|
83
90
|
"ci": {
|
84
|
-
"version": "16, 18"
|
85
|
-
},
|
86
|
-
"publishConfig": {
|
87
|
-
"tag": "next"
|
91
|
+
"version": "14, 16, 18"
|
88
92
|
},
|
89
93
|
"license": "MIT"
|
90
94
|
}
|
package/src/HttpAgent.ts
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
import dns from 'dns';
|
2
|
+
import { LookupFunction, isIP } from 'net';
|
3
|
+
import {
|
4
|
+
Agent,
|
5
|
+
} from 'undici';
|
6
|
+
import { DispatchHandlers } from 'undici/types/dispatcher';
|
7
|
+
import { BuildOptions } from 'undici/types/connector';
|
8
|
+
|
9
|
+
export type CheckAddressFunction = (ip: string, family: number | string) => boolean;
|
10
|
+
|
11
|
+
export type HttpAgentOptions = {
|
12
|
+
lookup?: LookupFunction;
|
13
|
+
checkAddress?: CheckAddressFunction;
|
14
|
+
connect?: BuildOptions,
|
15
|
+
};
|
16
|
+
|
17
|
+
class IllegalAddressError extends Error {
|
18
|
+
hostname: string;
|
19
|
+
ip: string;
|
20
|
+
family: number;
|
21
|
+
|
22
|
+
constructor(hostname: string, ip: string, family: number) {
|
23
|
+
const message = 'illegal address';
|
24
|
+
super(message);
|
25
|
+
this.name = this.constructor.name;
|
26
|
+
this.hostname = hostname;
|
27
|
+
this.ip = ip;
|
28
|
+
this.family = family;
|
29
|
+
Error.captureStackTrace(this, this.constructor);
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
export class HttpAgent extends Agent {
|
34
|
+
#checkAddress?: CheckAddressFunction;
|
35
|
+
|
36
|
+
constructor(options: HttpAgentOptions) {
|
37
|
+
/* eslint node/prefer-promises/dns: off*/
|
38
|
+
const _lookup = options.lookup ?? dns.lookup;
|
39
|
+
const lookup: LookupFunction = (hostname, dnsOptions, callback) => {
|
40
|
+
_lookup(hostname, dnsOptions, (err, address, family) => {
|
41
|
+
if (err) return callback(err, address, family);
|
42
|
+
if (options.checkAddress && !options.checkAddress(address, family)) {
|
43
|
+
err = new IllegalAddressError(hostname, address, family);
|
44
|
+
}
|
45
|
+
callback(err, address, family);
|
46
|
+
});
|
47
|
+
};
|
48
|
+
super({
|
49
|
+
connect: { ...options.connect, lookup },
|
50
|
+
});
|
51
|
+
this.#checkAddress = options.checkAddress;
|
52
|
+
}
|
53
|
+
|
54
|
+
dispatch(options: Agent.DispatchOptions, handler: DispatchHandlers): boolean {
|
55
|
+
if (this.#checkAddress && options.origin) {
|
56
|
+
const originUrl = typeof options.origin === 'string' ? new URL(options.origin) : options.origin;
|
57
|
+
let hostname = originUrl.hostname;
|
58
|
+
// [2001:db8:2de::e13] => 2001:db8:2de::e13
|
59
|
+
if (hostname.startsWith('[') && hostname.endsWith(']')) {
|
60
|
+
hostname = hostname.substring(1, hostname.length - 1);
|
61
|
+
}
|
62
|
+
const family = isIP(hostname);
|
63
|
+
if (family === 4 || family === 6) {
|
64
|
+
// if request hostname is ip, custom lookup won't excute
|
65
|
+
if (!this.#checkAddress(hostname, family)) {
|
66
|
+
throw new IllegalAddressError(hostname, hostname, family);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
return super.dispatch(options, handler);
|
71
|
+
}
|
72
|
+
}
|