commit
3b3a96d9e4
|
@ -5,7 +5,11 @@ Changelog
|
||||||
|
|
||||||
# 1.x release
|
# 1.x release
|
||||||
|
|
||||||
## v1.2.1 (master)
|
## v1.3.0 (master)
|
||||||
|
|
||||||
|
- Enhance: now fetch.Request is exposed as well.
|
||||||
|
|
||||||
|
## v1.2.1
|
||||||
|
|
||||||
- Enhance: `Headers` now normalized `Number` value to `String`, prevent common mistakes
|
- Enhance: `Headers` now normalized `Number` value to `String`, prevent common mistakes
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,4 @@ Known differences
|
||||||
|
|
||||||
- Only support `res.text()` and `res.json()` at the moment, until there are good use-cases for blob.
|
- Only support `res.text()` and `res.json()` at the moment, until there are good use-cases for blob.
|
||||||
|
|
||||||
- Only expose `Response` and `Headers` constructors at the moment, we don't see a good use-case for `Request` interface yet.
|
|
||||||
|
|
||||||
- There is currently no built-in caching, as server-side caching varies by use-cases.
|
- There is currently no built-in caching, as server-side caching varies by use-cases.
|
||||||
|
|
60
index.js
60
index.js
|
@ -14,13 +14,14 @@ var stream = require('stream');
|
||||||
|
|
||||||
var Response = require('./lib/response');
|
var Response = require('./lib/response');
|
||||||
var Headers = require('./lib/headers');
|
var Headers = require('./lib/headers');
|
||||||
|
var Request = require('./lib/request');
|
||||||
|
|
||||||
module.exports = Fetch;
|
module.exports = Fetch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch class
|
* Fetch class
|
||||||
*
|
*
|
||||||
* @param String url Absolute url
|
* @param Mixed url Absolute url or Request instance
|
||||||
* @param Object opts Fetch options
|
* @param Object opts Fetch options
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
|
@ -41,44 +42,22 @@ function Fetch(url, opts) {
|
||||||
|
|
||||||
// wrap http.request into fetch
|
// wrap http.request into fetch
|
||||||
return new Fetch.Promise(function(resolve, reject) {
|
return new Fetch.Promise(function(resolve, reject) {
|
||||||
var uri = parse_url(url);
|
// build request object
|
||||||
|
var options;
|
||||||
if (!uri.protocol || !uri.hostname) {
|
try {
|
||||||
reject(new Error('only absolute urls are supported'));
|
options = new Request(url, opts);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri.protocol !== 'http:' && uri.protocol !== 'https:') {
|
var send;
|
||||||
reject(new Error('only http(s) protocols are supported'));
|
if (options.protocol === 'https:') {
|
||||||
return;
|
send = https.request;
|
||||||
}
|
|
||||||
|
|
||||||
var request;
|
|
||||||
if (uri.protocol === 'https:') {
|
|
||||||
request = https.request;
|
|
||||||
} else {
|
} else {
|
||||||
request = http.request;
|
send = http.request;
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
// avoid side-effect on input options
|
|
||||||
var options = {
|
|
||||||
hostname: uri.hostname
|
|
||||||
, port: uri.port
|
|
||||||
, path: uri.path
|
|
||||||
, auth: uri.auth
|
|
||||||
, method: opts.method || 'GET'
|
|
||||||
, headers: opts.headers || {}
|
|
||||||
, follow: opts.follow !== undefined ? opts.follow : 20
|
|
||||||
, counter: opts.counter || 0
|
|
||||||
, timeout: opts.timeout || 0
|
|
||||||
, compress: opts.compress !== false
|
|
||||||
, size: opts.size || 0
|
|
||||||
, body: opts.body
|
|
||||||
, agent: opts.agent
|
|
||||||
};
|
|
||||||
|
|
||||||
// normalize headers
|
// normalize headers
|
||||||
var headers = new Headers(options.headers);
|
var headers = new Headers(options.headers);
|
||||||
|
|
||||||
|
@ -101,21 +80,21 @@ function Fetch(url, opts) {
|
||||||
options.headers = headers.raw();
|
options.headers = headers.raw();
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
var req = request(options);
|
var req = send(options);
|
||||||
var reqTimeout;
|
var reqTimeout;
|
||||||
|
|
||||||
if (options.timeout) {
|
if (options.timeout) {
|
||||||
req.once('socket', function(socket) {
|
req.once('socket', function(socket) {
|
||||||
reqTimeout = setTimeout(function() {
|
reqTimeout = setTimeout(function() {
|
||||||
req.abort();
|
req.abort();
|
||||||
reject(new Error('network timeout at: ' + uri.href));
|
reject(new Error('network timeout at: ' + options.url));
|
||||||
}, options.timeout);
|
}, options.timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
req.on('error', function(err) {
|
req.on('error', function(err) {
|
||||||
clearTimeout(reqTimeout);
|
clearTimeout(reqTimeout);
|
||||||
reject(new Error('request to ' + uri.href + ' failed, reason: ' + err.message));
|
reject(new Error('request to ' + options.url + ' failed, reason: ' + err.message));
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('response', function(res) {
|
req.on('response', function(res) {
|
||||||
|
@ -124,18 +103,18 @@ function Fetch(url, opts) {
|
||||||
// handle redirect
|
// handle redirect
|
||||||
if (self.isRedirect(res.statusCode)) {
|
if (self.isRedirect(res.statusCode)) {
|
||||||
if (options.counter >= options.follow) {
|
if (options.counter >= options.follow) {
|
||||||
reject(new Error('maximum redirect reached at: ' + uri.href));
|
reject(new Error('maximum redirect reached at: ' + options.url));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.headers.location) {
|
if (!res.headers.location) {
|
||||||
reject(new Error('redirect location header missing at: ' + uri.href));
|
reject(new Error('redirect location header missing at: ' + options.url));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.counter++;
|
options.counter++;
|
||||||
|
|
||||||
resolve(Fetch(resolve_url(uri.href, res.headers.location), options));
|
resolve(Fetch(resolve_url(options.url, res.headers.location), options));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +134,7 @@ function Fetch(url, opts) {
|
||||||
|
|
||||||
// response object
|
// response object
|
||||||
var output = new Response(body, {
|
var output = new Response(body, {
|
||||||
url: uri.href
|
url: options.url
|
||||||
, status: res.statusCode
|
, status: res.statusCode
|
||||||
, headers: headers
|
, headers: headers
|
||||||
, size: options.size
|
, size: options.size
|
||||||
|
@ -192,3 +171,4 @@ Fetch.prototype.isRedirect = function(code) {
|
||||||
Fetch.Promise = global.Promise;
|
Fetch.Promise = global.Promise;
|
||||||
Fetch.Response = Response;
|
Fetch.Response = Response;
|
||||||
Fetch.Headers = Headers;
|
Fetch.Headers = Headers;
|
||||||
|
Fetch.Request = Request;
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request.js
|
||||||
|
*
|
||||||
|
* Request class contains server only options
|
||||||
|
*/
|
||||||
|
|
||||||
|
var parse_url = require('url').parse;
|
||||||
|
|
||||||
|
module.exports = Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request class
|
||||||
|
*
|
||||||
|
* @param Mixed input Url or Request instance
|
||||||
|
* @param Object init Custom options
|
||||||
|
* @return Void
|
||||||
|
*/
|
||||||
|
function Request(input, init) {
|
||||||
|
var url, url_parsed;
|
||||||
|
|
||||||
|
// normalize input
|
||||||
|
if (!(input instanceof Request)) {
|
||||||
|
url = input;
|
||||||
|
url_parsed = parse_url(url);
|
||||||
|
input = {};
|
||||||
|
} else {
|
||||||
|
url = input.url;
|
||||||
|
url_parsed = parse_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url_parsed.protocol || !url_parsed.hostname) {
|
||||||
|
throw new Error('only absolute urls are supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url_parsed.protocol !== 'http:' && url_parsed.protocol !== 'https:') {
|
||||||
|
throw new Error('only http(s) protocols are supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize init
|
||||||
|
init = init || {};
|
||||||
|
|
||||||
|
// fetch spec options
|
||||||
|
this.method = init.method || input.method || 'GET';
|
||||||
|
this.headers = init.headers || input.headers || {};
|
||||||
|
this.body = init.body || input.body;
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
// server only options
|
||||||
|
this.follow = init.follow !== undefined ?
|
||||||
|
init.follow : input.follow !== undefined ?
|
||||||
|
input.follow : 20;
|
||||||
|
this.counter = init.counter || input.follow || 0;
|
||||||
|
this.timeout = init.timeout || input.timeout || 0;
|
||||||
|
this.compress = init.compress !== undefined ?
|
||||||
|
init.compress : input.compress !== undefined ?
|
||||||
|
input.compress : true;
|
||||||
|
this.size = init.size || input.size || 0;
|
||||||
|
this.agent = init.agent || input.agent;
|
||||||
|
|
||||||
|
// server request options
|
||||||
|
this.protocol = url_parsed.protocol;
|
||||||
|
this.hostname = url_parsed.hostname;
|
||||||
|
this.port = url_parsed.port;
|
||||||
|
this.path = url_parsed.path;
|
||||||
|
this.auth = url_parsed.auth;
|
||||||
|
}
|
52
test/test.js
52
test/test.js
|
@ -16,6 +16,7 @@ var TestServer = require('./server');
|
||||||
var fetch = require('../index.js');
|
var fetch = require('../index.js');
|
||||||
var Headers = require('../lib/headers.js');
|
var Headers = require('../lib/headers.js');
|
||||||
var Response = require('../lib/response.js');
|
var Response = require('../lib/response.js');
|
||||||
|
var Request = require('../lib/request.js');
|
||||||
// test with native promise on node 0.11, and bluebird for node 0.10
|
// test with native promise on node 0.11, and bluebird for node 0.10
|
||||||
fetch.Promise = fetch.Promise || bluebird;
|
fetch.Promise = fetch.Promise || bluebird;
|
||||||
|
|
||||||
|
@ -59,9 +60,10 @@ describe('node-fetch', function() {
|
||||||
fetch.Promise = old;
|
fetch.Promise = old;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should expose Headers and Response constructors', function() {
|
it('should expose Headers, Response and Request constructors', function() {
|
||||||
expect(fetch.Headers).to.equal(Headers);
|
expect(fetch.Headers).to.equal(Headers);
|
||||||
expect(fetch.Response).to.equal(Response);
|
expect(fetch.Response).to.equal(Response);
|
||||||
|
expect(fetch.Request).to.equal(Request);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject with error if url is protocol relative', function() {
|
it('should reject with error if url is protocol relative', function() {
|
||||||
|
@ -474,7 +476,6 @@ describe('node-fetch', function() {
|
||||||
url = base + '/hello';
|
url = base + '/hello';
|
||||||
opts = {
|
opts = {
|
||||||
method: 'HEAD'
|
method: 'HEAD'
|
||||||
|
|
||||||
};
|
};
|
||||||
return fetch(url, opts).then(function(res) {
|
return fetch(url, opts).then(function(res) {
|
||||||
expect(res.status).to.equal(200);
|
expect(res.status).to.equal(200);
|
||||||
|
@ -693,6 +694,52 @@ describe('node-fetch', function() {
|
||||||
expect(h3._headers['b']).to.include('1');
|
expect(h3._headers['b']).to.include('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support fetch with Request instance', function() {
|
||||||
|
url = base + '/hello';
|
||||||
|
var req = new Request(url);
|
||||||
|
return fetch(req).then(function(res) {
|
||||||
|
expect(res.url).to.equal(url);
|
||||||
|
expect(res.ok).to.be.true;
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support wrapping Request instance', function() {
|
||||||
|
url = base + '/hello';
|
||||||
|
var r1 = new Request(url, {
|
||||||
|
method: 'POST'
|
||||||
|
, follow: 1
|
||||||
|
});
|
||||||
|
var r2 = new Request(r1, {
|
||||||
|
follow: 2
|
||||||
|
})
|
||||||
|
expect(r2.url).to.equal(url);
|
||||||
|
expect(r2.method).to.equal('POST');
|
||||||
|
expect(r1.follow).to.equal(1);
|
||||||
|
expect(r2.follow).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support overwrite Request instance', function() {
|
||||||
|
url = base + '/inspect';
|
||||||
|
var req = new Request(url, {
|
||||||
|
method: 'POST'
|
||||||
|
, headers: {
|
||||||
|
a: '1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return fetch(req, {
|
||||||
|
method: 'GET'
|
||||||
|
, headers: {
|
||||||
|
a: '2'
|
||||||
|
}
|
||||||
|
}).then(function(res) {
|
||||||
|
return res.json();
|
||||||
|
}).then(function(body) {
|
||||||
|
expect(body.method).to.equal('GET');
|
||||||
|
expect(body.headers.a).to.equal('2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should support https request', function() {
|
it('should support https request', function() {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
url = 'https://github.com/';
|
url = 'https://github.com/';
|
||||||
|
@ -704,5 +751,4 @@ describe('node-fetch', function() {
|
||||||
expect(res.ok).to.be.true;
|
expect(res.ok).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue