Convert all files to ES2015 (#182)
Elements of this commit come from #140 by @gwicke.
This commit is contained in:
parent
993d4cdea1
commit
838071247d
|
@ -6,7 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "babel -d lib src",
|
"build": "babel -d lib src",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"test": "mocha --compilers js:babel-register test/test.js",
|
"test": "mocha --compilers js:babel-polyfill --compilers js:babel-register test/test.js",
|
||||||
"report": "cross-env BABEL_ENV=test nyc --reporter lcov --reporter text mocha -R spec test/test.js",
|
"report": "cross-env BABEL_ENV=test nyc --reporter lcov --reporter text mocha -R spec test/test.js",
|
||||||
"coverage": "cross-env BABEL_ENV=test nyc --reporter lcovonly mocha -R spec test/test.js && cat ./coverage/lcov.info | coveralls"
|
"coverage": "cross-env BABEL_ENV=test nyc --reporter lcovonly mocha -R spec test/test.js && cat ./coverage/lcov.info | coveralls"
|
||||||
},
|
},
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
"babel-cli": "^6.16.0",
|
"babel-cli": "^6.16.0",
|
||||||
"babel-plugin-istanbul": "^2.0.1",
|
"babel-plugin-istanbul": "^2.0.1",
|
||||||
"babel-plugin-transform-runtime": "^6.15.0",
|
"babel-plugin-transform-runtime": "^6.15.0",
|
||||||
|
"babel-polyfill": "^6.16.0",
|
||||||
"babel-preset-es2015": "^6.16.0",
|
"babel-preset-es2015": "^6.16.0",
|
||||||
"babel-register": "^6.16.3",
|
"babel-register": "^6.16.3",
|
||||||
"bluebird": "^3.3.4",
|
"bluebird": "^3.3.4",
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
"resumer": "0.0.0"
|
"resumer": "0.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"babel-runtime": "^6.11.6",
|
||||||
"encoding": "^0.1.11",
|
"encoding": "^0.1.11",
|
||||||
"is-stream": "^1.0.1"
|
"is-stream": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
@ -67,6 +69,7 @@
|
||||||
"src/*.js"
|
"src/*.js"
|
||||||
],
|
],
|
||||||
"require": [
|
"require": [
|
||||||
|
"babel-polyfill",
|
||||||
"babel-register"
|
"babel-register"
|
||||||
],
|
],
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
|
|
412
src/body.js
412
src/body.js
|
@ -5,12 +5,17 @@
|
||||||
* Body interface provides common methods for Request and Response
|
* Body interface provides common methods for Request and Response
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var convert = require('encoding').convert;
|
import {convert} from 'encoding';
|
||||||
var bodyStream = require('is-stream');
|
import bodyStream from 'is-stream';
|
||||||
var PassThrough = require('stream').PassThrough;
|
import {PassThrough} from 'stream';
|
||||||
var FetchError = require('./fetch-error');
|
import FetchError from './fetch-error.js';
|
||||||
|
|
||||||
module.exports = Body;
|
const DISTURBED = Symbol('disturbed');
|
||||||
|
const BYTES = Symbol('bytes');
|
||||||
|
const RAW = Symbol('raw');
|
||||||
|
const ABORT = Symbol('abort');
|
||||||
|
const CONVERT = Symbol('convert');
|
||||||
|
const DECODE = Symbol('decode');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Body class
|
* Body class
|
||||||
|
@ -19,221 +24,210 @@ module.exports = Body;
|
||||||
* @param Object opts Response options
|
* @param Object opts Response options
|
||||||
* @return Void
|
* @return Void
|
||||||
*/
|
*/
|
||||||
function Body(body, opts) {
|
export default class Body {
|
||||||
|
constructor(body, {
|
||||||
|
size = 0,
|
||||||
|
timeout = 0
|
||||||
|
} = {}) {
|
||||||
|
this.body = body;
|
||||||
|
this[DISTURBED] = false;
|
||||||
|
this.size = size;
|
||||||
|
this[BYTES] = 0;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this[RAW] = [];
|
||||||
|
this[ABORT] = false;
|
||||||
|
}
|
||||||
|
|
||||||
opts = opts || {};
|
get bodyUsed() {
|
||||||
|
return this[DISTURBED];
|
||||||
|
}
|
||||||
|
|
||||||
this.body = body;
|
/**
|
||||||
this.bodyUsed = false;
|
* Decode response as json
|
||||||
this.size = opts.size || 0;
|
*
|
||||||
this.timeout = opts.timeout || 0;
|
* @return Promise
|
||||||
this._raw = [];
|
*/
|
||||||
this._abort = false;
|
json() {
|
||||||
|
// for 204 No Content response, buffer will be empty, parsing it will throw error
|
||||||
|
if (this.status === 204) {
|
||||||
|
return Body.Promise.resolve({});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this[DECODE]().then(buffer => JSON.parse(buffer.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode response as text
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
text() {
|
||||||
|
return this[DECODE]().then(buffer => buffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode response as buffer (non-spec api)
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
buffer() {
|
||||||
|
return this[DECODE]();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode buffers into utf-8 string
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
|
*/
|
||||||
|
[DECODE]() {
|
||||||
|
if (this[DISTURBED]) {
|
||||||
|
return Body.Promise.reject(new Error(`body used already for: ${this.url}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
this[DISTURBED] = true;
|
||||||
|
this[BYTES] = 0;
|
||||||
|
this[ABORT] = false;
|
||||||
|
this[RAW] = [];
|
||||||
|
|
||||||
|
return new Body.Promise((resolve, reject) => {
|
||||||
|
let resTimeout;
|
||||||
|
|
||||||
|
// body is string
|
||||||
|
if (typeof this.body === 'string') {
|
||||||
|
this[BYTES] = this.body.length;
|
||||||
|
this[RAW] = [new Buffer(this.body)];
|
||||||
|
return resolve(this[CONVERT]());
|
||||||
|
}
|
||||||
|
|
||||||
|
// body is buffer
|
||||||
|
if (this.body instanceof Buffer) {
|
||||||
|
this[BYTES] = this.body.length;
|
||||||
|
this[RAW] = [this.body];
|
||||||
|
return resolve(this[CONVERT]());
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow timeout on slow response body
|
||||||
|
if (this.timeout) {
|
||||||
|
resTimeout = setTimeout(() => {
|
||||||
|
this[ABORT] = true;
|
||||||
|
reject(new FetchError('response timeout at ' + this.url + ' over limit: ' + this.timeout, 'body-timeout'));
|
||||||
|
}, this.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle stream error, such as incorrect content-encoding
|
||||||
|
this.body.on('error', err => {
|
||||||
|
reject(new FetchError('invalid response body at: ' + this.url + ' reason: ' + err.message, 'system', err));
|
||||||
|
});
|
||||||
|
|
||||||
|
// body is stream
|
||||||
|
this.body.on('data', chunk => {
|
||||||
|
if (this[ABORT] || chunk === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.size && this[BYTES] + chunk.length > this.size) {
|
||||||
|
this[ABORT] = true;
|
||||||
|
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this[BYTES] += chunk.length;
|
||||||
|
this[RAW].push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.body.on('end', () => {
|
||||||
|
if (this[ABORT]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(resTimeout);
|
||||||
|
resolve(this[CONVERT]());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect buffer encoding and convert to target encoding
|
||||||
|
* ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
|
||||||
|
*
|
||||||
|
* @param String encoding Target encoding
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
[CONVERT](encoding = 'utf-8') {
|
||||||
|
const ct = this.headers.get('content-type');
|
||||||
|
let charset = 'utf-8';
|
||||||
|
let res, str;
|
||||||
|
|
||||||
|
// header
|
||||||
|
if (ct) {
|
||||||
|
// skip encoding detection altogether if not html/xml/plain text
|
||||||
|
if (!/text\/html|text\/plain|\+xml|\/xml/i.test(ct)) {
|
||||||
|
return Buffer.concat(this[RAW]);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = /charset=([^;]*)/i.exec(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no charset in content type, peek at response body for at most 1024 bytes
|
||||||
|
if (!res && this[RAW].length > 0) {
|
||||||
|
for (let i = 0; i < this[RAW].length; i++) {
|
||||||
|
str += this[RAW][i].toString()
|
||||||
|
if (str.length > 1024) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str = str.substr(0, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
// html5
|
||||||
|
if (!res && str) {
|
||||||
|
res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// html4
|
||||||
|
if (!res && str) {
|
||||||
|
res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
res = /charset=(.*)/i.exec(res.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xml
|
||||||
|
if (!res && str) {
|
||||||
|
res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// found charset
|
||||||
|
if (res) {
|
||||||
|
charset = res.pop();
|
||||||
|
|
||||||
|
// prevent decode issues when sites use incorrect encoding
|
||||||
|
// ref: https://hsivonen.fi/encoding-menu/
|
||||||
|
if (charset === 'gb2312' || charset === 'gbk') {
|
||||||
|
charset = 'gb18030';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn raw buffers into a single utf-8 buffer
|
||||||
|
return convert(
|
||||||
|
Buffer.concat(this[RAW])
|
||||||
|
, encoding
|
||||||
|
, charset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode response as json
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
Body.prototype.json = function() {
|
|
||||||
|
|
||||||
// for 204 No Content response, buffer will be empty, parsing it will throw error
|
|
||||||
if (this.status === 204) {
|
|
||||||
return Body.Promise.resolve({});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._decode().then(function(buffer) {
|
|
||||||
return JSON.parse(buffer.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode response as text
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
Body.prototype.text = function() {
|
|
||||||
|
|
||||||
return this._decode().then(function(buffer) {
|
|
||||||
return buffer.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode response as buffer (non-spec api)
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
Body.prototype.buffer = function() {
|
|
||||||
|
|
||||||
return this._decode();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode buffers into utf-8 string
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*/
|
|
||||||
Body.prototype._decode = function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
return Body.Promise.reject(new Error('body used already for: ' + this.url));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bodyUsed = true;
|
|
||||||
this._bytes = 0;
|
|
||||||
this._abort = false;
|
|
||||||
this._raw = [];
|
|
||||||
|
|
||||||
return new Body.Promise(function(resolve, reject) {
|
|
||||||
var resTimeout;
|
|
||||||
|
|
||||||
// body is string
|
|
||||||
if (typeof self.body === 'string') {
|
|
||||||
self._bytes = self.body.length;
|
|
||||||
self._raw = [new Buffer(self.body)];
|
|
||||||
return resolve(self._convert());
|
|
||||||
}
|
|
||||||
|
|
||||||
// body is buffer
|
|
||||||
if (self.body instanceof Buffer) {
|
|
||||||
self._bytes = self.body.length;
|
|
||||||
self._raw = [self.body];
|
|
||||||
return resolve(self._convert());
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow timeout on slow response body
|
|
||||||
if (self.timeout) {
|
|
||||||
resTimeout = setTimeout(function() {
|
|
||||||
self._abort = true;
|
|
||||||
reject(new FetchError('response timeout at ' + self.url + ' over limit: ' + self.timeout, 'body-timeout'));
|
|
||||||
}, self.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle stream error, such as incorrect content-encoding
|
|
||||||
self.body.on('error', function(err) {
|
|
||||||
reject(new FetchError('invalid response body at: ' + self.url + ' reason: ' + err.message, 'system', err));
|
|
||||||
});
|
|
||||||
|
|
||||||
// body is stream
|
|
||||||
self.body.on('data', function(chunk) {
|
|
||||||
if (self._abort || chunk === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.size && self._bytes + chunk.length > self.size) {
|
|
||||||
self._abort = true;
|
|
||||||
reject(new FetchError('content size at ' + self.url + ' over limit: ' + self.size, 'max-size'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self._bytes += chunk.length;
|
|
||||||
self._raw.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.body.on('end', function() {
|
|
||||||
if (self._abort) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(resTimeout);
|
|
||||||
resolve(self._convert());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect buffer encoding and convert to target encoding
|
|
||||||
* ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
|
|
||||||
*
|
|
||||||
* @param String encoding Target encoding
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
Body.prototype._convert = function(encoding) {
|
|
||||||
|
|
||||||
encoding = encoding || 'utf-8';
|
|
||||||
|
|
||||||
var ct = this.headers.get('content-type');
|
|
||||||
var charset = 'utf-8';
|
|
||||||
var res, str;
|
|
||||||
|
|
||||||
// header
|
|
||||||
if (ct) {
|
|
||||||
// skip encoding detection altogether if not html/xml/plain text
|
|
||||||
if (!/text\/html|text\/plain|\+xml|\/xml/i.test(ct)) {
|
|
||||||
return Buffer.concat(this._raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = /charset=([^;]*)/i.exec(ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no charset in content type, peek at response body for at most 1024 bytes
|
|
||||||
if (!res && this._raw.length > 0) {
|
|
||||||
for (var i = 0; i < this._raw.length; i++) {
|
|
||||||
str += this._raw[i].toString()
|
|
||||||
if (str.length > 1024) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str = str.substr(0, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
// html5
|
|
||||||
if (!res && str) {
|
|
||||||
res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// html4
|
|
||||||
if (!res && str) {
|
|
||||||
res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
res = /charset=(.*)/i.exec(res.pop());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xml
|
|
||||||
if (!res && str) {
|
|
||||||
res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// found charset
|
|
||||||
if (res) {
|
|
||||||
charset = res.pop();
|
|
||||||
|
|
||||||
// prevent decode issues when sites use incorrect encoding
|
|
||||||
// ref: https://hsivonen.fi/encoding-menu/
|
|
||||||
if (charset === 'gb2312' || charset === 'gbk') {
|
|
||||||
charset = 'gb18030';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn raw buffers into a single utf-8 buffer
|
|
||||||
return convert(
|
|
||||||
Buffer.concat(this._raw)
|
|
||||||
, encoding
|
|
||||||
, charset
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone body given Res/Req instance
|
* Clone body given Res/Req instance
|
||||||
*
|
*
|
||||||
* @param Mixed instance Response or Request instance
|
* @param Mixed instance Response or Request instance
|
||||||
* @return Mixed
|
* @return Mixed
|
||||||
*/
|
*/
|
||||||
Body.prototype._clone = function(instance) {
|
export function clone(instance) {
|
||||||
var p1, p2;
|
let p1, p2;
|
||||||
var body = instance.body;
|
let body = instance.body;
|
||||||
|
|
||||||
// don't allow cloning a used body
|
// don't allow cloning a used body
|
||||||
if (instance.bodyUsed) {
|
if (instance.bodyUsed) {
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
* FetchError interface for operational errors
|
* FetchError interface for operational errors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = FetchError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create FetchError instance
|
* Create FetchError instance
|
||||||
*
|
*
|
||||||
|
@ -15,7 +13,7 @@ module.exports = FetchError;
|
||||||
* @param String systemError For Node.js system error
|
* @param String systemError For Node.js system error
|
||||||
* @return FetchError
|
* @return FetchError
|
||||||
*/
|
*/
|
||||||
function FetchError(message, type, systemError) {
|
export default function FetchError(message, type, systemError) {
|
||||||
|
|
||||||
// hide custom error implementation details from end-users
|
// hide custom error implementation details from end-users
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
|
109
src/index.js
109
src/index.js
|
@ -5,50 +5,38 @@
|
||||||
* a request API compatible with window.fetch
|
* a request API compatible with window.fetch
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var parse_url = require('url').parse;
|
import {resolve as resolve_url} from 'url';
|
||||||
var resolve_url = require('url').resolve;
|
import * as http from 'http';
|
||||||
var http = require('http');
|
import * as https from 'https';
|
||||||
var https = require('https');
|
import * as zlib from 'zlib';
|
||||||
var zlib = require('zlib');
|
import {PassThrough} from 'stream';
|
||||||
var stream = require('stream');
|
|
||||||
|
|
||||||
var Body = require('./body');
|
import Body from './body';
|
||||||
var Response = require('./response');
|
import Response from './response';
|
||||||
import Headers from './headers';
|
import Headers from './headers';
|
||||||
var Request = require('./request');
|
import Request from './request';
|
||||||
var FetchError = require('./fetch-error');
|
import FetchError from './fetch-error';
|
||||||
|
|
||||||
// commonjs
|
|
||||||
module.exports = Fetch;
|
|
||||||
// es6 default export compatibility
|
|
||||||
module.exports.default = module.exports;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch class
|
* Fetch function
|
||||||
*
|
*
|
||||||
* @param Mixed url Absolute url or Request instance
|
* @param Mixed url Absolute url or Request instance
|
||||||
* @param Object opts Fetch options
|
* @param Object opts Fetch options
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
function Fetch(url, opts) {
|
function fetch(url, opts) {
|
||||||
|
|
||||||
// allow call as function
|
|
||||||
if (!(this instanceof Fetch))
|
|
||||||
return new Fetch(url, opts);
|
|
||||||
|
|
||||||
// allow custom promise
|
// allow custom promise
|
||||||
if (!Fetch.Promise) {
|
if (!fetch.Promise) {
|
||||||
throw new Error('native promise missing, set Fetch.Promise to your favorite alternative');
|
throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
|
||||||
}
|
}
|
||||||
|
|
||||||
Body.Promise = Fetch.Promise;
|
Body.Promise = fetch.Promise;
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// wrap http.request into fetch
|
// wrap http.request into fetch
|
||||||
return new Fetch.Promise(function(resolve, reject) {
|
return new fetch.Promise((resolve, reject) => {
|
||||||
// build request object
|
// build request object
|
||||||
var options = new Request(url, opts);
|
const options = new Request(url, opts);
|
||||||
|
|
||||||
if (!options.protocol || !options.hostname) {
|
if (!options.protocol || !options.hostname) {
|
||||||
throw new Error('only absolute urls are supported');
|
throw new Error('only absolute urls are supported');
|
||||||
|
@ -58,15 +46,10 @@ function Fetch(url, opts) {
|
||||||
throw new Error('only http(s) protocols are supported');
|
throw new Error('only http(s) protocols are supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
var send;
|
const send = (options.protocol === 'https:' ? https : http).request;
|
||||||
if (options.protocol === 'https:') {
|
|
||||||
send = https.request;
|
|
||||||
} else {
|
|
||||||
send = http.request;
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize headers
|
// normalize headers
|
||||||
var headers = new Headers(options.headers);
|
const headers = new Headers(options.headers);
|
||||||
|
|
||||||
if (options.compress) {
|
if (options.compress) {
|
||||||
headers.set('accept-encoding', 'gzip,deflate');
|
headers.set('accept-encoding', 'gzip,deflate');
|
||||||
|
@ -86,7 +69,7 @@ function Fetch(url, opts) {
|
||||||
|
|
||||||
// detect form data input from form-data module, this hack avoid the need to pass multipart header manually
|
// detect form data input from form-data module, this hack avoid the need to pass multipart header manually
|
||||||
if (!headers.has('content-type') && options.body && typeof options.body.getBoundary === 'function') {
|
if (!headers.has('content-type') && options.body && typeof options.body.getBoundary === 'function') {
|
||||||
headers.set('content-type', 'multipart/form-data; boundary=' + options.body.getBoundary());
|
headers.set('content-type', `multipart/form-data; boundary=${options.body.getBoundary()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bring node-fetch closer to browser behavior by setting content-length automatically
|
// bring node-fetch closer to browser behavior by setting content-length automatically
|
||||||
|
@ -116,40 +99,40 @@ function Fetch(url, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
var req = send(options);
|
const req = send(options);
|
||||||
var reqTimeout;
|
let reqTimeout;
|
||||||
|
|
||||||
if (options.timeout) {
|
if (options.timeout) {
|
||||||
req.once('socket', function(socket) {
|
req.once('socket', socket => {
|
||||||
reqTimeout = setTimeout(function() {
|
reqTimeout = setTimeout(() => {
|
||||||
req.abort();
|
req.abort();
|
||||||
reject(new FetchError('network timeout at: ' + options.url, 'request-timeout'));
|
reject(new FetchError(`network timeout at: ${options.url}`, 'request-timeout'));
|
||||||
}, options.timeout);
|
}, options.timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
req.on('error', function(err) {
|
req.on('error', err => {
|
||||||
clearTimeout(reqTimeout);
|
clearTimeout(reqTimeout);
|
||||||
reject(new FetchError('request to ' + options.url + ' failed, reason: ' + err.message, 'system', err));
|
reject(new FetchError(`request to ${options.url} failed, reason: ${err.message}`, 'system', err));
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('response', function(res) {
|
req.on('response', res => {
|
||||||
clearTimeout(reqTimeout);
|
clearTimeout(reqTimeout);
|
||||||
|
|
||||||
// handle redirect
|
// handle redirect
|
||||||
if (self.isRedirect(res.statusCode) && options.redirect !== 'manual') {
|
if (fetch.isRedirect(res.statusCode) && options.redirect !== 'manual') {
|
||||||
if (options.redirect === 'error') {
|
if (options.redirect === 'error') {
|
||||||
reject(new FetchError('redirect mode is set to error: ' + options.url, 'no-redirect'));
|
reject(new FetchError(`redirect mode is set to error: ${options.url}`, 'no-redirect'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.counter >= options.follow) {
|
if (options.counter >= options.follow) {
|
||||||
reject(new FetchError('maximum redirect reached at: ' + options.url, 'max-redirect'));
|
reject(new FetchError(`maximum redirect reached at: ${options.url}`, 'max-redirect'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.headers.location) {
|
if (!res.headers.location) {
|
||||||
reject(new FetchError('redirect location header missing at: ' + options.url, 'invalid-redirect'));
|
reject(new FetchError(`redirect location header missing at: ${options.url}`, 'invalid-redirect'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,19 +147,19 @@ function Fetch(url, opts) {
|
||||||
|
|
||||||
options.counter++;
|
options.counter++;
|
||||||
|
|
||||||
resolve(Fetch(resolve_url(options.url, res.headers.location), options));
|
resolve(fetch(resolve_url(options.url, res.headers.location), options));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize location header for manual redirect mode
|
// normalize location header for manual redirect mode
|
||||||
var headers = new Headers(res.headers);
|
const headers = new Headers(res.headers);
|
||||||
if (options.redirect === 'manual' && headers.has('location')) {
|
if (options.redirect === 'manual' && headers.has('location')) {
|
||||||
headers.set('location', resolve_url(options.url, headers.get('location')));
|
headers.set('location', resolve_url(options.url, headers.get('location')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare response
|
// prepare response
|
||||||
var body = res.pipe(new stream.PassThrough());
|
let body = res.pipe(new PassThrough());
|
||||||
var response_options = {
|
const response_options = {
|
||||||
url: options.url
|
url: options.url
|
||||||
, status: res.statusCode
|
, status: res.statusCode
|
||||||
, statusText: res.statusMessage
|
, statusText: res.statusMessage
|
||||||
|
@ -186,7 +169,7 @@ function Fetch(url, opts) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// response object
|
// response object
|
||||||
var output;
|
let output;
|
||||||
|
|
||||||
// in following scenarios we ignore compression support
|
// in following scenarios we ignore compression support
|
||||||
// 1. compression support is disabled
|
// 1. compression support is disabled
|
||||||
|
@ -201,7 +184,7 @@ function Fetch(url, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, check for gzip or deflate
|
// otherwise, check for gzip or deflate
|
||||||
var name = headers.get('content-encoding');
|
let name = headers.get('content-encoding');
|
||||||
|
|
||||||
// for gzip
|
// for gzip
|
||||||
if (name == 'gzip' || name == 'x-gzip') {
|
if (name == 'gzip' || name == 'x-gzip') {
|
||||||
|
@ -214,8 +197,8 @@ function Fetch(url, opts) {
|
||||||
} else if (name == 'deflate' || name == 'x-deflate') {
|
} else if (name == 'deflate' || name == 'x-deflate') {
|
||||||
// handle the infamous raw deflate response from old servers
|
// handle the infamous raw deflate response from old servers
|
||||||
// a hack for old IIS and Apache servers
|
// a hack for old IIS and Apache servers
|
||||||
var raw = res.pipe(new stream.PassThrough());
|
const raw = res.pipe(new PassThrough());
|
||||||
raw.once('data', function(chunk) {
|
raw.once('data', chunk => {
|
||||||
// see http://stackoverflow.com/questions/37519828
|
// see http://stackoverflow.com/questions/37519828
|
||||||
if ((chunk[0] & 0x0F) === 0x08) {
|
if ((chunk[0] & 0x0F) === 0x08) {
|
||||||
body = body.pipe(zlib.createInflate());
|
body = body.pipe(zlib.createInflate());
|
||||||
|
@ -254,18 +237,18 @@ function Fetch(url, opts) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = fetch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect code matching
|
* Redirect code matching
|
||||||
*
|
*
|
||||||
* @param Number code Status code
|
* @param Number code Status code
|
||||||
* @return Boolean
|
* @return Boolean
|
||||||
*/
|
*/
|
||||||
Fetch.prototype.isRedirect = function(code) {
|
fetch.isRedirect = code => code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
|
||||||
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose Promise
|
// expose Promise
|
||||||
Fetch.Promise = global.Promise;
|
fetch.Promise = global.Promise;
|
||||||
Fetch.Response = Response;
|
fetch.Response = Response;
|
||||||
Fetch.Headers = Headers;
|
fetch.Headers = Headers;
|
||||||
Fetch.Request = Request;
|
fetch.Request = Request;
|
||||||
|
|
112
src/request.js
112
src/request.js
|
@ -5,11 +5,9 @@
|
||||||
* Request class contains server only options
|
* Request class contains server only options
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var parse_url = require('url').parse;
|
import { parse as parse_url } from 'url';
|
||||||
import Headers from './headers';
|
import Headers from './headers.js';
|
||||||
var Body = require('./body');
|
import Body, { clone } from './body';
|
||||||
|
|
||||||
module.exports = Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request class
|
* Request class
|
||||||
|
@ -18,58 +16,62 @@ module.exports = Request;
|
||||||
* @param Object init Custom options
|
* @param Object init Custom options
|
||||||
* @return Void
|
* @return Void
|
||||||
*/
|
*/
|
||||||
function Request(input, init) {
|
export default class Request extends Body {
|
||||||
var url, url_parsed;
|
constructor(input, init = {}) {
|
||||||
|
let url, url_parsed;
|
||||||
|
|
||||||
// normalize input
|
// normalize input
|
||||||
if (!(input instanceof Request)) {
|
if (!(input instanceof Request)) {
|
||||||
url = input;
|
url = input;
|
||||||
url_parsed = parse_url(url);
|
url_parsed = parse_url(url);
|
||||||
input = {};
|
input = {};
|
||||||
} else {
|
} else {
|
||||||
url = input.url;
|
url = input.url;
|
||||||
url_parsed = parse_url(url);
|
url_parsed = parse_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
super(init.body || clone(input), {
|
||||||
|
timeout: init.timeout || input.timeout || 0,
|
||||||
|
size: init.size || input.size || 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// server only options
|
||||||
|
this.follow = init.follow !== undefined ?
|
||||||
|
init.follow : input.follow !== undefined ?
|
||||||
|
input.follow : 20;
|
||||||
|
this.compress = init.compress !== undefined ?
|
||||||
|
init.compress : input.compress !== undefined ?
|
||||||
|
input.compress : true;
|
||||||
|
this.counter = init.counter || input.counter || 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize init
|
/**
|
||||||
init = init || {};
|
* Clone this request
|
||||||
|
*
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
clone() {
|
||||||
|
return new Request(this);
|
||||||
|
}
|
||||||
|
|
||||||
// fetch spec options
|
/**
|
||||||
this.method = init.method || input.method || 'GET';
|
* Tag used by `Object.prototype.toString()`.
|
||||||
this.redirect = init.redirect || input.redirect || 'follow';
|
*/
|
||||||
this.headers = new Headers(init.headers || input.headers || {});
|
get [Symbol.toStringTag]() {
|
||||||
this.url = url;
|
return 'Request';
|
||||||
|
}
|
||||||
// server only options
|
|
||||||
this.follow = init.follow !== undefined ?
|
|
||||||
init.follow : input.follow !== undefined ?
|
|
||||||
input.follow : 20;
|
|
||||||
this.compress = init.compress !== undefined ?
|
|
||||||
init.compress : input.compress !== undefined ?
|
|
||||||
input.compress : true;
|
|
||||||
this.counter = init.counter || input.counter || 0;
|
|
||||||
this.agent = init.agent || input.agent;
|
|
||||||
|
|
||||||
Body.call(this, init.body || this._clone(input), {
|
|
||||||
timeout: init.timeout || input.timeout || 0,
|
|
||||||
size: init.size || input.size || 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Request.prototype = Object.create(Body.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone this request
|
|
||||||
*
|
|
||||||
* @return Request
|
|
||||||
*/
|
|
||||||
Request.prototype.clone = function() {
|
|
||||||
return new Request(this);
|
|
||||||
};
|
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
* Response class provides content decoding
|
* Response class provides content decoding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var http = require('http');
|
import { STATUS_CODES } from 'http';
|
||||||
import Headers from './headers';
|
import Headers from './headers.js';
|
||||||
var Body = require('./body');
|
import Body, { clone } from './body';
|
||||||
|
|
||||||
module.exports = Response;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response class
|
* Response class
|
||||||
|
@ -18,33 +16,44 @@ module.exports = Response;
|
||||||
* @param Object opts Response options
|
* @param Object opts Response options
|
||||||
* @return Void
|
* @return Void
|
||||||
*/
|
*/
|
||||||
function Response(body, opts) {
|
export default class Response extends Body {
|
||||||
|
constructor(body, opts = {}) {
|
||||||
|
super(body, opts);
|
||||||
|
|
||||||
opts = opts || {};
|
this.url = opts.url;
|
||||||
|
this.status = opts.status || 200;
|
||||||
|
this.statusText = opts.statusText || STATUS_CODES[this.status];
|
||||||
|
this.headers = new Headers(opts.headers);
|
||||||
|
}
|
||||||
|
|
||||||
this.url = opts.url;
|
/**
|
||||||
this.status = opts.status || 200;
|
* Convenience property representing if the request ended normally
|
||||||
this.statusText = opts.statusText || http.STATUS_CODES[this.status];
|
*/
|
||||||
this.headers = new Headers(opts.headers);
|
get ok() {
|
||||||
this.ok = this.status >= 200 && this.status < 300;
|
return this.status >= 200 && this.status < 300;
|
||||||
|
}
|
||||||
|
|
||||||
Body.call(this, body, opts);
|
/**
|
||||||
|
* Clone this response
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
clone() {
|
||||||
|
|
||||||
|
return new Response(clone(this), {
|
||||||
|
url: this.url
|
||||||
|
, status: this.status
|
||||||
|
, statusText: this.statusText
|
||||||
|
, headers: this.headers
|
||||||
|
, ok: this.ok
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used by `Object.prototype.toString()`.
|
||||||
|
*/
|
||||||
|
get [Symbol.toStringTag]() {
|
||||||
|
return 'Response';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.prototype = Object.create(Body.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone this response
|
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
|
||||||
Response.prototype.clone = function() {
|
|
||||||
return new Response(this._clone(this), {
|
|
||||||
url: this.url
|
|
||||||
, status: this.status
|
|
||||||
, statusText: this.statusText
|
|
||||||
, headers: this.headers
|
|
||||||
, ok: this.ok
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
588
test/server.js
588
test/server.js
|
@ -1,337 +1,331 @@
|
||||||
|
import 'babel-polyfill';
|
||||||
|
import * as http from 'http';
|
||||||
|
import { parse } from 'url';
|
||||||
|
import * as zlib from 'zlib';
|
||||||
|
import * as stream from 'stream';
|
||||||
|
import { convert } from 'encoding';
|
||||||
|
import { multipart as Multipart } from 'parted';
|
||||||
|
|
||||||
var http = require('http');
|
export default class TestServer {
|
||||||
var parse = require('url').parse;
|
constructor() {
|
||||||
var zlib = require('zlib');
|
this.server = http.createServer(this.router);
|
||||||
var stream = require('stream');
|
this.port = 30001;
|
||||||
var convert = require('encoding').convert;
|
this.hostname = 'localhost';
|
||||||
var Multipart = require('parted').multipart;
|
this.server.on('error', function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
module.exports = TestServer;
|
});
|
||||||
|
this.server.on('connection', function(socket) {
|
||||||
function TestServer() {
|
socket.setTimeout(1500);
|
||||||
this.server = http.createServer(this.router);
|
|
||||||
this.port = 30001;
|
|
||||||
this.hostname = 'localhost';
|
|
||||||
this.server.on('error', function(err) {
|
|
||||||
console.log(err.stack);
|
|
||||||
});
|
|
||||||
this.server.on('connection', function(socket) {
|
|
||||||
socket.setTimeout(1500);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
TestServer.prototype.start = function(cb) {
|
|
||||||
this.server.listen(this.port, this.hostname, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestServer.prototype.stop = function(cb) {
|
|
||||||
this.server.close(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestServer.prototype.router = function(req, res) {
|
|
||||||
|
|
||||||
var p = parse(req.url).pathname;
|
|
||||||
|
|
||||||
if (p === '/hello') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end('world');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/plain') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end('text');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/options') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
|
|
||||||
res.end('hello world');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/html') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end('<html></html>');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/json') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.end(JSON.stringify({
|
|
||||||
name: 'value'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/gzip') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Encoding', 'gzip');
|
|
||||||
zlib.gzip('hello world', function(err, buffer) {
|
|
||||||
res.end(buffer);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/deflate') {
|
start(cb) {
|
||||||
res.statusCode = 200;
|
this.server.listen(this.port, this.hostname, cb);
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Encoding', 'deflate');
|
|
||||||
zlib.deflate('hello world', function(err, buffer) {
|
|
||||||
res.end(buffer);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/deflate-raw') {
|
stop(cb) {
|
||||||
res.statusCode = 200;
|
this.server.close(cb);
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Encoding', 'deflate');
|
|
||||||
zlib.deflateRaw('hello world', function(err, buffer) {
|
|
||||||
res.end(buffer);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/sdch') {
|
router(req, res) {
|
||||||
res.statusCode = 200;
|
let p = parse(req.url).pathname;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Encoding', 'sdch');
|
|
||||||
res.end('fake sdch string');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/invalid-content-encoding') {
|
if (p === '/hello') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.setHeader('Content-Encoding', 'gzip');
|
res.end('world');
|
||||||
res.end('fake gzip string');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/timeout') {
|
if (p === '/plain') {
|
||||||
setTimeout(function() {
|
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.end('text');
|
res.end('text');
|
||||||
}, 1000);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/slow') {
|
if (p === '/options') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
|
||||||
res.write('test');
|
res.end('hello world');
|
||||||
setTimeout(function() {
|
}
|
||||||
res.end('test');
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/cookie') {
|
if (p === '/html') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Set-Cookie', ['a=1', 'b=1']);
|
res.setHeader('Content-Type', 'text/html');
|
||||||
res.end('cookie');
|
res.end('<html></html>');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/size/chunk') {
|
if (p === '/json') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
setTimeout(function() {
|
res.end(JSON.stringify({
|
||||||
|
name: 'value'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/gzip') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
|
zlib.gzip('hello world', function(err, buffer) {
|
||||||
|
res.end(buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/deflate') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.setHeader('Content-Encoding', 'deflate');
|
||||||
|
zlib.deflate('hello world', function(err, buffer) {
|
||||||
|
res.end(buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/deflate-raw') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.setHeader('Content-Encoding', 'deflate');
|
||||||
|
zlib.deflateRaw('hello world', function(err, buffer) {
|
||||||
|
res.end(buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/sdch') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.setHeader('Content-Encoding', 'sdch');
|
||||||
|
res.end('fake sdch string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/invalid-content-encoding') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
|
res.end('fake gzip string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/timeout') {
|
||||||
|
setTimeout(function() {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.end('text');
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/slow') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.write('test');
|
res.write('test');
|
||||||
}, 50);
|
setTimeout(function() {
|
||||||
setTimeout(function() {
|
res.end('test');
|
||||||
res.end('test');
|
}, 1000);
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/size/long') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end('testtest');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/gbk') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(convert('<meta charset="gbk"><div>中文</div>', 'gbk'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/gb2312') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>', 'gb2312'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/shift-jis') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html; charset=Shift-JIS');
|
|
||||||
res.end(convert('<div>日本語</div>', 'Shift_JIS'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/euc-jp') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/xml');
|
|
||||||
res.end(convert('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>', 'EUC-JP'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/utf8') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.end('中文');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/order1') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'charset=gbk; text/plain');
|
|
||||||
res.end(convert('中文', 'gbk'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/order2') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1');
|
|
||||||
res.end(convert('中文', 'gbk'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/chunked') {
|
|
||||||
res.statusCode = 200;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.setHeader('Transfer-Encoding', 'chunked');
|
|
||||||
var padding = 'a';
|
|
||||||
for (var i = 0; i < 10; i++) {
|
|
||||||
res.write(padding);
|
|
||||||
}
|
}
|
||||||
res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>', 'Shift_JIS'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/encoding/invalid') {
|
if (p === '/cookie') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Set-Cookie', ['a=1', 'b=1']);
|
||||||
res.setHeader('Transfer-Encoding', 'chunked');
|
res.end('cookie');
|
||||||
// because node v0.12 doesn't have str.repeat
|
|
||||||
var padding = new Array(120 + 1).join('a');
|
|
||||||
for (var i = 0; i < 10; i++) {
|
|
||||||
res.write(padding);
|
|
||||||
}
|
}
|
||||||
res.end(convert('中文', 'gbk'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/redirect/301') {
|
if (p === '/size/chunk') {
|
||||||
res.statusCode = 301;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/inspect');
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.end();
|
setTimeout(function() {
|
||||||
}
|
res.write('test');
|
||||||
|
}, 50);
|
||||||
|
setTimeout(function() {
|
||||||
|
res.end('test');
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
if (p === '/redirect/302') {
|
if (p === '/size/long') {
|
||||||
res.statusCode = 302;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/inspect');
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.end();
|
res.end('testtest');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/redirect/303') {
|
if (p === '/encoding/gbk') {
|
||||||
res.statusCode = 303;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/inspect');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
res.end();
|
res.end(convert('<meta charset="gbk"><div>中文</div>', 'gbk'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/redirect/307') {
|
if (p === '/encoding/gb2312') {
|
||||||
res.statusCode = 307;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/inspect');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
res.end();
|
res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>', 'gb2312'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/redirect/308') {
|
if (p === '/encoding/shift-jis') {
|
||||||
res.statusCode = 308;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/inspect');
|
res.setHeader('Content-Type', 'text/html; charset=Shift-JIS');
|
||||||
res.end();
|
res.end(convert('<div>日本語</div>', 'Shift_JIS'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/redirect/chain') {
|
if (p === '/encoding/euc-jp') {
|
||||||
res.statusCode = 301;
|
res.statusCode = 200;
|
||||||
res.setHeader('Location', '/redirect/301');
|
res.setHeader('Content-Type', 'text/xml');
|
||||||
res.end();
|
res.end(convert('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>', 'EUC-JP'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/error/redirect') {
|
if (p === '/encoding/utf8') {
|
||||||
res.statusCode = 301;
|
res.statusCode = 200;
|
||||||
//res.setHeader('Location', '/inspect');
|
res.end('中文');
|
||||||
res.end();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/error/400') {
|
if (p === '/encoding/order1') {
|
||||||
res.statusCode = 400;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'charset=gbk; text/plain');
|
||||||
res.end('client error');
|
res.end(convert('中文', 'gbk'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/error/404') {
|
if (p === '/encoding/order2') {
|
||||||
res.statusCode = 404;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Encoding', 'gzip');
|
res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1');
|
||||||
res.end();
|
res.end(convert('中文', 'gbk'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/error/500') {
|
if (p === '/encoding/chunked') {
|
||||||
res.statusCode = 500;
|
res.statusCode = 200;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
res.end('server error');
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
}
|
const padding = 'a';
|
||||||
|
res.write(padding.repeat(10));
|
||||||
|
res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>', 'Shift_JIS'));
|
||||||
|
}
|
||||||
|
|
||||||
if (p === '/error/reset') {
|
if (p === '/encoding/invalid') {
|
||||||
res.destroy();
|
res.statusCode = 200;
|
||||||
}
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.setHeader('Transfer-Encoding', 'chunked');
|
||||||
|
const padding = 'a'.repeat(120);
|
||||||
|
res.write(padding.repeat(10));
|
||||||
|
res.end(convert('中文', 'gbk'));
|
||||||
|
}
|
||||||
|
|
||||||
if (p === '/error/json') {
|
if (p === '/redirect/301') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 301;
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Location', '/inspect');
|
||||||
res.end('invalid json');
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/no-content') {
|
if (p === '/redirect/302') {
|
||||||
res.statusCode = 204;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.setHeader('Location', '/inspect');
|
||||||
}
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
if (p === '/no-content/gzip') {
|
if (p === '/redirect/303') {
|
||||||
res.statusCode = 204;
|
res.statusCode = 303;
|
||||||
res.setHeader('Content-Encoding', 'gzip');
|
res.setHeader('Location', '/inspect');
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/not-modified') {
|
if (p === '/redirect/307') {
|
||||||
res.statusCode = 304;
|
res.statusCode = 307;
|
||||||
res.end();
|
res.setHeader('Location', '/inspect');
|
||||||
}
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
if (p === '/not-modified/gzip') {
|
if (p === '/redirect/308') {
|
||||||
res.statusCode = 304;
|
res.statusCode = 308;
|
||||||
res.setHeader('Content-Encoding', 'gzip');
|
res.setHeader('Location', '/inspect');
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p === '/inspect') {
|
if (p === '/redirect/chain') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 301;
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Location', '/redirect/301');
|
||||||
var body = '';
|
res.end();
|
||||||
req.on('data', function(c) { body += c });
|
}
|
||||||
req.on('end', function() {
|
|
||||||
res.end(JSON.stringify({
|
|
||||||
method: req.method,
|
|
||||||
url: req.url,
|
|
||||||
headers: req.headers,
|
|
||||||
body: body
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p === '/multipart') {
|
if (p === '/error/redirect') {
|
||||||
res.statusCode = 200;
|
res.statusCode = 301;
|
||||||
res.setHeader('Content-Type', 'application/json');
|
//res.setHeader('Location', '/inspect');
|
||||||
var parser = new Multipart(req.headers['content-type']);
|
res.end();
|
||||||
var body = '';
|
}
|
||||||
parser.on('part', function(field, part) {
|
|
||||||
body += field + '=' + part;
|
if (p === '/error/400') {
|
||||||
});
|
res.statusCode = 400;
|
||||||
parser.on('end', function() {
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.end(JSON.stringify({
|
res.end('client error');
|
||||||
method: req.method,
|
}
|
||||||
url: req.url,
|
|
||||||
headers: req.headers,
|
if (p === '/error/404') {
|
||||||
body: body
|
res.statusCode = 404;
|
||||||
}));
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
});
|
res.end();
|
||||||
req.pipe(parser);
|
}
|
||||||
|
|
||||||
|
if (p === '/error/500') {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
|
res.end('server error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/error/reset') {
|
||||||
|
res.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/error/json') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
res.end('invalid json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/no-content') {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/no-content/gzip') {
|
||||||
|
res.statusCode = 204;
|
||||||
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/not-modified') {
|
||||||
|
res.statusCode = 304;
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/not-modified/gzip') {
|
||||||
|
res.statusCode = 304;
|
||||||
|
res.setHeader('Content-Encoding', 'gzip');
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/inspect') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
let body = '';
|
||||||
|
req.on('data', function(c) { body += c });
|
||||||
|
req.on('end', function() {
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
method: req.method,
|
||||||
|
url: req.url,
|
||||||
|
headers: req.headers,
|
||||||
|
body
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '/multipart') {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
const parser = new Multipart(req.headers['content-type']);
|
||||||
|
let body = '';
|
||||||
|
parser.on('part', function(field, part) {
|
||||||
|
body += field + '=' + part;
|
||||||
|
});
|
||||||
|
parser.on('end', function() {
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
method: req.method,
|
||||||
|
url: req.url,
|
||||||
|
headers: req.headers,
|
||||||
|
body: body
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
req.pipe(parser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
691
test/test.js
691
test/test.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue