UNPKG

http-micro

Version:

Micro-framework on top of node's http module

253 lines (252 loc) 9.42 kB
"use strict"; 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; }