mirror of
https://github.com/yml2213/javascript.git
synced 2026-10-20 15:52:50 +08:00
211 lines
5.6 KiB
JavaScript
211 lines
5.6 KiB
JavaScript
'use strict';
|
|
|
|
var fs = require('fs'),
|
|
union = require('union'),
|
|
ecstatic = require('ecstatic'),
|
|
auth = require('basic-auth'),
|
|
httpProxy = require('http-proxy'),
|
|
corser = require('corser'),
|
|
path = require('path'),
|
|
secureCompare = require('secure-compare');
|
|
|
|
// a hacky and direct workaround to fix https://github.com/http-party/http-server/issues/525
|
|
function getCaller() {
|
|
try {
|
|
var stack = new Error().stack;
|
|
var stackLines = stack.split('\n');
|
|
var callerStack = stackLines[3];
|
|
return callerStack.match(/at (.+) \(/)[1];
|
|
}
|
|
catch (error) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
var _pathNormalize = path.normalize;
|
|
path.normalize = function (p) {
|
|
var caller = getCaller();
|
|
var result = _pathNormalize(p);
|
|
// https://github.com/jfhbrook/node-ecstatic/blob/master/lib/ecstatic.js#L20
|
|
if (caller === 'decodePathname') {
|
|
result = result.replace(/\\/g, '/');
|
|
}
|
|
return result;
|
|
};
|
|
|
|
//
|
|
// Remark: backwards compatibility for previous
|
|
// case convention of HTTP
|
|
//
|
|
exports.HttpServer = exports.HTTPServer = HttpServer;
|
|
|
|
/**
|
|
* Returns a new instance of HttpServer with the
|
|
* specified `options`.
|
|
*/
|
|
exports.createServer = function (options) {
|
|
return new HttpServer(options);
|
|
};
|
|
|
|
/**
|
|
* Constructor function for the HttpServer object
|
|
* which is responsible for serving static files along
|
|
* with other HTTP-related features.
|
|
*/
|
|
function HttpServer(options) {
|
|
options = options || {};
|
|
|
|
if (options.root) {
|
|
this.root = options.root;
|
|
}
|
|
else {
|
|
try {
|
|
fs.lstatSync('./public');
|
|
this.root = './public';
|
|
}
|
|
catch (err) {
|
|
this.root = './';
|
|
}
|
|
}
|
|
|
|
this.headers = options.headers || {};
|
|
|
|
this.cache = (
|
|
options.cache === undefined ? 3600 :
|
|
// -1 is a special case to turn off caching.
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Preventing_caching
|
|
options.cache === -1 ? 'no-cache, no-store, must-revalidate' :
|
|
options.cache // in seconds.
|
|
);
|
|
this.showDir = options.showDir !== 'false';
|
|
this.autoIndex = options.autoIndex !== 'false';
|
|
this.showDotfiles = options.showDotfiles;
|
|
this.gzip = options.gzip === true;
|
|
this.brotli = options.brotli === true;
|
|
if (options.ext) {
|
|
this.ext = options.ext === true
|
|
? 'html'
|
|
: options.ext;
|
|
}
|
|
this.contentType = options.contentType ||
|
|
this.ext === 'html' ? 'text/html' : 'application/octet-stream';
|
|
|
|
var before = options.before ? options.before.slice() : [];
|
|
|
|
if (options.logFn) {
|
|
before.push(function (req, res) {
|
|
options.logFn(req, res);
|
|
res.emit('next');
|
|
});
|
|
}
|
|
|
|
if (options.username || options.password) {
|
|
before.push(function (req, res) {
|
|
var credentials = auth(req);
|
|
|
|
// We perform these outside the if to avoid short-circuiting and giving
|
|
// an attacker knowledge of whether the username is correct via a timing
|
|
// attack.
|
|
if (credentials) {
|
|
// if credentials is defined, name and pass are guaranteed to be string
|
|
// type
|
|
var usernameEqual = secureCompare(options.username.toString(), credentials.name);
|
|
var passwordEqual = secureCompare(options.password.toString(), credentials.pass);
|
|
if (usernameEqual && passwordEqual) {
|
|
return res.emit('next');
|
|
}
|
|
}
|
|
|
|
res.statusCode = 401;
|
|
res.setHeader('WWW-Authenticate', 'Basic realm=""');
|
|
res.end('Access denied');
|
|
});
|
|
}
|
|
|
|
if (options.cors) {
|
|
this.headers['Access-Control-Allow-Origin'] = '*';
|
|
this.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range';
|
|
if (options.corsHeaders) {
|
|
options.corsHeaders.split(/\s*,\s*/)
|
|
.forEach(function (h) { this.headers['Access-Control-Allow-Headers'] += ', ' + h; }, this);
|
|
}
|
|
before.push(corser.create(options.corsHeaders ? {
|
|
requestHeaders: this.headers['Access-Control-Allow-Headers'].split(/\s*,\s*/)
|
|
} : null));
|
|
}
|
|
|
|
if (options.robots) {
|
|
before.push(function (req, res) {
|
|
if (req.url === '/robots.txt') {
|
|
res.setHeader('Content-Type', 'text/plain');
|
|
var robots = options.robots === true
|
|
? 'User-agent: *\nDisallow: /'
|
|
: options.robots.replace(/\\n/, '\n');
|
|
|
|
return res.end(robots);
|
|
}
|
|
|
|
res.emit('next');
|
|
});
|
|
}
|
|
|
|
before.push(ecstatic({
|
|
root: this.root,
|
|
cache: this.cache,
|
|
showDir: this.showDir,
|
|
showDotfiles: this.showDotfiles,
|
|
autoIndex: this.autoIndex,
|
|
defaultExt: this.ext,
|
|
gzip: this.gzip,
|
|
brotli: this.brotli,
|
|
contentType: this.contentType,
|
|
handleError: typeof options.proxy !== 'string'
|
|
}));
|
|
|
|
if (typeof options.proxy === 'string') {
|
|
var proxy = httpProxy.createProxyServer({});
|
|
before.push(function (req, res) {
|
|
proxy.web(req, res, {
|
|
target: options.proxy,
|
|
changeOrigin: true
|
|
}, function (err, req, res, target) {
|
|
if (options.logFn) {
|
|
options.logFn(req, res, {
|
|
message: err.message,
|
|
status: res.statusCode });
|
|
}
|
|
res.emit('next');
|
|
});
|
|
});
|
|
}
|
|
|
|
var serverOptions = {
|
|
before: before,
|
|
headers: this.headers,
|
|
onError: function (err, req, res) {
|
|
if (options.logFn) {
|
|
options.logFn(req, res, err);
|
|
}
|
|
|
|
res.end();
|
|
}
|
|
};
|
|
|
|
if (options.https) {
|
|
serverOptions.https = options.https;
|
|
}
|
|
|
|
this.server = union.createServer(serverOptions);
|
|
if (options.timeout !== undefined) {
|
|
this.server.setTimeout(options.timeout);
|
|
}
|
|
}
|
|
|
|
HttpServer.prototype.listen = function () {
|
|
this.server.listen.apply(this.server, arguments);
|
|
};
|
|
|
|
HttpServer.prototype.close = function () {
|
|
return this.server.close();
|
|
};
|