@ -1,12 +1,11 @@
'use strict' ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var http = require ( 'http' ) ;
var https = require ( 'https' ) ;
var urlLib = require ( 'url' ) ;
var util = require ( 'util' ) ;
var zlib = require ( 'zlib' ) ;
var querystring = require ( 'querystring' ) ;
var objectAssign = require ( 'object-assign' ) ;
var infinityAgent = require ( 'infinity-agent' ) ;
var duplexify = require ( 'duplexify' ) ;
var isStream = require ( 'is-stream' ) ;
var readAllStream = require ( 'read-all-stream' ) ;
@ -15,6 +14,8 @@ var prependHttp = require('prepend-http');
var lowercaseKeys = require ( 'lowercase-keys' ) ;
var isRedirect = require ( 'is-redirect' ) ;
var NestedErrorStacks = require ( 'nested-error-stacks' ) ;
var pinkiePromise = require ( 'pinkie-promise' ) ;
var unzipResponse = require ( 'unzip-response' ) ;
function GotError ( message , nested ) {
NestedErrorStacks . call ( this , message , nested ) ;
@ -24,221 +25,137 @@ function GotError(message, nested) {
util . inherits ( GotError , NestedErrorStacks ) ;
GotError . prototype . name = 'GotError' ;
function got ( url , opts , cb ) {
if ( typeof url !== 'string' && typeof url !== 'object' ) {
throw new GotError ( 'Parameter `url` must be a string or object, not ' + typeof url ) ;
}
function requestAsEventEmitter ( opts ) {
opts = opts || { } ;
if ( typeof opts === 'function' ) {
cb = opts ;
opts = { } ;
}
opts = objectAssign (
{
protocol : 'http:'
} ,
typeof url === 'string' ? urlLib . parse ( prependHttp ( url ) ) : url ,
opts
) ;
opts . headers = objectAssign ( {
'user-agent' : 'https://github.com/sindresorhus/got' ,
'accept-encoding' : 'gzip,deflate'
} , lowercaseKeys ( opts . headers ) ) ;
if ( opts . pathname ) {
opts . path = opts . pathname ;
}
if ( opts . query ) {
if ( typeof opts . query !== 'string' ) {
opts . query = querystring . stringify ( opts . query ) ;
}
opts . path = opts . pathname + '?' + opts . query ;
delete opts . query ;
}
var encoding = opts . encoding ;
var body = opts . body ;
var json = opts . json ;
var timeout = opts . timeout ;
var proxy ;
var ee = new EventEmitter ( ) ;
var redirectCount = 0 ;
delete opts . encoding ;
delete opts . body ;
delete opts . json ;
delete opts . timeout ;
if ( json ) {
opts . headers . accept = opts . headers . accept || 'application/json' ;
}
if ( body ) {
if ( typeof body !== 'string' && ! Buffer . isBuffer ( body ) && ! isStream . readable ( body ) ) {
throw new GotError ( 'options.body must be a ReadableStream, string or Buffer' ) ;
}
opts . method = opts . method || 'POST' ;
if ( ! opts . headers [ 'content-length' ] && ! opts . headers [ 'transfer-encoding' ] && ! isStream . readable ( body ) ) {
var length = typeof body === 'string' ? Buffer . byteLength ( body ) : body . length ;
opts . headers [ 'content-length' ] = length ;
}
}
opts . method = opts . method || 'GET' ;
// returns a proxy stream to the response
// if no callback has been provided
if ( ! cb ) {
proxy = duplexify ( ) ;
// forward errors on the stream
cb = function ( err , data , response ) {
proxy . emit ( 'error' , err , data , response ) ;
} ;
}
if ( proxy && json ) {
throw new GotError ( 'got can not be used as stream when options.json is used' ) ;
}
function get ( opts , cb ) {
var get = function ( opts ) {
var fn = opts . protocol === 'https:' ? https : http ;
var url = urlLib . format ( opts ) ;
if ( opts . agent === undefined ) {
opts . agent = infinityAgent [ fn === https ? 'https' : 'http' ] . globalAgent ;
if ( process . version . indexOf ( 'v0.10' ) === 0 && fn === https && (
typeof opts . ca !== 'undefined' ||
typeof opts . cert !== 'undefined' ||
typeof opts . ciphers !== 'undefined' ||
typeof opts . key !== 'undefined' ||
typeof opts . passphrase !== 'undefined' ||
typeof opts . pfx !== 'undefined' ||
typeof opts . rejectUnauthorized !== 'undefined' ) ) {
opts . agent = new infinityAgent . https . Agent ( {
ca : opts . ca ,
cert : opts . cert ,
ciphers : opts . ciphers ,
key : opts . key ,
passphrase : opts . passphrase ,
pfx : opts . pfx ,
rejectUnauthorized : opts . rejectUnauthorized
} ) ;
}
}
var req = fn . request ( opts , function ( response ) {
var statusCode = response . statusCode ;
var res = response ;
// auto-redirect only for GET and HEAD methods
var req = fn . request ( opts , function ( res ) {
var statusCode = res . statusCode ;
if ( isRedirect ( statusCode ) && 'location' in res . headers && ( opts . method === 'GET' || opts . method === 'HEAD' ) ) {
// discard response
res . resume ( ) ;
if ( ++ redirectCount > 10 ) {
cb ( new GotError ( 'Redirected 10 times. Aborting.' ) , undefined , res ) ;
ee . emit ( 'error' , new GotError ( 'Redirected 10 times. Aborting.' ) , undefined , res ) ;
return ;
}
var redirectUrl = urlLib . resolve ( url , res . headers . location ) ;
var redirectOpts = objectAssign ( { } , opts , urlLib . parse ( redirectUrl ) ) ;
if ( opts . agent === infinityAgent . http . globalAgent && redirectOpts . protocol === 'https:' && opts . protocol === 'http:' ) {
redirectOpts . agent = undefined ;
}
ee . emit ( 'redirect' , res , redirectOpts ) ;
if ( proxy ) {
proxy . emit ( 'redirect' , res , redirectOpts ) ;
}
get ( redirectOpts , cb ) ;
get ( redirectOpts ) ;
return ;
}
if ( proxy ) {
proxy . emit ( 'response' , res ) ;
}
ee . emit ( 'response' , unzipResponse ( res ) ) ;
} ) . once ( 'error' , function ( err ) {
ee . emit ( 'error' , new GotError ( 'Request to ' + url + ' failed' , err ) ) ;
} ) ;
if ( [ 'gzip' , 'deflate' ] . indexOf ( res . headers [ 'content-encoding' ] ) !== - 1 ) {
res = res . pipe ( zlib . createUnzip ( ) ) ;
}
if ( opts . timeout ) {
timedOut ( req , opts . timeout ) ;
}
if ( statusCode < 200 || statusCode > 299 ) {
readAllStream ( res , encoding , function ( err , data ) {
err = new GotError ( opts . method + ' ' + url + ' response code is ' + statusCode + ' (' + http . STATUS_CODES [ statusCode ] + ')' , err ) ;
err . code = statusCode ;
setImmediate ( ee . emit . bind ( ee ) , 'request' , req ) ;
} ;
get ( opts ) ;
return ee ;
}
if ( data && json ) {
try {
data = JSON . parse ( data ) ;
} catch ( e ) {
err = new GotError ( 'Parsing ' + url + ' response failed' , new GotError ( e . message , err ) ) ;
}
}
function asCallback ( opts , cb ) {
var ee = requestAsEventEmitter ( opts ) ;
var url = urlLib . format ( opts ) ;
cb ( err , data , response ) ;
} ) ;
ee . on ( 'request' , function ( req ) {
if ( isStream . readable ( opts . body ) ) {
opts . body . pipe ( req ) ;
opts . body = undefined ;
return ;
}
req . end ( opts . body ) ;
} ) ;
ee . on ( 'response' , function ( res ) {
readAllStream ( res , opts . encoding , function ( err , data ) {
if ( err ) {
cb ( new GotError ( 'Reading ' + url + ' response failed' , err ) , null , res ) ;
return ;
}
// pipe the response to the proxy if in proxy mode
if ( proxy ) {
proxy . setReadable ( res ) ;
return ;
var statusCode = res . statusCode ;
if ( statusCode < 200 || statusCode > 299 ) {
err = new GotError ( opts . method + ' ' + url + ' response code is ' + statusCode + ' (' + http . STATUS_CODES [ statusCode ] + ')' , err ) ;
err . code = statusCode ;
}
readAllStream ( res , encoding , function ( err , data ) {
if ( err ) {
err = new GotError ( 'Reading ' + url + ' response failed' , err ) ;
} else if ( json && statusCode !== 204 ) {
// only parse json if the option is enabled, and the response
// is not a 204 (empty reponse)
try {
data = JSON . parse ( data ) ;
} catch ( e ) {
err = new GotError ( 'Parsing ' + url + ' response failed' , e ) ;
}
if ( opts . json && statusCode !== 204 ) {
try {
data = JSON . parse ( data ) ;
} catch ( e ) {
err = new GotError ( 'Parsing ' + url + ' response failed' , new GotError ( e . message , err ) ) ;
}
}
cb ( err , data , response ) ;
} ) ;
} ) . once ( 'error' , function ( err ) {
cb ( new GotError ( 'Request to ' + url + ' failed' , err ) ) ;
cb ( err , data , res ) ;
} ) ;
} ) ;
if ( timeout ) {
timedOut ( req , timeout ) ;
}
ee . on ( 'error' , cb ) ;
}
if ( ! proxy ) {
if ( isStream . readable ( body ) ) {
body . pipe ( req ) ;
} else {
req . end ( body ) ;
function asPromise ( opts ) {
var promise = new pinkiePromise ( function ( resolve , reject ) {
asCallback ( opts , function ( err , data , response ) {
response . body = data ;
if ( err ) {
err . response = response ;
reject ( err ) ;
return ;
}
return ;
}
resolve ( response ) ;
} ) ;
} ) ;
if ( body ) {
proxy . write = function ( ) {
throw new Error ( 'got\'s stream is not writable when options.body is used' ) ;
} ;
return promise ;
}
if ( isStream . readable ( body ) ) {
body . pipe ( req ) ;
} else {
req . end ( body ) ;
}
function asStream ( opts ) {
var proxy = duplexify ( ) ;
if ( opts . json ) {
throw new GotError ( 'got can not be used as stream when options.json is used' ) ;
}
if ( opts . body ) {
proxy . write = function ( ) {
throw new Error ( 'got\'s stream is not writable when options.body is used' ) ;
} ;
}
var ee = requestAsEventEmitter ( opts ) ;
ee . on ( 'request' , function ( req ) {
proxy . emit ( 'request' , req ) ;
if ( isStream . readable ( opts . body ) ) {
opts . body . pipe ( req ) ;
return ;
}
if ( opts . body ) {
req . end ( opts . body ) ;
return ;
}
@ -248,21 +165,97 @@ function got(url, opts, cb) {
}
req . end ( ) ;
}
} ) ;
get ( opts , cb ) ;
ee . on ( 'response' , function ( res ) {
proxy . setReadable ( res ) ;
proxy . emit ( 'response' , res ) ;
} ) ;
ee . on ( 'redirect' , proxy . emit . bind ( proxy , 'redirect' ) ) ;
return proxy ;
}
[
function normalizeArguments ( url , opts ) {
if ( typeof url !== 'string' && typeof url !== 'object' ) {
throw new GotError ( 'Parameter `url` must be a string or object, not ' + typeof url ) ;
}
opts = objectAssign (
{ protocol : 'http:' } ,
typeof url === 'string' ? urlLib . parse ( prependHttp ( url ) ) : url ,
opts
) ;
opts . headers = objectAssign ( {
'user-agent' : 'https://github.com/sindresorhus/got' ,
'accept-encoding' : 'gzip,deflate'
} , lowercaseKeys ( opts . headers ) ) ;
if ( opts . pathname ) {
opts . path = opts . pathname ;
}
var query = opts . query ;
if ( query ) {
if ( typeof query !== 'string' ) {
opts . query = querystring . stringify ( query ) ;
}
opts . path = opts . pathname + '?' + opts . query ;
delete opts . query ;
}
if ( opts . json ) {
opts . headers . accept = opts . headers . accept || 'application/json' ;
}
var body = opts . body ;
if ( body ) {
if ( typeof body !== 'string' && ! Buffer . isBuffer ( body ) && ! isStream . readable ( body ) ) {
throw new GotError ( 'options.body must be a ReadableStream, string or Buffer' ) ;
}
opts . method = opts . method || 'POST' ;
if ( ! opts . headers [ 'content-length' ] && ! opts . headers [ 'transfer-encoding' ] && ! isStream . readable ( body ) ) {
var length = typeof body === 'string' ? Buffer . byteLength ( body ) : body . length ;
opts . headers [ 'content-length' ] = length ;
}
}
opts . method = opts . method || 'GET' ;
return opts ;
}
function got ( url , opts , cb ) {
if ( typeof opts === 'function' ) {
cb = opts ;
opts = { } ;
}
opts = normalizeArguments ( url , opts ) ;
if ( cb ) {
asCallback ( opts , cb ) ;
return ;
}
return asPromise ( opts ) ;
}
var helpers = [
'get' ,
'post' ,
'put' ,
'patch' ,
'head' ,
'delete'
] . forEach ( function ( el ) {
] ;
helpers . forEach ( function ( el ) {
got [ el ] = function ( url , opts , cb ) {
if ( typeof opts === 'function' ) {
cb = opts ;
@ -273,4 +266,14 @@ function got(url, opts, cb) {
} ;
} ) ;
got . stream = function ( url , opts ) {
return asStream ( normalizeArguments ( url , opts ) ) ;
} ;
helpers . forEach ( function ( el ) {
got . stream [ el ] = function ( url , opts ) {
return got . stream ( url , objectAssign ( { } , opts , { method : el . toUpperCase ( ) } ) ) ;
} ;
} ) ;
module . exports = got ;