const zlib = require('zlib')
const http = require('http')
const https = require('https')
const parse = require('url').parse
const translate = host => (global.hosts || {})[host] || host
const create = (url, proxy) =>
(((typeof proxy === 'undefined' ? global.proxy : proxy) || url).protocol ===
'https:'
? https
: http
).request
const configure = (method, url, headers, proxy) => {
headers = headers || {}
proxy = typeof proxy === 'undefined' ? global.proxy : proxy
if ('content-length' in headers) delete headers['content-length']
const options = {}
options._headers = headers
if (proxy && url.protocol === 'https:') {
options.method = 'CONNECT'
options.headers = Object.keys(headers).reduce(
(result, key) =>
Object.assign(
result,
['host', 'user-agent'].includes(key) && { [key]: headers[key] }
),
{}
)
} else {
options.method = method
options.headers = headers
}
if (proxy) {
options.hostname = translate(proxy.hostname)
options.port = proxy.port || (proxy.protocol === 'https:' ? 443 : 80)
options.path =
url.protocol === 'https:'
? translate(url.hostname) + ':' + (url.port || 443)
: 'http://' + translate(url.hostname) + url.path
} else {
options.hostname = translate(url.hostname)
options.port = url.port || (url.protocol === 'https:' ? 443 : 80)
options.path = url.path
}
return options
}
const request = (method, url, headers, body, proxy) => {
url = parse(url)
headers = headers || {}
const options = configure(
method,
url,
Object.assign(
{
host: url.hostname,
accept: 'application/json, text/plain, */*',
'accept-encoding': 'gzip, deflate',
'accept-language': 'zh-CN,zh;q=0.9',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
},
headers
),
proxy
)
return new Promise((resolve, reject) => {
create(
url,
proxy
)(options)
.on('response', response => resolve(response))
.on('connect', (_, socket) =>
https
.request({
method: method,
path: url.path,
headers: options._headers,
socket: socket,
agent: false
})
.on('response', response => resolve(response))
.on('error', error => reject(error))
.end(body)
)
.on('error', error => reject(error))
.end(options.method.toUpperCase() === 'CONNECT' ? undefined : body)
}).then(response => {
if (new Set([201, 301, 302, 303, 307, 308]).has(response.statusCode))
return request(
method,
url.resolve(response.headers.location || url.href),
(delete headers.host, headers),
body,
proxy
)
else
return Object.assign(response, {
url: url,
body: raw => read(response, raw),
json: () => json(response),
jsonp: () => jsonp(response)
})
})
}
const read = (connect, raw) =>
new Promise((resolve, reject) => {
const chunks = []
connect
.on('data', chunk => chunks.push(chunk))
.on('end', () => resolve(Buffer.concat(chunks)))
.on('error', error => reject(error))
}).then(buffer => {
buffer =
buffer.length &&
['gzip', 'deflate'].includes(connect.headers['content-encoding'])
? zlib.unzipSync(buffer)
: buffer
return raw ? buffer : buffer.toString()
})
const json = connect => read(connect, false).then(body => JSON.parse(body))
const jsonp = connect =>
read(connect, false).then(body =>
JSON.parse(body.slice(body.indexOf('(') + 1, -')'.length))
)
request.read = read
request.create = create
request.translate = translate
request.configure = configure
module.exports = request