UNPKG

five-server

Version:

Development Server with Live Reload Capability. (Maintained Fork of Live Server)

195 lines 8.29 kB
"use strict"; /** * @copyright * Copyright (c) 2014 Andrew Kelley * Copyright (c) 2021 Yannick Deubel (https://github.com/yandeu) * * @license {@link https://github.com/yandeu/five-server/blob/main/LICENSE LICENSE} * * @description * copied and modified from proxy-middleware@0.15.0 (https://github.com/gonzalocasas/node-proxy-middleware/blob/master/index.js) * previously licensed under MIT (https://github.com/gonzalocasas/node-proxy-middleware/blob/master/LICENSE) */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.proxyMiddleware = void 0; const injectCode_1 = require("./injectCode"); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const os_1 = __importDefault(require("os")); const owns = {}.hasOwnProperty; const proxyMiddleware = (options, baseURL, injectBody) => { // enable ability to quickly pass a url for shorthand setup if (typeof options === 'string') { options = require('url').parse(options); } const httpLib = options.protocol === 'https:' ? https_1.default : http_1.default; const request = httpLib.request; options = options || {}; // options.hostname = options.hostname // options.port = options.port options.pathname = options.pathname || '/'; const doRequest = async (req, res, next) => { let url = req.url; // You can pass the route within the options, as well if (typeof options.route === 'string') { if (url === options.route) { url = ''; } else if (url.slice(0, options.route.length) === options.route) { url = url.slice(options.route.length); } else { return next(); } } // options for this request const opts = extend({}, options); if (url && url.charAt(0) === '?') { // prevent /api/resource/?offset=0 if (options.pathname.length > 1 && options.pathname.charAt(options.pathname.length - 1) === '/') { opts.path = options.pathname.substring(0, options.pathname.length - 1) + url; } else { opts.path = options.pathname + url; } } else if (url) { opts.path = slashJoin(options.pathname, url); } else { opts.path = options.pathname; } opts.method = req.method; opts.headers = options.headers ? merge(req.headers, options.headers) : req.headers; applyViaHeader(req.headers, opts, opts.headers); if (!options.preserveHost) { // Forwarding the host breaks dotcloud delete opts.headers.host; } const MAX_RETRY = 20; const RETRY_TIMEOUT = 200; const myReq = request(opts, (request) => { const statusCode = request.statusCode; const headers = request.headers; const location = headers.location; const redirectCodes = !!statusCode && statusCode > 300 && statusCode < 304; // Fix the location (makes absolute path) if ((redirectCodes || statusCode === 201) && location && location.indexOf(options.href) > -1) headers.location = location.replace(options.href, slashJoin('/', slashJoin(options.route || '', ''))); // handle redirects if (statusCode && redirectCodes && url !== location) { res.writeHead(statusCode, { Location: headers.location }); return res.end(); } request.on('error', function (err) { next(err); }); // do injection here const htmlOrPhp = /\.(php|html)$/.test(url); const contentType1 = /html/.test(request.headers['content-type'] || ''); const contentType2 = /html/.test(request.headers['Content-Type'] || ''); const shouldInject = htmlOrPhp || contentType1 || contentType2; // inject the reload script before proxying if (shouldInject) { const inject = new injectCode_1.Inject(['</head>', '</html>', '</body>'], (0, injectCode_1.code)(url, baseURL, injectBody)); request.pipe(inject).on('finish', () => { // could not inject the script :/ if (!inject.injectTag) inject.data += `<script>console.warn("[Five Server] Could not inject script. Why? This file does probably not include a head, html or body tag.");</script>`; applyViaHeader(request.headers, opts, request.headers); rewriteCookieHosts(request.headers, opts, request.headers, req); // request.headers['content-type'] = 'text/html; charset=utf-8' request.headers['content-length'] = inject.data.length.toString(); // to inject the script, we had to decode the compression, hence we remove the content-encoding header request.headers['content-encoding'] = ''; res.writeHead(request.statusCode || 200, request.headers); res.end(inject.data); }); } if (!shouldInject) { // simply proxy the request applyViaHeader(request.headers, opts, request.headers); rewriteCookieHosts(request.headers, opts, request.headers, req); res.writeHead(request.statusCode || 500, request.headers); request.pipe(res); } }); myReq.on('error', function (err) { if (!req.retries) req.retries = 0; // retry every x ms (in case your dev-server needs some time to restart) if (req.retries < MAX_RETRY) { setTimeout(() => { req.retries++; return doRequest(req, res, next); }, RETRY_TIMEOUT); } // exit with error else { return next(err); } }); if (req.readable) req.pipe(myReq); else myReq.end(); }; return doRequest; }; exports.proxyMiddleware = proxyMiddleware; function applyViaHeader(existingHeaders, opts, applyTo) { if (!opts.via) return; const viaName = true === opts.via ? os_1.default.hostname() : opts.via; let viaHeader = `1.1 ${viaName}`; if (existingHeaders.via) { viaHeader = `${existingHeaders.via}, ${viaHeader}`; } applyTo.via = viaHeader; } function rewriteCookieHosts(existingHeaders, opts, applyTo, req) { if (!opts.cookieRewrite || !owns.call(existingHeaders, 'set-cookie')) { return; } let existingCookies = existingHeaders['set-cookie']; const rewrittenCookies = [], rewriteHostname = true === opts.cookieRewrite ? os_1.default.hostname() : opts.cookieRewrite; if (!Array.isArray(existingCookies)) { existingCookies = [existingCookies]; } const replacer = (match, _p1, p2) => match.replace(p2, rewriteHostname); for (let i = 0; i < existingCookies.length; i++) { let rewrittenCookie = existingCookies[i].replace(/(Domain=)([^;]+)/i, replacer); if (!req.connection.encrypted) { rewrittenCookie = rewrittenCookie.replace(/;\s*?(Secure)/i, ''); } rewrittenCookies.push(rewrittenCookie); } applyTo['set-cookie'] = rewrittenCookies; } function slashJoin(p1, p2) { let trailing_slash = false; if (p1.length && p1[p1.length - 1] === '/') { trailing_slash = true; } if (trailing_slash && p2.length && p2[0] === '/') { p2 = p2.substring(1); } return p1 + p2; } function extend(obj, src) { for (const key in src) if (owns.call(src, key)) obj[key] = src[key]; return obj; } //merges data without changing state in either argument function merge(src1, src2) { const merged = {}; extend(merged, src1); extend(merged, src2); return merged; } //# sourceMappingURL=proxy.js.map