@ -1,4 +1,5 @@
'use strict' ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var http = require ( 'http' ) ;
var https = require ( 'https' ) ;
var urlLib = require ( 'url' ) ;
@ -14,6 +15,7 @@ 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' ) ;
function GotError ( message , nested ) {
NestedErrorStacks . call ( this , message , nested ) ;
@ -23,196 +25,157 @@ 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 ) ;
}
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 ) ;
}
function requestAsEventEmitter ( opts ) {
opts = opts || { } ;
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 ) {
function get ( opts ) {
var fn = opts . protocol === 'https:' ? https : http ;
var url = urlLib . format ( opts ) ;
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 ;
}
if ( proxy ) {
proxy . emit ( 'redirect' , res , redirectOpts ) ;
}
ee . emit ( 'redirect' , res , redirectOpts ) ;
get ( redirectOpts , cb ) ;
get ( redirectOpts ) ;
return ;
}
if ( proxy ) {
proxy . emit ( 'response' , res ) ;
}
if ( [ 'gzip' , 'deflate' ] . indexOf ( res . headers [ 'content-encoding' ] ) !== - 1 ) {
res = res . pipe ( zlib . createUnzip ( ) ) ;
var unzip = zlib . createUnzip ( ) ;
unzip . httpVersion = res . httpVersion ;
unzip . headers = res . headers ;
unzip . rawHeaders = res . rawHeaders ;
unzip . trailers = res . trailers ;
unzip . rawTrailers = res . rawTrailers ;
unzip . setTimeout = res . setTimeout . bind ( res ) ;
unzip . statusCode = res . statusCode ;
unzip . statusMessage = res . statusMessage ;
unzip . socket = res . socket ;
res = res . pipe ( unzip ) ;
}
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 ;
ee . emit ( 'response' , res ) ;
} ) . once ( 'error' , function ( err ) {
ee . emit ( 'error' , new GotError ( 'Request to ' + url + ' failed' , err ) ) ;
} ) ;
if ( data && json ) {
try {
data = JSON . parse ( data ) ;
} catch ( e ) {
err = new GotError ( 'Parsing ' + url + ' response failed' , new GotError ( e . message , err ) ) ;
}
}
if ( opts . timeout ) {
timedOut ( req , opts . timeout ) ;
}
ee . emit ( 'request' , req ) ;
}
setImmediate ( get , opts ) ; // quirk to attach event listeners
return ee ;
}
cb ( err , data , response ) ;
} ) ;
function asCallback ( opts , cb ) {
var ee = requestAsEventEmitter ( opts ) ;
var url = urlLib . format ( opts ) ;
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 resolve , reject ;
var promise = new pinkiePromise ( function ( _ resolve , _ reject ) {
resolve = _ resolve ;
reject = _ reject ;
} ) ;
var cb = function ( err , data , response ) {
response . body = data ;
if ( err ) {
err . response = response ;
reject ( err ) ;
return ;
}
if ( body ) {
proxy . write = function ( ) {
throw new Error ( 'got\'s stream is not writable when options.body is used' ) ;
} ;
resolve ( response ) ;
} ;
if ( isStream . readable ( body ) ) {
body . pipe ( req ) ;
} else {
req . end ( body ) ;
}
asCallback ( opts , cb ) ;
return promise ;
}
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 ;
}
@ -222,13 +185,88 @@ function got(url, opts, cb) {
}
req . end ( ) ;
}
} ) ;
ee . on ( 'response' , function ( res ) {
proxy . setReadable ( res ) ;
proxy . emit ( 'response' , res ) ;
} ) ;
get ( opts , cb ) ;
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 ) {
return asCallback ( opts , cb ) ;
}
return asPromise ( opts ) ;
}
[
'get' ,
'post' ,
@ -247,4 +285,8 @@ function got(url, opts, cb) {
} ;
} ) ;
got . stream = function ( url , opts ) {
return asStream ( normalizeArguments ( url , opts ) ) ;
} ;
module . exports = got ;