http-micro
Version:
Micro-framework on top of node's http module
253 lines (252 loc) • 9.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./utils");
const lang_1 = require("./lang");
const contentType = require("content-type");
const error_utils_1 = require("./error-utils");
const contentDisposition = require("content-disposition");
class RequestUtils {
static getUpstreamIpAddresses(req) {
let addrs;
let forwardHeaders = req.headers['x-forwarded-for'];
if (forwardHeaders) {
addrs = forwardHeaders
.split(/ *, */)
.filter(x => x);
addrs.push(req.socket.remoteAddress);
}
else {
addrs = [req.socket.remoteAddress];
}
return addrs;
}
static getHost(req) {
let host = req.headers["x-forwarded-host"] || req.headers["host"];
return stripHostBrackets(host);
}
static isEncrypted(req) {
return req.connection.encrypted ? true : false;
}
static getProtocol(req) {
return RequestUtils.isEncrypted(req) ? "https" : "http";
}
static getContentType(req) {
let contentTypeHeader = req.headers["content-type"];
let result = null;
if (contentTypeHeader) {
try {
result = contentType.parse(contentTypeHeader);
}
catch (err) {
throw error_utils_1.intoHttpError(err, 400);
}
}
return result;
}
}
exports.RequestUtils = RequestUtils;
class ResponseUtils {
static send(res, body, headers, code = 200) {
ResponseUtils.setHeaders(res, headers);
ResponseUtils.setStatus(res, code);
if (!body) {
res.end();
return;
}
lang_1.isString(body) ?
ResponseUtils.sendText(res, body) :
ResponseUtils.sendAsJson(res, body);
}
static sendText(res, text) {
ResponseUtils.setContentType(res, "text/plain");
res.end(text);
}
static sendAsJson(res, data, replacer, spaces) {
ResponseUtils.setContentType(res, "application/json");
let payload = utils_1.stringify(data, replacer, spaces);
res.end(payload);
}
static setHeaders(res, headers) {
if (!headers)
return;
// Do the same thing that writeHead does, to ensure compatibility.
let keys = Object.keys(headers);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
res.setHeader(key, headers[key]);
}
}
static setHeader(res, key, value, replace = true) {
if (!replace && res.getHeader(key))
return false;
res.setHeader(key, value);
return true;
}
/**
* Appends a value item to a mutli-value header key. It separates
* values with "," if there's an string value, or a appends to the
* array if there's an existing array. If none exists, creates an
* array with the item.
*
* @param key {String} The header key
* @param value {String} The header value
* @param forceAppend {Boolean} If true, the value will be appended
* regardless of whether the value is already present or not.
* Helpful performance optmization if it's known for certain
* that a value will not exist, as it avoids a regex call.
*/
static appendHeaderValue(res, key, value, forceAppend = false) {
let existing = res.getHeader(key);
if (!existing) {
res.setHeader(key, [value]);
return;
}
if (Array.isArray(existing)) {
if (forceAppend ||
!existing.includes(value)) {
existing.push(value);
res.setHeader(key, existing);
}
}
else {
// Header value is a string seperated by a ",".
let shouldSet = true;
let existingHeader = existing;
if (!forceAppend) {
let pattern = `(?: *, *)?${value}(?: *, *)?`;
if (new RegExp(pattern).test(existingHeader))
shouldSet = false;
}
if (shouldSet)
res.setHeader(key, `${existingHeader}, ${value}`);
}
}
/**
* Removes a value from a multi-value header item. Multi-values
* header can either be a string separated by ", ", or an array.
*
* Note: The value provided should one be a single string, and
* not an array.
*
* @param key {String} Key of the header.
* @param value {String} The string value to be removed from the
* header item.
* @param removeHeaderIfEmpty {Boolean} If true, removes the entire
* header, if the header is empty after removal of the item.
*/
static removeHeaderValue(res, key, value, removeHeaderIfEmpty = true) {
let existing = res.getHeader(key);
if (!existing)
return;
if (Array.isArray(existing)) {
let arr = existing;
let index = arr.findIndex(x => x === value);
if (index > -1) {
arr.splice(index, 1);
if (removeHeaderIfEmpty && arr.length === 0)
res.removeHeader(key);
else
res.setHeader(key, arr);
}
}
else {
let existingHeader = existing;
// Header value is a string seperated by a ",".
// If both the comma's are present, replace the pattern with one
// ", ". Or else, replace it with an empty string, and trim it.
let pattern = `( *, *)?${value}( *, *)?`;
let regex = new RegExp(pattern);
let match = existingHeader.match(regex);
if (match) {
// If match length is 3, then it means both ", " have been
// matched. So, add one ",". Or else, either one of none
// of the comma has been matched. It's a safe assumption
// to remove them entirely.
let v = existingHeader
.replace(match[0], match.length === 3 ? ", " : "")
.trim();
if (!v && removeHeaderIfEmpty)
res.removeHeader(key);
else
res.setHeader(key, v);
}
}
}
static setContentType(res, value, force = false) {
if (res.headersSent)
return;
const ContentTypeKey = "Content-Type";
ResponseUtils.setHeader(res, ContentTypeKey, value, force);
}
static setStatus(res, code, message) {
res.statusCode = code;
if (message)
res.statusMessage = message;
}
static sendStatus(res, code, message, headers) {
ResponseUtils.setHeaders(res, headers);
ResponseUtils.setStatus(res, code, message);
res.end();
}
static sendMethodNotAllowed(res, allowedMethods, reason = null, headers) {
let allowHeaderString;
if (Array.isArray(allowedMethods)) {
if (allowedMethods.length < 1)
throw new Error("allowed methods invalid");
allowHeaderString = allowedMethods.join(", ");
}
else {
if (!allowedMethods)
throw new Error("allowed methods parameter required");
allowHeaderString = allowedMethods;
}
let mergedHeaders;
let allowHeaders = {
"Allow": allowHeaderString,
};
if (headers) {
mergedHeaders = Object.assign({}, headers, allowHeaders);
}
else {
mergedHeaders = allowHeaders;
}
ResponseUtils.sendStatus(res, 405, reason, mergedHeaders);
}
static setContentDisposition(res, filename, type = DispositionKind.Attachment) {
res.setHeader("Content-Disposition", contentDisposition(filename, { type }));
}
static sendNoContent(res, headers) {
ResponseUtils.sendStatus(res, 204, null, headers);
}
static sendResetContent(res, headers) {
ResponseUtils.sendStatus(res, 205, null, headers);
}
static sendBadRequest(res, body, headers) {
ResponseUtils.send(res, body, headers, 400);
}
static sendNotFound(res, reason = null, headers) {
ResponseUtils.send(res, reason, headers, 404);
}
static sendForbidden(res, reason, headers) {
ResponseUtils.send(res, reason, headers, 401);
}
}
exports.ResponseUtils = ResponseUtils;
var DispositionKind;
(function (DispositionKind) {
DispositionKind["Attachment"] = "attachment";
DispositionKind["Inline"] = "inline";
})(DispositionKind = exports.DispositionKind || (exports.DispositionKind = {}));
function stripHostBrackets(host) {
// IPv6 uses [::]:port format.
// Brackets are used to separate the port from
// the address. In this case, remove the brackets,
// and extract the address only.
let offset = host[0] === '['
? host.indexOf(']') + 1
: 0;
let index = host.indexOf(':', offset);
return index !== -1
? host.substring(0, index)
: host;
}