Convert all files to ES2015 (#182)

Elements of this commit come from #140 by @gwicke.
This commit is contained in:
Timothy Gu 2016-10-10 11:50:04 -07:00 committed by GitHub
parent 993d4cdea1
commit 838071247d
8 changed files with 986 additions and 1004 deletions

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "babel -d lib src",
"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",
"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-plugin-istanbul": "^2.0.1",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"babel-register": "^6.16.3",
"bluebird": "^3.3.4",
@ -44,6 +45,7 @@
"resumer": "0.0.0"
},
"dependencies": {
"babel-runtime": "^6.11.6",
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
},
@ -67,6 +69,7 @@
"src/*.js"
],
"require": [
"babel-polyfill",
"babel-register"
],
"sourceMap": false,

View File

@ -5,12 +5,17 @@
* Body interface provides common methods for Request and Response
*/
var convert = require('encoding').convert;
var bodyStream = require('is-stream');
var PassThrough = require('stream').PassThrough;
var FetchError = require('./fetch-error');
import {convert} from 'encoding';
import bodyStream from 'is-stream';
import {PassThrough} from 'stream';
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
@ -19,221 +24,210 @@ module.exports = Body;
* @param Object opts Response options
* @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;
this.size = opts.size || 0;
this.timeout = opts.timeout || 0;
this._raw = [];
this._abort = false;
/**
* Decode response as json
*
* @return Promise
*/
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
*
* @param Mixed instance Response or Request instance
* @return Mixed
*/
Body.prototype._clone = function(instance) {
var p1, p2;
var body = instance.body;
export function clone(instance) {
let p1, p2;
let body = instance.body;
// don't allow cloning a used body
if (instance.bodyUsed) {

View File

@ -5,8 +5,6 @@
* FetchError interface for operational errors
*/
module.exports = FetchError;
/**
* Create FetchError instance
*
@ -15,7 +13,7 @@ module.exports = FetchError;
* @param String systemError For Node.js system error
* @return FetchError
*/
function FetchError(message, type, systemError) {
export default function FetchError(message, type, systemError) {
// hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);

View File

@ -5,50 +5,38 @@
* a request API compatible with window.fetch
*/
var parse_url = require('url').parse;
var resolve_url = require('url').resolve;
var http = require('http');
var https = require('https');
var zlib = require('zlib');
var stream = require('stream');
import {resolve as resolve_url} from 'url';
import * as http from 'http';
import * as https from 'https';
import * as zlib from 'zlib';
import {PassThrough} from 'stream';
var Body = require('./body');
var Response = require('./response');
import Body from './body';
import Response from './response';
import Headers from './headers';
var Request = require('./request');
var FetchError = require('./fetch-error');
// commonjs
module.exports = Fetch;
// es6 default export compatibility
module.exports.default = module.exports;
import Request from './request';
import FetchError from './fetch-error';
/**
* Fetch class
* Fetch function
*
* @param Mixed url Absolute url or Request instance
* @param Object opts Fetch options
* @return Promise
*/
function Fetch(url, opts) {
// allow call as function
if (!(this instanceof Fetch))
return new Fetch(url, opts);
function fetch(url, opts) {
// allow custom promise
if (!Fetch.Promise) {
throw new Error('native promise missing, set Fetch.Promise to your favorite alternative');
if (!fetch.Promise) {
throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
}
Body.Promise = Fetch.Promise;
var self = this;
Body.Promise = fetch.Promise;
// wrap http.request into fetch
return new Fetch.Promise(function(resolve, reject) {
return new fetch.Promise((resolve, reject) => {
// build request object
var options = new Request(url, opts);
const options = new Request(url, opts);
if (!options.protocol || !options.hostname) {
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');
}
var send;
if (options.protocol === 'https:') {
send = https.request;
} else {
send = http.request;
}
const send = (options.protocol === 'https:' ? https : http).request;
// normalize headers
var headers = new Headers(options.headers);
const headers = new Headers(options.headers);
if (options.compress) {
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
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
@ -116,40 +99,40 @@ function Fetch(url, opts) {
}
// send request
var req = send(options);
var reqTimeout;
const req = send(options);
let reqTimeout;
if (options.timeout) {
req.once('socket', function(socket) {
reqTimeout = setTimeout(function() {
req.once('socket', socket => {
reqTimeout = setTimeout(() => {
req.abort();
reject(new FetchError('network timeout at: ' + options.url, 'request-timeout'));
reject(new FetchError(`network timeout at: ${options.url}`, 'request-timeout'));
}, options.timeout);
});
}
req.on('error', function(err) {
req.on('error', err => {
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);
// handle redirect
if (self.isRedirect(res.statusCode) && options.redirect !== 'manual') {
if (fetch.isRedirect(res.statusCode) && options.redirect !== 'manual') {
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;
}
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;
}
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;
}
@ -164,19 +147,19 @@ function Fetch(url, opts) {
options.counter++;
resolve(Fetch(resolve_url(options.url, res.headers.location), options));
resolve(fetch(resolve_url(options.url, res.headers.location), options));
return;
}
// 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')) {
headers.set('location', resolve_url(options.url, headers.get('location')));
}
// prepare response
var body = res.pipe(new stream.PassThrough());
var response_options = {
let body = res.pipe(new PassThrough());
const response_options = {
url: options.url
, status: res.statusCode
, statusText: res.statusMessage
@ -186,7 +169,7 @@ function Fetch(url, opts) {
};
// response object
var output;
let output;
// in following scenarios we ignore compression support
// 1. compression support is disabled
@ -201,7 +184,7 @@ function Fetch(url, opts) {
}
// otherwise, check for gzip or deflate
var name = headers.get('content-encoding');
let name = headers.get('content-encoding');
// for gzip
if (name == 'gzip' || name == 'x-gzip') {
@ -214,8 +197,8 @@ function Fetch(url, opts) {
} else if (name == 'deflate' || name == 'x-deflate') {
// handle the infamous raw deflate response from old servers
// a hack for old IIS and Apache servers
var raw = res.pipe(new stream.PassThrough());
raw.once('data', function(chunk) {
const raw = res.pipe(new PassThrough());
raw.once('data', chunk => {
// see http://stackoverflow.com/questions/37519828
if ((chunk[0] & 0x0F) === 0x08) {
body = body.pipe(zlib.createInflate());
@ -254,18 +237,18 @@ function Fetch(url, opts) {
};
module.exports = fetch;
/**
* Redirect code matching
*
* @param Number code Status code
* @return Boolean
*/
Fetch.prototype.isRedirect = function(code) {
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
}
fetch.isRedirect = code => code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
// expose Promise
Fetch.Promise = global.Promise;
Fetch.Response = Response;
Fetch.Headers = Headers;
Fetch.Request = Request;
fetch.Promise = global.Promise;
fetch.Response = Response;
fetch.Headers = Headers;
fetch.Request = Request;

View File

@ -5,11 +5,9 @@
* Request class contains server only options
*/
var parse_url = require('url').parse;
import Headers from './headers';
var Body = require('./body');
module.exports = Request;
import { parse as parse_url } from 'url';
import Headers from './headers.js';
import Body, { clone } from './body';
/**
* Request class
@ -18,58 +16,62 @@ module.exports = Request;
* @param Object init Custom options
* @return Void
*/
function Request(input, init) {
var url, url_parsed;
export default class Request extends Body {
constructor(input, init = {}) {
let 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);
// normalize input
if (!(input instanceof Request)) {
url = input;
url_parsed = parse_url(url);
input = {};
} else {
url = input.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';
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;
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;
/**
* Tag used by `Object.prototype.toString()`.
*/
get [Symbol.toStringTag]() {
return 'Request';
}
}
Request.prototype = Object.create(Body.prototype);
/**
* Clone this request
*
* @return Request
*/
Request.prototype.clone = function() {
return new Request(this);
};

View File

@ -5,11 +5,9 @@
* Response class provides content decoding
*/
var http = require('http');
import Headers from './headers';
var Body = require('./body');
module.exports = Response;
import { STATUS_CODES } from 'http';
import Headers from './headers.js';
import Body, { clone } from './body';
/**
* Response class
@ -18,33 +16,44 @@ module.exports = Response;
* @param Object opts Response options
* @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;
this.statusText = opts.statusText || http.STATUS_CODES[this.status];
this.headers = new Headers(opts.headers);
this.ok = this.status >= 200 && this.status < 300;
/**
* Convenience property representing if the request ended normally
*/
get ok() {
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
});
};

View File

@ -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');
var parse = require('url').parse;
var zlib = require('zlib');
var stream = require('stream');
var convert = require('encoding').convert;
var Multipart = require('parted').multipart;
module.exports = TestServer;
function TestServer() {
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);
export default class TestServer {
constructor() {
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);
});
}
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);
});
start(cb) {
this.server.listen(this.port, this.hostname, cb);
}
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);
});
stop(cb) {
this.server.close(cb);
}
if (p === '/sdch') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Encoding', 'sdch');
res.end('fake sdch string');
}
router(req, res) {
let p = parse(req.url).pathname;
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 === '/hello') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('world');
}
if (p === '/timeout') {
setTimeout(function() {
if (p === '/plain') {
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');
setTimeout(function() {
res.end('test');
}, 1000);
}
if (p === '/options') {
res.statusCode = 200;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.end('hello world');
}
if (p === '/cookie') {
res.statusCode = 200;
res.setHeader('Set-Cookie', ['a=1', 'b=1']);
res.end('cookie');
}
if (p === '/html') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<html></html>');
}
if (p === '/size/chunk') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
setTimeout(function() {
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') {
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');
}, 50);
setTimeout(function() {
res.end('test');
}, 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);
setTimeout(function() {
res.end('test');
}, 1000);
}
res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>', 'Shift_JIS'));
}
if (p === '/encoding/invalid') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.setHeader('Transfer-Encoding', 'chunked');
// 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);
if (p === '/cookie') {
res.statusCode = 200;
res.setHeader('Set-Cookie', ['a=1', 'b=1']);
res.end('cookie');
}
res.end(convert('中文', 'gbk'));
}
if (p === '/redirect/301') {
res.statusCode = 301;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/size/chunk') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
setTimeout(function() {
res.write('test');
}, 50);
setTimeout(function() {
res.end('test');
}, 100);
}
if (p === '/redirect/302') {
res.statusCode = 302;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/size/long') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('testtest');
}
if (p === '/redirect/303') {
res.statusCode = 303;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/encoding/gbk') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(convert('<meta charset="gbk"><div>中文</div>', 'gbk'));
}
if (p === '/redirect/307') {
res.statusCode = 307;
res.setHeader('Location', '/inspect');
res.end();
}
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 === '/redirect/308') {
res.statusCode = 308;
res.setHeader('Location', '/inspect');
res.end();
}
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 === '/redirect/chain') {
res.statusCode = 301;
res.setHeader('Location', '/redirect/301');
res.end();
}
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 === '/error/redirect') {
res.statusCode = 301;
//res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/encoding/utf8') {
res.statusCode = 200;
res.end('中文');
}
if (p === '/error/400') {
res.statusCode = 400;
res.setHeader('Content-Type', 'text/plain');
res.end('client error');
}
if (p === '/encoding/order1') {
res.statusCode = 200;
res.setHeader('Content-Type', 'charset=gbk; text/plain');
res.end(convert('中文', 'gbk'));
}
if (p === '/error/404') {
res.statusCode = 404;
res.setHeader('Content-Encoding', 'gzip');
res.end();
}
if (p === '/encoding/order2') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1');
res.end(convert('中文', 'gbk'));
}
if (p === '/error/500') {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('server error');
}
if (p === '/encoding/chunked') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
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') {
res.destroy();
}
if (p === '/encoding/invalid') {
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') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end('invalid json');
}
if (p === '/redirect/301') {
res.statusCode = 301;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/no-content') {
res.statusCode = 204;
res.end();
}
if (p === '/redirect/302') {
res.statusCode = 302;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/no-content/gzip') {
res.statusCode = 204;
res.setHeader('Content-Encoding', 'gzip');
res.end();
}
if (p === '/redirect/303') {
res.statusCode = 303;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/not-modified') {
res.statusCode = 304;
res.end();
}
if (p === '/redirect/307') {
res.statusCode = 307;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/not-modified/gzip') {
res.statusCode = 304;
res.setHeader('Content-Encoding', 'gzip');
res.end();
}
if (p === '/redirect/308') {
res.statusCode = 308;
res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/inspect') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
var 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: body
}));
});
}
if (p === '/redirect/chain') {
res.statusCode = 301;
res.setHeader('Location', '/redirect/301');
res.end();
}
if (p === '/multipart') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
var parser = new Multipart(req.headers['content-type']);
var 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);
if (p === '/error/redirect') {
res.statusCode = 301;
//res.setHeader('Location', '/inspect');
res.end();
}
if (p === '/error/400') {
res.statusCode = 400;
res.setHeader('Content-Type', 'text/plain');
res.end('client error');
}
if (p === '/error/404') {
res.statusCode = 404;
res.setHeader('Content-Encoding', 'gzip');
res.end();
}
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);
}
}
}

File diff suppressed because it is too large Load Diff