UNPKG

9.96 kBJavaScriptView Raw
1var http = require('http')
2var https = require('https')
3var urlUtils = require('url')
4var _ = require('underscore')
5var merge = require('./merge')
6var querystringLite = require('./querystring-lite')
7var utils = require('./middlewareUtils')
8var createDebug = require('debug')
9var debug = createDebug('httpism')
10var debugResponse = createDebug('httpism:response')
11var debugRequest = createDebug('httpism:request')
12var HttpsProxyAgent = require('https-proxy-agent')
13var obfuscateUrlPassword = require('./obfuscateUrlPassword')
14var mimeTypes = require('mime-types')
15var proxyForUrl = require('proxy-from-env').getProxyForUrl
16
17function middleware (name, fn) {
18 exports[name] = fn
19 fn.httpismMiddleware = {
20 name: name
21 }
22}
23
24middleware('exception', utils.exception)
25middleware('querystring', utils.querystring)
26middleware('expandUrl', utils.expandUrl)
27
28exports.streamToString = function (s) {
29 return new Promise(function (resolve, reject) {
30 s.setEncoding('utf-8')
31 var strings = []
32
33 s.on('data', function (d) {
34 strings.push(d)
35 })
36
37 s.on('end', function () {
38 resolve(strings.join(''))
39 })
40
41 s.on('error', function (e) {
42 reject(e)
43 })
44 })
45}
46
47exports.consumeStream = function (s) {
48 return new Promise(function (resolve, reject) {
49 s.on('end', function () {
50 resolve()
51 })
52
53 s.on('error', function (e) {
54 reject(e)
55 })
56
57 s.resume()
58 })
59}
60
61function isStream (body) {
62 return body !== undefined && typeof body.pipe === 'function'
63}
64
65middleware('json', function (request, next) {
66 if (request.body instanceof Object && !isStream(request.body)) {
67 setBodyToString(request, JSON.stringify(request.body))
68 utils.setHeaderTo(request, 'content-type', 'application/json')
69 }
70
71 utils.setHeaderTo(request, 'accept', 'application/json')
72
73 return next().then(function (response) {
74 if (utils.shouldParseAs(response, 'json', request)) {
75 return exports.streamToString(response.body).then(function (jsonString) {
76 response.body = JSON.parse(jsonString, request.options.jsonReviver)
77 return response
78 })
79 } else {
80 return response
81 }
82 })
83})
84
85function setBodyToString (r, s) {
86 r.body = stringToStream(s)
87 r.headers['content-length'] = Buffer.byteLength(s, 'utf-8')
88 r.stringBody = s
89}
90
91function stringToStream (s) {
92 return {
93 pipe: function (stream) {
94 stream.write(s)
95 stream.end()
96 }
97 }
98}
99
100exports.stringToStream = stringToStream
101
102function nodeRequest (request, options, protocol, withResponse) {
103 if (protocol === 'https:') {
104 return https.request(merge(request, options.https), withResponse)
105 } else {
106 return http.request(merge(request, options.http), withResponse)
107 }
108}
109
110function proxyUrl (request, proxy) {
111 var url = urlUtils.parse(request.url)
112 var proxyUrl = urlUtils.parse(proxy)
113
114 request.headers.host = url.hostname
115
116 if (url.protocol === 'https:') {
117 url.agent = new HttpsProxyAgent(proxy)
118 return url
119 } else {
120 if (proxyUrl.auth) {
121 request.headers['proxy-authorization'] = encodeBasicAuthorizationHeader(proxyUrl.auth)
122 }
123
124 return {
125 hostname: proxyUrl.hostname,
126 port: proxyUrl.port,
127 path: request.url
128 }
129 }
130}
131
132function parseUrl (request) {
133 var proxy = proxyForUrl(request.url) || request.options.proxy
134
135 if (proxy) {
136 return proxyUrl(request, proxy)
137 } else {
138 return urlUtils.parse(request.url)
139 }
140}
141
142middleware('http', function (request) {
143 return new Promise(function (resolve, reject) {
144 var url = parseUrl(request)
145
146 var req = nodeRequest(
147 {
148 hostname: url.hostname,
149 port: url.port,
150 method: request.method,
151 path: url.path,
152 headers: request.headers,
153 agent: url.agent
154 },
155 request.options,
156 url.protocol,
157 function (res) {
158 return resolve({
159 statusCode: res.statusCode,
160 statusText: http.STATUS_CODES[res.statusCode],
161 url: request.url,
162 headers: res.headers,
163 body: res
164 })
165 }
166 )
167
168 req.on('error', function (e) {
169 reject(e)
170 })
171
172 if (request.body) {
173 request.body.pipe(req)
174 } else {
175 req.end()
176 }
177 })
178})
179
180function removeUndefined (obj) {
181 Object.keys(obj).map(function (key) {
182 if (typeof obj[key] === 'undefined') {
183 delete obj[key]
184 }
185 })
186
187 return obj
188}
189
190function prepareForLogging (r) {
191 return removeUndefined({
192 method: r.method,
193 url: r.url && obfuscateUrlPassword(r.url),
194 headers: r.headers,
195 body: isStream(r.body) ? '[Stream]' : r.body,
196 statusCode: r.statusCode,
197 statusText: r.statusText
198 })
199}
200
201function logRequest (request) {
202 if (debugRequest.enabled) {
203 debugRequest(prepareForLogging(request))
204 }
205}
206
207middleware('log', function (request, next) {
208 logRequest(request)
209
210 var promise = next()
211
212 if (debugResponse.enabled) {
213 return promise.then(function (response) {
214 logResponse(response)
215 return response
216 }, function (e) {
217 var res = _.extend({}, e)
218 logResponse(res)
219 throw e
220 })
221 } else {
222 return promise
223 }
224})
225
226middleware('debugLog', function (request, next) {
227 if (debug.enabled) {
228 var startTime = Date.now()
229 return next().then(function (response) {
230 var headerTime = Date.now() - startTime
231 response.body.on('end', function () {
232 var bodyTime = Date.now() - startTime
233 debug(request.method.toUpperCase() + ' ' + obfuscateUrlPassword(request.url) + ' => ' + response.statusCode + ' (' + headerTime + 'ms, ' + bodyTime + 'ms)')
234 })
235 return response
236 }, function (error) {
237 var headerTime = Date.now() - startTime
238 debug(request.method.toUpperCase() + ' ' + obfuscateUrlPassword(request.url) + ' => ' + error.message + ' (' + headerTime + 'ms)')
239 throw error
240 })
241 } else {
242 return next()
243 }
244})
245
246function logResponse (response) {
247 if (!response.redirectResponse) {
248 debugResponse(prepareForLogging(response))
249 }
250}
251
252middleware('redirect', function (request, next, client) {
253 return next().then(function (response) {
254 var statusCode = response.statusCode
255 var location = response.headers.location
256
257 if (request.options.redirect !== false && location && (statusCode === 300 || statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307)) {
258 return exports.consumeStream(response.body).then(function () {
259 logResponse(response)
260 return client.get(urlUtils.resolve(request.url, location), request.options).then(function (redirectResponse) {
261 var error = new Error('redirect')
262 error.redirectResponse = redirectResponse
263 throw error
264 })
265 })
266 } else {
267 return response
268 }
269 })
270})
271
272function loadCookies (cookies, url) {
273 return cookies.getCookieStringSync(url)
274}
275
276function storeCookies (cookies, url, header) {
277 if (header) {
278 var headers =
279 header instanceof Array
280 ? header
281 : [header]
282
283 headers.forEach(function (setCookieHeader) {
284 cookies.setCookieSync(setCookieHeader, url)
285 })
286 }
287}
288
289middleware('cookies', function (request, next, client) {
290 var cookies
291
292 if (client._options.cookies === true) {
293 var toughCookie = require('tough-cookie')
294 cookies = request.options.cookies = client._options.cookies = new toughCookie.CookieJar()
295 } else {
296 cookies = request.options.cookies
297 }
298
299 if (cookies) {
300 request.headers.cookie = loadCookies(cookies, request.url)
301 return next().then(function (response) {
302 storeCookies(cookies, response.url, response.headers['set-cookie'])
303 return response
304 })
305 } else {
306 return next()
307 }
308})
309
310middleware('text', function (request, next) {
311 if (typeof request.body === 'string') {
312 setBodyToString(request, request.body)
313 utils.setHeaderTo(request, 'content-type', 'text/plain')
314 }
315
316 return next().then(function (response) {
317 if (utils.shouldParseAs(response, 'text', request)) {
318 return exports.streamToString(response.body).then(function (body) {
319 response.body = body
320 return response
321 })
322 } else {
323 return response
324 }
325 })
326})
327
328middleware('form', function (request, next) {
329 if (request.options.form && request.body instanceof Object && !isStream(request.body)) {
330 var querystring = request.options.qs || querystringLite
331 setBodyToString(request, querystring.stringify(request.body))
332 utils.setHeaderTo(request, 'content-type', 'application/x-www-form-urlencoded')
333 }
334
335 return next().then(function (response) {
336 if (utils.shouldParseAs(response, 'form', request)) {
337 return exports.streamToString(response.body).then(function (body) {
338 var querystring = request.options.qs || querystringLite
339 response.body = querystring.parse(body)
340 return response
341 })
342 } else {
343 return response
344 }
345 })
346})
347
348function contentTypeOfStream (stream) {
349 if (typeof stream.getHeaders === 'function') {
350 return stream.getHeaders()['content-type']
351 } else if (stream.path) {
352 return mimeTypes.lookup(stream.path)
353 }
354}
355
356middleware('streamContentType', function (request, next) {
357 if (isStream(request.body) && !request.headers['content-type']) {
358 request.headers['content-type'] = contentTypeOfStream(request.body)
359 }
360 return next()
361})
362
363function encodeBasicAuthorizationHeader (s) {
364 return 'Basic ' + Buffer.from(s).toString('base64')
365}
366
367middleware('basicAuth', function (request, next) {
368 function basicAuthorizationHeader () {
369 if (request.options.basicAuth) {
370 return encodeBasicAuthorizationHeader(request.options.basicAuth.username.replace(/:/g, '') + ':' + request.options.basicAuth.password)
371 } else {
372 var url = urlUtils.parse(request.url)
373 if (url.auth) {
374 return encodeBasicAuthorizationHeader(url.auth)
375 }
376 }
377 }
378
379 var header = basicAuthorizationHeader()
380 if (header) {
381 request.headers.authorization = header
382 }
383
384 return next()
385})