superagent 4.0.0-alpha.1 → 4.0.0-beta.5
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/.travis.yml +4 -1
- package/.zuul.yml +0 -2
- package/History.md +5 -1
- package/Makefile +9 -0
- package/Readme.md +8 -5
- package/docs/index.md +30 -32
- package/docs/test.html +6 -6
- package/dump.js +1 -0
- package/lib/client.js +1 -2
- package/lib/node/http2wrapper.js +188 -0
- package/lib/node/index.js +72 -14
- package/lib/node/response.js +1 -1
- package/lib/node/unzip.js +2 -2
- package/package.json +8 -8
- package/superagent.js +1 -2
- package/yarn.lock +896 -384
package/.travis.yml
CHANGED
package/.zuul.yml
CHANGED
package/History.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Breaking changes
|
|
4
4
|
|
|
5
|
-
* Node.js v4 has reached it's end of life, so we no longer support it. It's v6+ or later.
|
|
5
|
+
* Node.js v4 has reached it's end of life, so we no longer support it. It's v6+ or later. We recommend Node.js 10.
|
|
6
6
|
* We now use ES6 in the browser code, too.
|
|
7
7
|
* If you're using Browserify or Webpack to package code for Internet Explorer, you will also have to use Babel.
|
|
8
8
|
* The pre-built node_modules/superagent.js is still ES5-compatible.
|
|
9
|
+
* `.end(…)` returns `undefined` instead of the request. If you need the request object after calling `.end()` (and you probably don't), save it in a variable and call `request.end(…)`. Consider not using `.end()` at all, and migrating to promises by calling `.then()` instead.
|
|
10
|
+
* In Node, responses with unknown MIME type are buffered by default. To get old behavior, if you use custom *unbuffered* parsers, add `.buffer(false)` to requests or set `superagent.buffer[yourMimeType] = false`.
|
|
11
|
+
* Invalid uses of `.pipe()` throw.
|
|
9
12
|
|
|
10
13
|
## Minor changes
|
|
11
14
|
|
|
@@ -16,6 +19,7 @@
|
|
|
16
19
|
* Leave backticks unencoded in query strings where possible (Ethan Resnick)
|
|
17
20
|
* Update node-mime to 2.x (Alexey Kucherenko)
|
|
18
21
|
* Allow default buffer settings based on response-type (shrey)
|
|
22
|
+
* `response.buffered` is more accurate.
|
|
19
23
|
|
|
20
24
|
# 3.8.3 (2018-04-29)
|
|
21
25
|
|
package/Makefile
CHANGED
|
@@ -11,6 +11,15 @@ test:
|
|
|
11
11
|
test-node:
|
|
12
12
|
@NODE_ENV=test NODE_TLS_REJECT_UNAUTHORIZED=0 ./node_modules/.bin/mocha \
|
|
13
13
|
--require should \
|
|
14
|
+
--trace-warnings \
|
|
15
|
+
--reporter $(REPORTER) \
|
|
16
|
+
--timeout 5000 \
|
|
17
|
+
$(NODETESTS)
|
|
18
|
+
|
|
19
|
+
test-node-http2:
|
|
20
|
+
@NODE_ENV=test HTTP2_TEST=1 NODE_TLS_REJECT_UNAUTHORIZED=0 node ./node_modules/.bin/mocha \
|
|
21
|
+
--require should \
|
|
22
|
+
--trace-warnings \
|
|
14
23
|
--reporter $(REPORTER) \
|
|
15
24
|
--timeout 5000 \
|
|
16
25
|
$(NODETESTS)
|
package/Readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://saucelabs.com/u/shtylman-superagent)
|
|
4
4
|
|
|
5
|
-
SuperAgent is a small progressive __client-side__ HTTP request library, and __Node.js__ module with the same API, sporting many high-level HTTP client features. View the [docs](
|
|
5
|
+
SuperAgent is a small progressive __client-side__ HTTP request library, and __Node.js__ module with the same API, sporting many high-level HTTP client features. View the [docs](https://visionmedia.github.io/superagent/).
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -63,10 +63,9 @@ Existing plugins:
|
|
|
63
63
|
* [superagent-mock](https://github.com/M6Web/superagent-mock) - simulate HTTP calls by returning data fixtures based on the requested URL
|
|
64
64
|
* [superagent-mocker](https://github.com/shuvalov-anton/superagent-mocker) — simulate REST API
|
|
65
65
|
* [superagent-cache](https://github.com/jpodwys/superagent-cache) - A global SuperAgent patch with built-in, flexible caching
|
|
66
|
-
|
|
66
|
+
* [superagent-cache-plugin](https://github.com/jpodwys/superagent-cache-plugin) - A SuperAgent plugin with built-in, flexible caching
|
|
67
67
|
* [superagent-jsonapify](https://github.com/alex94puchades/superagent-jsonapify) - A lightweight [json-api](http://jsonapi.org/format/) client addon for superagent
|
|
68
68
|
* [superagent-serializer](https://github.com/zzarcon/superagent-serializer) - Converts server payload into different cases
|
|
69
|
-
* [superagent-use](https://github.com/koenpunt/superagent-use) - A client addon to apply plugins to all requests.
|
|
70
69
|
* [superagent-httpbackend](https://www.npmjs.com/package/superagent-httpbackend) - stub out requests using AngularJS' $httpBackend syntax
|
|
71
70
|
* [superagent-throttle](https://github.com/leviwheatcroft/superagent-throttle) - queues and intelligently throttles requests
|
|
72
71
|
* [superagent-charset](https://github.com/magicdawn/superagent-charset) - add charset support for node's SuperAgent
|
|
@@ -80,15 +79,19 @@ For SuperAgent extensions such as couchdb and oauth visit the [wiki](https://git
|
|
|
80
79
|
|
|
81
80
|
Our breaking changes are mostly in rarely used functionality and from stricter error handling.
|
|
82
81
|
|
|
82
|
+
* [3.x to 4.x](https://github.com/visionmedia/superagent/releases/tag/v4.0.0-alpha.1):
|
|
83
|
+
- Ensure you're running Node 6 or later. We've dropped support for Node 4.
|
|
84
|
+
- We've started using ES6 and for compatibility with Internet Explorer you may need to use Babel.
|
|
85
|
+
- We suggest migrating from `.end()` callbacks to `.then()` or `await`.
|
|
83
86
|
* [2.x to 3.x](https://github.com/visionmedia/superagent/releases/tag/v3.0.0):
|
|
84
|
-
- Ensure you're running Node 4 or later. We dropped support for Node 0.x.
|
|
87
|
+
- Ensure you're running Node 4 or later. We've dropped support for Node 0.x.
|
|
85
88
|
- Test code that calls `.send()` multiple times. Invalid calls to `.send()` will now throw instead of sending garbage.
|
|
86
89
|
* [1.x to 2.x](https://github.com/visionmedia/superagent/releases/tag/v2.0.0):
|
|
87
90
|
- If you use `.parse()` in the *browser* version, rename it to `.serialize()`.
|
|
88
91
|
- If you rely on `undefined` in query-string values being sent literally as the text "undefined", switch to checking for missing value instead. `?key=undefined` is now `?key` (without a value).
|
|
89
92
|
- If you use `.then()` in Internet Explorer, ensure that you have a polyfill that adds a global `Promise` object.
|
|
90
93
|
* 0.x to 1.x:
|
|
91
|
-
-
|
|
94
|
+
- Instead of 1-argument callback `.end(function(res){})` use `.then(res => {})`.
|
|
92
95
|
|
|
93
96
|
## Running node tests
|
|
94
97
|
|
package/docs/index.md
CHANGED
|
@@ -8,13 +8,13 @@ SuperAgent is light-weight progressive ajax API crafted for flexibility, readabi
|
|
|
8
8
|
.send({ name: 'Manny', species: 'cat' })
|
|
9
9
|
.set('X-API-Key', 'foobar')
|
|
10
10
|
.set('Accept', 'application/json')
|
|
11
|
-
.then(
|
|
11
|
+
.then(res => {
|
|
12
12
|
alert('yay got ' + JSON.stringify(res.body));
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
## Test documentation
|
|
16
16
|
|
|
17
|
-
The following [test documentation](docs/test.html) was generated with [Mocha's](
|
|
17
|
+
The following [test documentation](docs/test.html) was generated with [Mocha's](https://mochajs.org/) "doc" reporter, and directly reflects the test suite. This provides an additional source of documentation.
|
|
18
18
|
|
|
19
19
|
## Request basics
|
|
20
20
|
|
|
@@ -22,10 +22,10 @@ A request can be initiated by invoking the appropriate method on the `request` o
|
|
|
22
22
|
|
|
23
23
|
request
|
|
24
24
|
.get('/search')
|
|
25
|
-
.then(
|
|
25
|
+
.then(res => {
|
|
26
26
|
// res.body, res.headers, res.status
|
|
27
27
|
})
|
|
28
|
-
.catch(
|
|
28
|
+
.catch(err => {
|
|
29
29
|
// err.message, err.response
|
|
30
30
|
});
|
|
31
31
|
|
|
@@ -33,7 +33,7 @@ HTTP method may also be passed as a string:
|
|
|
33
33
|
|
|
34
34
|
request('GET', '/search').then(success, failure);
|
|
35
35
|
|
|
36
|
-
Old-style callbacks are also supported. *Instead of* `.then()` you can call `.end()`:
|
|
36
|
+
Old-style callbacks are also supported, but not recommended. *Instead of* `.then()` you can call `.end()`:
|
|
37
37
|
|
|
38
38
|
request('GET', '/search').end(function(err, res){
|
|
39
39
|
if (res.ok) {}
|
|
@@ -42,12 +42,12 @@ Old-style callbacks are also supported. *Instead of* `.then()` you can call `.en
|
|
|
42
42
|
Absolute URLs can be used. In web browsers absolute URLs work only if the server implements [CORS](#cors).
|
|
43
43
|
|
|
44
44
|
request
|
|
45
|
-
.get('
|
|
46
|
-
.then(
|
|
45
|
+
.get('https://example.com/search')
|
|
46
|
+
.then(res => {
|
|
47
47
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
The __Node__ client supports making requests to [Unix Domain Sockets](
|
|
50
|
+
The __Node__ client supports making requests to [Unix Domain Sockets](https://en.wikipedia.org/wiki/Unix_domain_socket):
|
|
51
51
|
|
|
52
52
|
// pattern: https?+unix://SOCKET_PATH/REQUEST_PATH
|
|
53
53
|
// Use `%2F` as `/` in SOCKET_PATH
|
|
@@ -61,7 +61,7 @@ __DELETE__, __HEAD__, __PATCH__, __POST__, and __PUT__ requests can also be used
|
|
|
61
61
|
|
|
62
62
|
request
|
|
63
63
|
.head('/favicon.ico')
|
|
64
|
-
.then(
|
|
64
|
+
.then(res => {
|
|
65
65
|
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -69,7 +69,7 @@ __DELETE__ can be also called as `.del()` for compatibility with old IE where `d
|
|
|
69
69
|
|
|
70
70
|
The HTTP method defaults to __GET__, so if you wish, the following is valid:
|
|
71
71
|
|
|
72
|
-
request('/search',
|
|
72
|
+
request('/search', (err, res) => {
|
|
73
73
|
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -99,7 +99,7 @@ The `.query()` method accepts objects, which when used with the __GET__ method w
|
|
|
99
99
|
.query({ query: 'Manny' })
|
|
100
100
|
.query({ range: '1..5' })
|
|
101
101
|
.query({ order: 'desc' })
|
|
102
|
-
.then(
|
|
102
|
+
.then(res => {
|
|
103
103
|
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -108,7 +108,7 @@ Or as a single object:
|
|
|
108
108
|
request
|
|
109
109
|
.get('/search')
|
|
110
110
|
.query({ query: 'Manny', range: '1..5', order: 'desc' })
|
|
111
|
-
.then(
|
|
111
|
+
.then(res => {
|
|
112
112
|
|
|
113
113
|
});
|
|
114
114
|
|
|
@@ -117,7 +117,7 @@ The `.query()` method accepts strings as well:
|
|
|
117
117
|
request
|
|
118
118
|
.get('/querystring')
|
|
119
119
|
.query('search=Manny&range=1..5')
|
|
120
|
-
.then(
|
|
120
|
+
.then(res => {
|
|
121
121
|
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -127,7 +127,7 @@ Or joined:
|
|
|
127
127
|
.get('/querystring')
|
|
128
128
|
.query('search=Manny')
|
|
129
129
|
.query('range=1..5')
|
|
130
|
-
.then(
|
|
130
|
+
.then(res => {
|
|
131
131
|
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -138,7 +138,7 @@ You can also use the `.query()` method for HEAD requests. The following will pro
|
|
|
138
138
|
request
|
|
139
139
|
.head('/users')
|
|
140
140
|
.query({ email: 'joe@smith.com' })
|
|
141
|
-
.then(
|
|
141
|
+
.then(res => {
|
|
142
142
|
|
|
143
143
|
});
|
|
144
144
|
|
|
@@ -216,8 +216,8 @@ request.serialize['application/xml'] = function (obj) {
|
|
|
216
216
|
return 'string generated from obj';
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
//going forward, all requests with a Content-type of
|
|
220
|
-
//'application/xml' will be automatically serialized
|
|
219
|
+
// going forward, all requests with a Content-type of
|
|
220
|
+
// 'application/xml' will be automatically serialized
|
|
221
221
|
```
|
|
222
222
|
If you want to send the payload in a custom format, you can replace
|
|
223
223
|
the built-in serialization with the `.serialize()` method on a per-request basis:
|
|
@@ -226,7 +226,7 @@ the built-in serialization with the `.serialize()` method on a per-request basis
|
|
|
226
226
|
request
|
|
227
227
|
.post('/user')
|
|
228
228
|
.send({foo: 'bar'})
|
|
229
|
-
.serialize(
|
|
229
|
+
.serialize(obj => {
|
|
230
230
|
return 'string generated from obj';
|
|
231
231
|
});
|
|
232
232
|
```
|
|
@@ -237,7 +237,7 @@ When given the `.retry()` method, SuperAgent will automatically retry requests,
|
|
|
237
237
|
This method has two optional arguments: number of retries (default 3) and a callback. It calls `callback(err, res)` before each retry. The callback may return `true`/`false` to control whether the request sould be retried (but the maximum number of retries is always applied).
|
|
238
238
|
|
|
239
239
|
request
|
|
240
|
-
.get('
|
|
240
|
+
.get('https://example.com/search')
|
|
241
241
|
.retry(2) // or:
|
|
242
242
|
.retry(2, callback)
|
|
243
243
|
.then(finished);
|
|
@@ -286,9 +286,7 @@ By default the query string is not assembled in any particular order. An asciibe
|
|
|
286
286
|
request.get('/user')
|
|
287
287
|
.query('name=Nick')
|
|
288
288
|
.query('search=Manny')
|
|
289
|
-
.sortQuery(
|
|
290
|
-
return a.length - b.length;
|
|
291
|
-
})
|
|
289
|
+
.sortQuery((a, b) => a.length - b.length)
|
|
292
290
|
.then(callback)
|
|
293
291
|
```
|
|
294
292
|
|
|
@@ -383,7 +381,7 @@ In browsers, you may use `.responseType('blob')` to request handling of binary r
|
|
|
383
381
|
```js
|
|
384
382
|
req.get('/binary.data')
|
|
385
383
|
.responseType('blob')
|
|
386
|
-
.
|
|
384
|
+
.then(res => {
|
|
387
385
|
// res.body will be a browser native Blob type here
|
|
388
386
|
});
|
|
389
387
|
```
|
|
@@ -444,11 +442,11 @@ To abort requests simply invoke the `req.abort()` method.
|
|
|
444
442
|
|
|
445
443
|
Sometimes networks and servers get "stuck" and never respond after accepting a request. Set timeouts to avoid requests waiting forever.
|
|
446
444
|
|
|
447
|
-
* `req.timeout({deadline:ms})` or `req.timeout(ms)` (where `ms` is a number of milliseconds > 0) sets a deadline for the entire request (including all redirects) to complete. If the response isn't fully downloaded within that time, the request will be aborted.
|
|
445
|
+
* `req.timeout({deadline:ms})` or `req.timeout(ms)` (where `ms` is a number of milliseconds > 0) sets a deadline for the entire request (including all uploads, redirects, server processing time) to complete. If the response isn't fully downloaded within that time, the request will be aborted.
|
|
448
446
|
|
|
449
|
-
* `req.timeout({response:ms})` sets maximum time to wait for the first byte to arrive from the server, but it does not limit how long the entire download can take. Response timeout should be
|
|
447
|
+
* `req.timeout({response:ms})` sets maximum time to wait for the first byte to arrive from the server, but it does not limit how long the entire download can take. Response timeout should be at least few seconds longer than just the time it takes the server to respond, because it also includes time to make DNS lookup, TCP/IP and TLS connections, and time to upload request data.
|
|
450
448
|
|
|
451
|
-
You should use both `deadline` and `response` timeouts. This way you can use a short response timeout to detect unresponsive networks quickly, and a long deadline to give time for downloads on slow, but reliable, networks.
|
|
449
|
+
You should use both `deadline` and `response` timeouts. This way you can use a short response timeout to detect unresponsive networks quickly, and a long deadline to give time for downloads on slow, but reliable, networks. Note that both of these timers limit how long *uploads* of attached files are allowed to take. Use long timeouts if you're uploading files.
|
|
452
450
|
|
|
453
451
|
request
|
|
454
452
|
.get('/big-file?network=slow')
|
|
@@ -609,9 +607,9 @@ For security reasons, browsers will block cross-origin requests unless the serve
|
|
|
609
607
|
The `.withCredentials()` method enables the ability to send cookies from the origin, however only when `Access-Control-Allow-Origin` is _not_ a wildcard ("*"), and `Access-Control-Allow-Credentials` is "true".
|
|
610
608
|
|
|
611
609
|
request
|
|
612
|
-
.get('
|
|
610
|
+
.get('https://api.example.com:4001/')
|
|
613
611
|
.withCredentials()
|
|
614
|
-
.then(
|
|
612
|
+
.then(res => {
|
|
615
613
|
assert.equal(200, res.status);
|
|
616
614
|
assert.equal('tobi', res.text);
|
|
617
615
|
})
|
|
@@ -623,7 +621,7 @@ Your callback function will always be passed two arguments: error and response.
|
|
|
623
621
|
request
|
|
624
622
|
.post('/upload')
|
|
625
623
|
.attach('image', 'path/to/tobi.png')
|
|
626
|
-
.then(
|
|
624
|
+
.then(res => {
|
|
627
625
|
|
|
628
626
|
});
|
|
629
627
|
|
|
@@ -633,7 +631,7 @@ An "error" event is also emitted, with you can listen for:
|
|
|
633
631
|
.post('/upload')
|
|
634
632
|
.attach('image', 'path/to/tobi.png')
|
|
635
633
|
.on('error', handle)
|
|
636
|
-
.then(
|
|
634
|
+
.then(res => {
|
|
637
635
|
|
|
638
636
|
});
|
|
639
637
|
|
|
@@ -673,7 +671,7 @@ SuperAgent fires `progress` events on upload and download of large files.
|
|
|
673
671
|
loaded: // bytes downloaded or uploaded so far
|
|
674
672
|
} */
|
|
675
673
|
})
|
|
676
|
-
.
|
|
674
|
+
.then()
|
|
677
675
|
|
|
678
676
|
## Promise and Generator support
|
|
679
677
|
|
|
@@ -698,4 +696,4 @@ If want to use WebPack to compile code for Node.JS, you *must* specify [node tar
|
|
|
698
696
|
|
|
699
697
|
### Using browser version in electron
|
|
700
698
|
|
|
701
|
-
[Electron](
|
|
699
|
+
[Electron](https://electron.atom.io/) developers report if you would prefer to use the browser version of SuperAgent instead of the Node version, you can `require('superagent/superagent')`. Your requests will now show up in the Chrome developer tools Network tab. Note this environment is not covered by automated test suite and not officially supported.
|
package/docs/test.html
CHANGED
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
.post(uri + '/echo')
|
|
266
266
|
.send({ name: 'tobi' })
|
|
267
267
|
.end(function(err, res){
|
|
268
|
-
assert.
|
|
268
|
+
assert.ifError(err)
|
|
269
269
|
res.text.should.equal('{"name":"tobi"}');
|
|
270
270
|
done();
|
|
271
271
|
});</code></pre></dd>
|
|
@@ -662,7 +662,7 @@ request
|
|
|
662
662
|
.get('http://localhost:5000/custom')
|
|
663
663
|
.buffer()
|
|
664
664
|
.end(function(err, res){
|
|
665
|
-
assert.
|
|
665
|
+
assert.ifError(err)
|
|
666
666
|
assert.equal('custom stuff', res.text);
|
|
667
667
|
assert(res.buffered);
|
|
668
668
|
done();
|
|
@@ -679,7 +679,7 @@ request
|
|
|
679
679
|
.send('hello this is dog')
|
|
680
680
|
.buffer(false)
|
|
681
681
|
.end(function(err, res){
|
|
682
|
-
assert.
|
|
682
|
+
assert.ifError(err)
|
|
683
683
|
assert.equal(null, res.text);
|
|
684
684
|
res.body.should.eql({});
|
|
685
685
|
var buf = '';
|
|
@@ -734,7 +734,7 @@ done();</code></pre></dd>
|
|
|
734
734
|
.type('application/x-dog')
|
|
735
735
|
.send('hello this is dog')
|
|
736
736
|
.end(function(err, res){
|
|
737
|
-
assert.
|
|
737
|
+
assert.ifError(err)
|
|
738
738
|
assert.equal(null, res.text);
|
|
739
739
|
res.body.should.eql({});
|
|
740
740
|
var buf = '';
|
|
@@ -761,7 +761,7 @@ request
|
|
|
761
761
|
.send(img)
|
|
762
762
|
.buffer(false)
|
|
763
763
|
.end(function(err, res){
|
|
764
|
-
assert.
|
|
764
|
+
assert.ifError(err)
|
|
765
765
|
assert(!res.buffered);
|
|
766
766
|
assert.equal(res.header['content-length'], Buffer.byteLength(img));
|
|
767
767
|
done();
|
|
@@ -774,7 +774,7 @@ request
|
|
|
774
774
|
.send(img)
|
|
775
775
|
.buffer(true)
|
|
776
776
|
.end(function(err, res){
|
|
777
|
-
assert.
|
|
777
|
+
assert.ifError(err)
|
|
778
778
|
assert(res.buffered);
|
|
779
779
|
assert.equal(res.header['content-length'], img.length);
|
|
780
780
|
done();
|
package/dump.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/lib/client.js
CHANGED
|
@@ -673,7 +673,7 @@ Request.prototype.end = function(fn){
|
|
|
673
673
|
// querystring
|
|
674
674
|
this._finalizeQueryString();
|
|
675
675
|
|
|
676
|
-
|
|
676
|
+
this._end();
|
|
677
677
|
};
|
|
678
678
|
|
|
679
679
|
Request.prototype._end = function() {
|
|
@@ -772,7 +772,6 @@ Request.prototype._end = function() {
|
|
|
772
772
|
// IE11 xhr.send(undefined) sends 'undefined' string as POST payload (instead of nothing)
|
|
773
773
|
// We need null here if data is undefined
|
|
774
774
|
xhr.send(typeof data !== 'undefined' ? data : null);
|
|
775
|
-
return this;
|
|
776
775
|
};
|
|
777
776
|
|
|
778
777
|
request.agent = () => new Agent();
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http2 = require('http2');
|
|
4
|
+
const Stream = require('stream');
|
|
5
|
+
const util = require('util');
|
|
6
|
+
const net = require('net');
|
|
7
|
+
const tls = require('tls');
|
|
8
|
+
const parse = require('url').parse;
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
HTTP2_HEADER_PATH,
|
|
12
|
+
HTTP2_HEADER_STATUS,
|
|
13
|
+
HTTP2_HEADER_METHOD,
|
|
14
|
+
HTTP2_HEADER_AUTHORITY,
|
|
15
|
+
HTTP2_HEADER_HOST,
|
|
16
|
+
HTTP2_HEADER_SET_COOKIE,
|
|
17
|
+
NGHTTP2_CANCEL,
|
|
18
|
+
} = http2.constants;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function setProtocol(protocol) {
|
|
22
|
+
return {
|
|
23
|
+
request: function (options) {
|
|
24
|
+
return new Request(protocol, options);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Request(protocol, options) {
|
|
30
|
+
Stream.call(this);
|
|
31
|
+
const defaultPort = protocol === 'https:' ? 443 : 80;
|
|
32
|
+
const defaultHost = 'localhost'
|
|
33
|
+
const port = options.port || defaultPort;
|
|
34
|
+
const host = options.host || defaultHost;
|
|
35
|
+
|
|
36
|
+
delete options.port
|
|
37
|
+
delete options.host
|
|
38
|
+
|
|
39
|
+
this.method = options.method;
|
|
40
|
+
this.path = options.path;
|
|
41
|
+
this.protocol = protocol;
|
|
42
|
+
this.host = host;
|
|
43
|
+
|
|
44
|
+
delete options.method
|
|
45
|
+
delete options.path
|
|
46
|
+
|
|
47
|
+
const sessionOptions = Object.assign({}, options);
|
|
48
|
+
if (options.socketPath) {
|
|
49
|
+
sessionOptions.socketPath = options.socketPath;
|
|
50
|
+
sessionOptions.createConnection = this.createUnixConnection.bind(this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._headers = {};
|
|
54
|
+
|
|
55
|
+
const session = http2.connect(`${protocol}//${host}:${port}`, sessionOptions);
|
|
56
|
+
this.setHeader('host', `${host}:${port}`)
|
|
57
|
+
|
|
58
|
+
session.on('error', (err) => this.emit('error', err));
|
|
59
|
+
|
|
60
|
+
this.session = session;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Inherit from `Stream` (which inherits from `EventEmitter`).
|
|
65
|
+
*/
|
|
66
|
+
util.inherits(Request, Stream);
|
|
67
|
+
|
|
68
|
+
Request.prototype.createUnixConnection = function (authority, options) {
|
|
69
|
+
switch (this.protocol) {
|
|
70
|
+
case 'http:':
|
|
71
|
+
return net.connect(options.socketPath);
|
|
72
|
+
case 'https:':
|
|
73
|
+
options.ALPNProtocols = ['h2'];
|
|
74
|
+
options.servername = this.host;
|
|
75
|
+
options.allowHalfOpen = true;
|
|
76
|
+
return tls.connect(options.socketPath, options);
|
|
77
|
+
default:
|
|
78
|
+
throw new Error('Unsupported protocol', this.protocol);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Request.prototype.setNoDelay = function (bool) {
|
|
83
|
+
// We can not use setNoDelay with HTTP/2.
|
|
84
|
+
// Node 10 limits http2session.socket methods to ones safe to use with HTTP/2.
|
|
85
|
+
// See also https://nodejs.org/api/http2.html#http2_http2session_socket
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Request.prototype.getFrame = function () {
|
|
89
|
+
if (this.frame) {
|
|
90
|
+
return this.frame;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const method = {
|
|
94
|
+
[HTTP2_HEADER_PATH]: this.path,
|
|
95
|
+
[HTTP2_HEADER_METHOD]: this.method,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let headers = this.mapToHttp2Header(this._headers);
|
|
99
|
+
|
|
100
|
+
headers = Object.assign(headers, method);
|
|
101
|
+
|
|
102
|
+
const frame = this.session.request(headers);
|
|
103
|
+
frame.once('response', (headers, flags) => {
|
|
104
|
+
headers = this.mapToHttpHeader(headers);
|
|
105
|
+
frame.headers = headers;
|
|
106
|
+
frame.status = frame.statusCode = headers[HTTP2_HEADER_STATUS];
|
|
107
|
+
this.emit('response', frame);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this._headerSent = true;
|
|
111
|
+
|
|
112
|
+
frame.once('drain', () => this.emit('drain'));
|
|
113
|
+
frame.on('error', (err) => this.emit('error', err));
|
|
114
|
+
frame.on('close', () => this.session.close());
|
|
115
|
+
|
|
116
|
+
this.frame = frame;
|
|
117
|
+
return frame;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Request.prototype.mapToHttpHeader = function (headers) {
|
|
121
|
+
const keys = Object.keys(headers);
|
|
122
|
+
const http2Headers = {};
|
|
123
|
+
for (var i = 0; i < keys.length; i++) {
|
|
124
|
+
let key = keys[i];
|
|
125
|
+
let value = headers[key];
|
|
126
|
+
key = key.toLowerCase();
|
|
127
|
+
switch (key) {
|
|
128
|
+
case HTTP2_HEADER_SET_COOKIE:
|
|
129
|
+
value = Array.isArray(value) ? value : [value];
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
http2Headers[key] = value;
|
|
135
|
+
}
|
|
136
|
+
return http2Headers;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Request.prototype.mapToHttp2Header = function (headers) {
|
|
140
|
+
const keys = Object.keys(headers);
|
|
141
|
+
const http2Headers = {};
|
|
142
|
+
for (var i = 0; i < keys.length; i++) {
|
|
143
|
+
let key = keys[i];
|
|
144
|
+
let value = headers[key];
|
|
145
|
+
key = key.toLowerCase();
|
|
146
|
+
switch (key) {
|
|
147
|
+
case HTTP2_HEADER_HOST:
|
|
148
|
+
key = HTTP2_HEADER_AUTHORITY;
|
|
149
|
+
value = /^http\:\/\/|^https\:\/\//.test(value) ? parse(value).host : value;
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
http2Headers[key] = value;
|
|
155
|
+
}
|
|
156
|
+
return http2Headers;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Request.prototype.setHeader = function (name, value) {
|
|
160
|
+
this._headers[name.toLowerCase()] = value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Request.prototype.getHeader = function (name) {
|
|
164
|
+
return this._headers[name.toLowerCase()];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Request.prototype.write = function (data, encoding) {
|
|
168
|
+
const frame = this.getFrame();
|
|
169
|
+
return frame.write(data, encoding);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
Request.prototype.pipe = function (stream, options) {
|
|
173
|
+
const frame = this.getFrame();
|
|
174
|
+
return frame.pipe(stream, options);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
Request.prototype.end = function (data) {
|
|
178
|
+
const frame = this.getFrame();
|
|
179
|
+
frame.end(data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Request.prototype.abort = function (data) {
|
|
183
|
+
const frame = this.getFrame();
|
|
184
|
+
frame.close(NGHTTP2_CANCEL);
|
|
185
|
+
this.session.destroy();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exports.setProtocol = setProtocol;
|