Merge pull request #100 from bitinn/prepare-new-release

Prepare release v1.5.0
This commit is contained in:
David Frank 2016-04-06 02:54:00 +08:00
commit 7bdc41f64a
11 changed files with 90 additions and 20 deletions

View File

@ -5,7 +5,13 @@ Changelog
# 1.x release
## v1.4.1 (master)
## v1.5.0 (master)
- Enhance: rejected promise now use custom `Error` (thx to @pekeler)
- Enhance: `FetchError` contains `err.type` and `err.code`, allows for better error handling (thx to @pekeler)
- Enhance: basic support for redirect mode `manual` and `error`, allows for location header extraction (thx to @jimmywarting for the initial PR)
## v1.4.1
- Fix: wrapping Request instance with FormData body again should preserve the body as-is

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 David Frank
Copyright (c) 2016 David Frank
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -14,6 +14,8 @@ Known differences
- Similarly, `req.body` can either be a string or a readable stream.
- Also, you can handle rejected fetch requests through checking `err.type` and `err.code`.
- Only support `res.text()` and `res.json()` at the moment, until there are good use-cases for blob.
- There is currently no built-in caching, as server-side caching varies by use-cases.

View File

@ -6,7 +6,7 @@ node-fetch
[![build status][travis-image]][travis-url]
[![coverage status][coveralls-image]][coveralls-url]
A light-weight module that brings `window.fetch` to node.js & io.js
A light-weight module that brings `window.fetch` to Node.js
# Motivation
@ -15,7 +15,7 @@ I really like the notion of Matt Andrews' [isomorphic-fetch](https://github.com/
Instead of implementing `XMLHttpRequest` in node to run browser-specific [fetch polyfill](https://github.com/github/fetch), why not go from node's `http` to `fetch` API directly? Node has native stream support, browserify build targets (browsers) don't, so underneath they are going to be vastly different anyway.
Hence `node-fetch`, minimal code for a `window.fetch` compatible API on node.js/io.js runtime.
Hence `node-fetch`, minimal code for a `window.fetch` compatible API on Node.js runtime.
# Features
@ -25,7 +25,7 @@ Hence `node-fetch`, minimal code for a `window.fetch` compatible API on node.js/
- Use native promise, but allow substituting it with [insert your favorite promise library].
- Use native stream for body, on both request and response.
- Decode content encoding (gzip/deflate) properly, and convert string output (such as `res.text()` and `res.json()`) to utf-8 automatically.
- Useful extensions such as timeout, redirect limit, response size limit.
- Useful extensions such as timeout, redirect limit, response size limit, explicit reject errors.
# Difference from client-side fetch
@ -157,7 +157,7 @@ default values are shown, note that only `method`, `headers` and `body` are allo
, compress: true // support gzip/deflate content encoding, false to disable
, size: 0 // maximum response body size in bytes, 0 to disable
, body: empty // request body, can be a string or readable stream
, agent: null // custom http.Agent instance
, agent: null // http.Agent instance, allows custom proxy, certificate etc.
}
```

View File

@ -115,7 +115,7 @@ function Fetch(url, opts) {
req.once('socket', function(socket) {
reqTimeout = setTimeout(function() {
req.abort();
reject(new FetchError('network timeout at: ' + options.url, 'socket-timeout'));
reject(new FetchError('network timeout at: ' + options.url, 'request-timeout'));
}, options.timeout);
});
}
@ -129,7 +129,12 @@ function Fetch(url, opts) {
clearTimeout(reqTimeout);
// handle redirect
if (self.isRedirect(res.statusCode)) {
if (self.isRedirect(res.statusCode) && options.redirect !== 'manual') {
if (options.redirect === 'error') {
reject(new FetchError('redirect mode is set to error: ' + options.url, 'no-redirect'));
return;
}
if (options.counter >= options.follow) {
reject(new FetchError('maximum redirect reached at: ' + options.url, 'max-redirect'));
return;
@ -169,6 +174,11 @@ function Fetch(url, opts) {
}
}
// normalize location header for manual redirect mode
if (options.redirect === 'manual') {
headers.set('location', resolve_url(options.url, headers.get('location')));
}
// response object
var output = new Response(body, {
url: options.url

View File

@ -1,7 +1,8 @@
/**
* response.js
* body.js
*
* Response class provides content decoding
* Body interface provides common methods for Request and Response
*/
var convert = require('encoding').convert;
@ -12,7 +13,7 @@ var FetchError = require('./fetch-error');
module.exports = Body;
/**
* Response class
* Body class
*
* @param Stream body Readable stream
* @param Object opts Response options

View File

@ -1,25 +1,34 @@
/**
* fetch-error.js
*
* FetchError class for operational errors
* FetchError interface for operational errors
*/
module.exports = FetchError;
/**
* Create FetchError
* Create FetchError instance
*
* @param String reason String type Error optionalSystemError
* @param String message Error message for human
* @param String type Error type for machine
* @param String systemError For Node.js system error
* @return FetchError
*/
function FetchError(message, type, optionalSystemError) {
function FetchError(message, type, systemError) {
// hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = message;
this.type = type;
if (optionalSystemError) {
this.code = this.errno = optionalSystemError.code;
// when err.type is `system`, err.code contains system error code
if (systemError) {
this.code = this.errno = systemError.code;
}
}
require('util').inherits(FetchError, Error);

View File

@ -1,3 +1,4 @@
/**
* headers.js
*

View File

@ -1,3 +1,4 @@
/**
* request.js
*
@ -43,6 +44,7 @@ function Request(input, init) {
// fetch spec options
this.method = init.method || input.method || 'GET';
this.redirect = init.redirect || input.redirect || 'follow';
this.headers = new Headers(init.headers || input.headers || {});
this.url = url;

View File

@ -1,3 +1,4 @@
/**
* response.js
*

View File

@ -89,7 +89,7 @@ describe('node-fetch', function() {
url = 'http://localhost:50000/';
return expect(fetch(url)).to.eventually.be.rejected
.and.be.an.instanceOf(FetchError)
.and.include({type: 'system', code: 'ECONNREFUSED', errno: 'ECONNREFUSED'});
.and.include({ type: 'system', code: 'ECONNREFUSED', errno: 'ECONNREFUSED' });
});
it('should resolve into response', function() {
@ -298,6 +298,28 @@ describe('node-fetch', function() {
.and.have.property('type', 'max-redirect');
});
it('should support redirect mode, manual flag', function() {
url = base + '/redirect/301';
opts = {
redirect: 'manual'
};
return fetch(url, opts).then(function(res) {
expect(res.url).to.equal(base + '/redirect/301');
expect(res.status).to.equal(301);
expect(res.headers.get('location')).to.equal(base + '/inspect');
});
});
it('should support redirect mode, error flag', function() {
url = base + '/redirect/301';
opts = {
redirect: 'error'
};
return expect(fetch(url, opts)).to.eventually.be.rejected
.and.be.an.instanceOf(FetchError)
.and.have.property('type', 'no-redirect');
});
it('should follow redirect code 301 and keep existing headers', function() {
url = base + '/redirect/301';
opts = {
@ -356,7 +378,7 @@ describe('node-fetch', function() {
});
it('should handle DNS-error response', function() {
url = 'http://invalid.commm';
url = 'http://domain.invalid';
return expect(fetch(url)).to.eventually.be.rejected
.and.be.an.instanceOf(FetchError)
.and.have.property('code', 'ENOTFOUND');
@ -448,7 +470,7 @@ describe('node-fetch', function() {
};
return expect(fetch(url, opts)).to.eventually.be.rejected
.and.be.an.instanceOf(FetchError)
.and.have.property('type', 'socket-timeout');
.and.have.property('type', 'request-timeout');
});
it('should allow custom timeout on response body', function() {
@ -1145,6 +1167,7 @@ describe('node-fetch', function() {
var req = new Request(url, {
body: body
, method: 'POST'
, redirect: 'manual'
, headers: {
b: '2'
}
@ -1155,6 +1178,7 @@ describe('node-fetch', function() {
var cl = req.clone();
expect(cl.url).to.equal(url);
expect(cl.method).to.equal('POST');
expect(cl.redirect).to.equal('manual');
expect(cl.headers.get('b')).to.equal('2');
expect(cl.follow).to.equal(3);
expect(cl.compress).to.equal(false);
@ -1175,6 +1199,20 @@ describe('node-fetch', function() {
expect(body).to.have.property('json');
});
it('should create custom FetchError', function() {
var systemError = new Error('system');
systemError.code = 'ESOMEERROR';
var err = new FetchError('test message', 'test-error', systemError);
expect(err).to.be.an.instanceof(Error);
expect(err).to.be.an.instanceof(FetchError);
expect(err.name).to.equal('FetchError');
expect(err.message).to.equal('test message');
expect(err.type).to.equal('test-error');
expect(err.code).to.equal('ESOMEERROR');
expect(err.errno).to.equal('ESOMEERROR');
});
it('should support https request', function() {
this.timeout(5000);
url = 'https://github.com/';