@datanimbus/postman-request 3.0.1
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/LICENSE +55 -0
- package/README.md +1315 -0
- package/index.js +166 -0
- package/lib/auth.js +167 -0
- package/lib/autohttp/agent.js +206 -0
- package/lib/autohttp/headerValidations.js +40 -0
- package/lib/autohttp/index.js +8 -0
- package/lib/autohttp/request.js +192 -0
- package/lib/autohttp/requestName.js +86 -0
- package/lib/cookies.js +20 -0
- package/lib/getProxyFromURI.js +79 -0
- package/lib/har.js +200 -0
- package/lib/hawk.js +89 -0
- package/lib/helpers.js +91 -0
- package/lib/http2/agent.js +92 -0
- package/lib/http2/index.js +8 -0
- package/lib/http2/request.js +369 -0
- package/lib/inflate.js +64 -0
- package/lib/multipart.js +112 -0
- package/lib/oauth.js +147 -0
- package/lib/querystring.js +50 -0
- package/lib/redirect.js +218 -0
- package/lib/socks.js +45 -0
- package/lib/tunnel.js +175 -0
- package/lib/url-parse.js +187 -0
- package/package.json +77 -0
- package/request.js +2098 -0
package/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Copyright 2010-2012 Mikeal Rogers
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
var extend = require('extend')
|
|
18
|
+
var cookies = require('./lib/cookies')
|
|
19
|
+
|
|
20
|
+
// organize params for patch, post, put, head, del
|
|
21
|
+
function initParams (uri, options, callback) {
|
|
22
|
+
if (typeof options === 'function') {
|
|
23
|
+
callback = options
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var params = {protocolVersion: 'http1'}
|
|
27
|
+
|
|
28
|
+
if (options !== null && typeof options === 'object') {
|
|
29
|
+
extend(params, options, {uri: uri})
|
|
30
|
+
} else if (typeof uri === 'string') {
|
|
31
|
+
extend(params, {uri: uri})
|
|
32
|
+
} else {
|
|
33
|
+
extend(params, uri)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
params.callback = callback || params.callback
|
|
37
|
+
|
|
38
|
+
// Disable http/2 when using custom agents that don't handle different versions separately
|
|
39
|
+
if (params.agents && !(params.agents.http1 || params.agents.auto || params.agents.http2)) {
|
|
40
|
+
params.protocolVersion = 'http1'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Disable http/2 when using proxy or tunnels
|
|
44
|
+
// TODO: Remove this when http2 supports proxy and tunneling
|
|
45
|
+
if (params.tunnel || params.proxy) {
|
|
46
|
+
params.protocolVersion = 'http1'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Disable flow when running in browser
|
|
50
|
+
if (typeof window !== 'undefined' && window.XMLHttpRequest) {
|
|
51
|
+
params.protocolVersion = 'http1'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return params
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function request (uri, options, callback) {
|
|
58
|
+
if (typeof uri === 'undefined') {
|
|
59
|
+
throw new Error('undefined is not a valid uri or options object.')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var params = initParams(uri, options, callback)
|
|
63
|
+
|
|
64
|
+
return new request.Request(params)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function verbFunc (verb) {
|
|
68
|
+
var method = verb.toUpperCase()
|
|
69
|
+
return function (uri, options, callback) {
|
|
70
|
+
var params = initParams(uri, options, callback)
|
|
71
|
+
params.method = method
|
|
72
|
+
return request(params, params.callback)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// define like this to please codeintel/intellisense IDEs
|
|
77
|
+
request.get = verbFunc('get')
|
|
78
|
+
request.head = verbFunc('head')
|
|
79
|
+
request.options = verbFunc('options')
|
|
80
|
+
request.post = verbFunc('post')
|
|
81
|
+
request.put = verbFunc('put')
|
|
82
|
+
request.patch = verbFunc('patch')
|
|
83
|
+
request.del = verbFunc('delete')
|
|
84
|
+
request['delete'] = verbFunc('delete')
|
|
85
|
+
|
|
86
|
+
request.jar = function (store) {
|
|
87
|
+
return cookies.jar(store)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
request.cookie = function (str) {
|
|
91
|
+
return cookies.parse(str)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wrapRequestMethod (method, options, requester, verb) {
|
|
95
|
+
return function (uri, opts, callback) {
|
|
96
|
+
var params = initParams(uri, opts, callback)
|
|
97
|
+
|
|
98
|
+
var target = {}
|
|
99
|
+
extend(true, target, options, params)
|
|
100
|
+
|
|
101
|
+
target.pool = params.pool || options.pool
|
|
102
|
+
|
|
103
|
+
if (verb) {
|
|
104
|
+
target.method = verb.toUpperCase()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (typeof requester === 'function') {
|
|
108
|
+
method = requester
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return method(target, target.callback)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
request.defaults = function (options, requester) {
|
|
116
|
+
var self = this
|
|
117
|
+
|
|
118
|
+
options = options || {}
|
|
119
|
+
|
|
120
|
+
if (typeof options === 'function') {
|
|
121
|
+
requester = options
|
|
122
|
+
options = {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
var defaults = wrapRequestMethod(self, options, requester)
|
|
126
|
+
|
|
127
|
+
var verbs = ['get', 'head', 'post', 'put', 'patch', 'del', 'delete']
|
|
128
|
+
verbs.forEach(function (verb) {
|
|
129
|
+
defaults[verb] = wrapRequestMethod(self[verb], options, requester, verb)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
defaults.cookie = wrapRequestMethod(self.cookie, options, requester)
|
|
133
|
+
defaults.jar = self.jar
|
|
134
|
+
defaults.defaults = self.defaults
|
|
135
|
+
return defaults
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
request.forever = function (agentOptions, optionsArg) {
|
|
139
|
+
var options = {}
|
|
140
|
+
if (optionsArg) {
|
|
141
|
+
extend(options, optionsArg)
|
|
142
|
+
}
|
|
143
|
+
if (agentOptions) {
|
|
144
|
+
options.agentOptions = agentOptions
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
options.forever = true
|
|
148
|
+
return request.defaults(options)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Exports
|
|
152
|
+
|
|
153
|
+
module.exports = request
|
|
154
|
+
request.Request = require('./request')
|
|
155
|
+
request.initParams = initParams
|
|
156
|
+
|
|
157
|
+
// Backwards compatibility for request.debug
|
|
158
|
+
Object.defineProperty(request, 'debug', {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function () {
|
|
161
|
+
return request.Request.debug
|
|
162
|
+
},
|
|
163
|
+
set: function (debug) {
|
|
164
|
+
request.Request.debug = debug
|
|
165
|
+
}
|
|
166
|
+
})
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var caseless = require('caseless')
|
|
4
|
+
var uuid = require('uuid').v4
|
|
5
|
+
var helpers = require('./helpers')
|
|
6
|
+
|
|
7
|
+
var md5 = helpers.md5
|
|
8
|
+
var toBase64 = helpers.toBase64
|
|
9
|
+
|
|
10
|
+
function Auth (request) {
|
|
11
|
+
// define all public properties here
|
|
12
|
+
this.request = request
|
|
13
|
+
this.hasAuth = false
|
|
14
|
+
this.sentAuth = false
|
|
15
|
+
this.bearerToken = null
|
|
16
|
+
this.user = null
|
|
17
|
+
this.pass = null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Auth.prototype.basic = function (user, pass, sendImmediately) {
|
|
21
|
+
var self = this
|
|
22
|
+
if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
|
|
23
|
+
self.request.emit('error', new Error('auth() received invalid user or password'))
|
|
24
|
+
}
|
|
25
|
+
self.user = user
|
|
26
|
+
self.pass = pass
|
|
27
|
+
self.hasAuth = true
|
|
28
|
+
var header = user + ':' + (pass || '')
|
|
29
|
+
if (sendImmediately || typeof sendImmediately === 'undefined') {
|
|
30
|
+
var authHeader = 'Basic ' + toBase64(header)
|
|
31
|
+
self.sentAuth = true
|
|
32
|
+
return authHeader
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Auth.prototype.bearer = function (bearer, sendImmediately) {
|
|
37
|
+
var self = this
|
|
38
|
+
self.bearerToken = bearer
|
|
39
|
+
self.hasAuth = true
|
|
40
|
+
if (sendImmediately || typeof sendImmediately === 'undefined') {
|
|
41
|
+
if (typeof bearer === 'function') {
|
|
42
|
+
bearer = bearer()
|
|
43
|
+
}
|
|
44
|
+
var authHeader = 'Bearer ' + (bearer || '')
|
|
45
|
+
self.sentAuth = true
|
|
46
|
+
return authHeader
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Auth.prototype.digest = function (method, path, authHeader) {
|
|
51
|
+
// TODO: More complete implementation of RFC 2617.
|
|
52
|
+
// - handle challenge.domain
|
|
53
|
+
// - support qop="auth-int" only
|
|
54
|
+
// - handle Authentication-Info (not necessarily?)
|
|
55
|
+
// - check challenge.stale (not necessarily?)
|
|
56
|
+
// - increase nc (not necessarily?)
|
|
57
|
+
// For reference:
|
|
58
|
+
// http://tools.ietf.org/html/rfc2617#section-3
|
|
59
|
+
// https://github.com/bagder/curl/blob/master/lib/http_digest.c
|
|
60
|
+
|
|
61
|
+
var self = this
|
|
62
|
+
|
|
63
|
+
var challenge = {}
|
|
64
|
+
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
|
65
|
+
while (true) {
|
|
66
|
+
var match = re.exec(authHeader)
|
|
67
|
+
if (!match) {
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
challenge[match[1]] = match[2] || match[3]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
|
75
|
+
*
|
|
76
|
+
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
|
77
|
+
* HA1=MD5(username:realm:password)
|
|
78
|
+
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
|
79
|
+
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
|
80
|
+
*/
|
|
81
|
+
var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) {
|
|
82
|
+
var ha1 = md5(user + ':' + realm + ':' + pass)
|
|
83
|
+
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
|
84
|
+
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
|
85
|
+
} else {
|
|
86
|
+
return ha1
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
|
91
|
+
var nc = qop && '00000001'
|
|
92
|
+
var cnonce = qop && uuid().replace(/-/g, '')
|
|
93
|
+
var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce)
|
|
94
|
+
var ha2 = md5(method + ':' + path)
|
|
95
|
+
var digestResponse = qop
|
|
96
|
+
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
|
97
|
+
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
|
98
|
+
var authValues = {
|
|
99
|
+
username: self.user,
|
|
100
|
+
realm: challenge.realm,
|
|
101
|
+
nonce: challenge.nonce,
|
|
102
|
+
uri: path,
|
|
103
|
+
qop: qop,
|
|
104
|
+
response: digestResponse,
|
|
105
|
+
nc: nc,
|
|
106
|
+
cnonce: cnonce,
|
|
107
|
+
algorithm: challenge.algorithm,
|
|
108
|
+
opaque: challenge.opaque
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
authHeader = []
|
|
112
|
+
for (var k in authValues) {
|
|
113
|
+
if (authValues[k]) {
|
|
114
|
+
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
|
|
115
|
+
authHeader.push(k + '=' + authValues[k])
|
|
116
|
+
} else {
|
|
117
|
+
authHeader.push(k + '="' + authValues[k] + '"')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
authHeader = 'Digest ' + authHeader.join(', ')
|
|
122
|
+
self.sentAuth = true
|
|
123
|
+
return authHeader
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
|
|
127
|
+
var self = this
|
|
128
|
+
var request = self.request
|
|
129
|
+
|
|
130
|
+
var authHeader
|
|
131
|
+
if (bearer === undefined && user === undefined) {
|
|
132
|
+
self.request.emit('error', new Error('no auth mechanism defined'))
|
|
133
|
+
} else if (bearer !== undefined) {
|
|
134
|
+
authHeader = self.bearer(bearer, sendImmediately)
|
|
135
|
+
} else {
|
|
136
|
+
authHeader = self.basic(user, pass, sendImmediately)
|
|
137
|
+
}
|
|
138
|
+
if (authHeader) {
|
|
139
|
+
request.setHeader('Authorization', authHeader)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Auth.prototype.onResponse = function (response) {
|
|
144
|
+
var self = this
|
|
145
|
+
var request = self.request
|
|
146
|
+
|
|
147
|
+
if (!self.hasAuth || self.sentAuth) { return null }
|
|
148
|
+
|
|
149
|
+
var c = caseless(response.headers)
|
|
150
|
+
|
|
151
|
+
var authHeader = c.get('www-authenticate')
|
|
152
|
+
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
|
|
153
|
+
request.debug('reauth', authVerb)
|
|
154
|
+
|
|
155
|
+
switch (authVerb) {
|
|
156
|
+
case 'basic':
|
|
157
|
+
return self.basic(self.user, self.pass, true)
|
|
158
|
+
|
|
159
|
+
case 'bearer':
|
|
160
|
+
return self.bearer(self.bearerToken, true)
|
|
161
|
+
|
|
162
|
+
case 'digest':
|
|
163
|
+
return self.digest(request.method, request.path, authHeader)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
exports.Auth = Auth
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const { Agent: Http2Agent } = require('../http2')
|
|
2
|
+
const https = require('https')
|
|
3
|
+
const tls = require('tls')
|
|
4
|
+
const { EventEmitter } = require('events')
|
|
5
|
+
const net = require('net')
|
|
6
|
+
const { getName: getSocketName } = require('../autohttp/requestName')
|
|
7
|
+
|
|
8
|
+
// All valid options defined at https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
|
|
9
|
+
const supportedProtocols = ['h2', 'http/1.1', 'http/1.0', 'http/0.9']
|
|
10
|
+
|
|
11
|
+
// Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/_http_agent.js#L350
|
|
12
|
+
function calculateServerName (options) {
|
|
13
|
+
let servername = options.host || ''
|
|
14
|
+
const hostHeader = options.headers && options.headers.host
|
|
15
|
+
|
|
16
|
+
if (hostHeader) {
|
|
17
|
+
if (typeof hostHeader !== 'string') {
|
|
18
|
+
throw new TypeError(
|
|
19
|
+
'host header content must be a string, received' + hostHeader
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// abc => abc
|
|
24
|
+
// abc:123 => abc
|
|
25
|
+
// [::1] => ::1
|
|
26
|
+
// [::1]:123 => ::1
|
|
27
|
+
if (hostHeader.startsWith('[')) {
|
|
28
|
+
const index = hostHeader.indexOf(']')
|
|
29
|
+
if (index === -1) {
|
|
30
|
+
// Leading '[', but no ']'. Need to do something...
|
|
31
|
+
servername = hostHeader
|
|
32
|
+
} else {
|
|
33
|
+
servername = hostHeader.substring(1, index)
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
servername = hostHeader.split(':', 1)[0]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Don't implicitly set invalid (IP) servernames.
|
|
40
|
+
if (net.isIP(servername)) servername = ''
|
|
41
|
+
return servername
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class AutoHttp2Agent extends EventEmitter {
|
|
45
|
+
constructor (options) {
|
|
46
|
+
super()
|
|
47
|
+
this.http2Agent = new Http2Agent(options)
|
|
48
|
+
this.httpsAgent = new https.Agent(options)
|
|
49
|
+
this.ALPNCache = new Map()
|
|
50
|
+
this.options = options
|
|
51
|
+
this.defaultPort = 443
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
createConnection (
|
|
55
|
+
req,
|
|
56
|
+
reqOptions,
|
|
57
|
+
cb,
|
|
58
|
+
socketCb
|
|
59
|
+
) {
|
|
60
|
+
const options = {
|
|
61
|
+
...reqOptions,
|
|
62
|
+
...this.options,
|
|
63
|
+
port: Number(reqOptions.port || this.options.port || this.defaultPort),
|
|
64
|
+
host: reqOptions.hostname || reqOptions.host || 'localhost'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// check if ALPN is cached
|
|
68
|
+
const name = getSocketName(options)
|
|
69
|
+
const [protocol, cachedSocket] = this.ALPNCache.get(name) || []
|
|
70
|
+
|
|
71
|
+
if (!protocol || !cachedSocket || cachedSocket.closed || cachedSocket.destroyed) {
|
|
72
|
+
// No cache exists or the initial socket used to establish the connection has been closed. Perform ALPN again.
|
|
73
|
+
this.ALPNCache.delete(name)
|
|
74
|
+
this.createNewSocketConnection(req, options, cb, socketCb)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// No need to pass the cachedSocket since the respective protocol's agents will reuse the socket that was initially
|
|
79
|
+
// passed during ALPN Negotiation
|
|
80
|
+
if (protocol === 'h2') {
|
|
81
|
+
const http2Options = {
|
|
82
|
+
...options,
|
|
83
|
+
path: options.socketPath
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let connection
|
|
87
|
+
try {
|
|
88
|
+
const uri = options.uri
|
|
89
|
+
connection = this.http2Agent.createConnection(req, uri, http2Options)
|
|
90
|
+
} catch (e) {
|
|
91
|
+
cb(e)
|
|
92
|
+
connection && connection.socket && socketCb(connection.socket)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cb(null, 'http2', connection)
|
|
97
|
+
socketCb(connection.socket)
|
|
98
|
+
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const http1RequestOptions = {
|
|
103
|
+
...options,
|
|
104
|
+
agent: this.httpsAgent
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let request
|
|
108
|
+
try {
|
|
109
|
+
request = https.request(http1RequestOptions)
|
|
110
|
+
} catch (e) {
|
|
111
|
+
cb(e)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
request.on('socket', (socket) => socketCb(socket))
|
|
116
|
+
cb(null, 'http1', request)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
createNewSocketConnection (req, options, cb, socketCb) {
|
|
120
|
+
const uri = options.uri
|
|
121
|
+
const name = getSocketName(options)
|
|
122
|
+
|
|
123
|
+
const socket = tls.connect({
|
|
124
|
+
...options,
|
|
125
|
+
path: options.socketPath,
|
|
126
|
+
ALPNProtocols: supportedProtocols,
|
|
127
|
+
servername: options.servername || calculateServerName(options)
|
|
128
|
+
})
|
|
129
|
+
socketCb(socket)
|
|
130
|
+
|
|
131
|
+
const socketConnectionErrorHandler = (e) => {
|
|
132
|
+
cb(e)
|
|
133
|
+
}
|
|
134
|
+
socket.on('error', socketConnectionErrorHandler)
|
|
135
|
+
|
|
136
|
+
socket.once('secureConnect', () => {
|
|
137
|
+
socket.removeListener('error', socketConnectionErrorHandler)
|
|
138
|
+
|
|
139
|
+
const protocol = socket.alpnProtocol || 'http/1.1'
|
|
140
|
+
|
|
141
|
+
if (!supportedProtocols.includes(protocol)) {
|
|
142
|
+
cb(new Error('Unknown protocol' + protocol))
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Update the cache
|
|
147
|
+
this.ALPNCache.set(name, [protocol, socket])
|
|
148
|
+
|
|
149
|
+
socket.once('close', () => {
|
|
150
|
+
// Clean the cache when the socket closes
|
|
151
|
+
this.ALPNCache.delete(name)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (protocol === 'h2') {
|
|
155
|
+
const http2Options = {
|
|
156
|
+
...options,
|
|
157
|
+
path: options.socketPath
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const connection = this.http2Agent.createConnection(
|
|
161
|
+
req,
|
|
162
|
+
uri,
|
|
163
|
+
http2Options,
|
|
164
|
+
socket
|
|
165
|
+
)
|
|
166
|
+
cb(null, 'http2', connection)
|
|
167
|
+
} catch (e) {
|
|
168
|
+
cb(e)
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Protocol is http1, using the built in agent
|
|
174
|
+
// We need to release all free sockets so that new connection is created using the overridden createconnection
|
|
175
|
+
// forcing the agent to reuse the socket used for alpn
|
|
176
|
+
|
|
177
|
+
// This reassignment works, since all code so far is sync, and happens in the same tick, hence there will be no
|
|
178
|
+
// race conditions
|
|
179
|
+
const oldCreateConnection = this.httpsAgent.createConnection
|
|
180
|
+
|
|
181
|
+
this.httpsAgent.createConnection = () => {
|
|
182
|
+
return socket
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const http1RequestOptions = {
|
|
186
|
+
...options,
|
|
187
|
+
agent: this.httpsAgent
|
|
188
|
+
}
|
|
189
|
+
let request
|
|
190
|
+
try {
|
|
191
|
+
request = https.request(http1RequestOptions)
|
|
192
|
+
} catch (e) {
|
|
193
|
+
cb(e)
|
|
194
|
+
return
|
|
195
|
+
} finally {
|
|
196
|
+
this.httpsAgent.createConnection = oldCreateConnection
|
|
197
|
+
}
|
|
198
|
+
cb(null, 'http1', request)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
AutoHttp2Agent,
|
|
205
|
+
globalAgent: new AutoHttp2Agent({})
|
|
206
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const {constants = {}} = require('http2')
|
|
2
|
+
|
|
3
|
+
// Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/util.js#L107
|
|
4
|
+
const kValidPseudoHeaders = new Set([
|
|
5
|
+
constants.HTTP2_HEADER_STATUS,
|
|
6
|
+
constants.HTTP2_HEADER_METHOD,
|
|
7
|
+
constants.HTTP2_HEADER_AUTHORITY,
|
|
8
|
+
constants.HTTP2_HEADER_SCHEME,
|
|
9
|
+
constants.HTTP2_HEADER_PATH
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
// Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/util.js#L573
|
|
13
|
+
function assertValidPseudoHeader (header) {
|
|
14
|
+
if (!kValidPseudoHeaders.has(header)) {
|
|
15
|
+
throw new Error('Invalid PseudoHeader ' + header)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/_http_common.js#L206
|
|
20
|
+
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
|
|
21
|
+
function checkIsHttpToken (token) {
|
|
22
|
+
return RegExp(tokenRegExp).exec(token) !== null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/core.js#L1763
|
|
26
|
+
function validateRequestHeaders (headers) {
|
|
27
|
+
if (headers !== null && headers !== undefined) {
|
|
28
|
+
const keys = Object.keys(headers)
|
|
29
|
+
for (let i = 0; i < keys.length; i++) {
|
|
30
|
+
const header = keys[i]
|
|
31
|
+
if (header[0] === ':') {
|
|
32
|
+
assertValidPseudoHeader(header)
|
|
33
|
+
} else if (header && !checkIsHttpToken(header)) { throw new Error('Invalid HTTP Token: Header name' + header) }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
validateRequestHeaders
|
|
40
|
+
}
|