From 883ba3ba0223e7743e5e27eae805788b5795c383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pankowski?= Date: Sun, 9 Jun 2019 13:54:59 +0200 Subject: [PATCH] add `--proxy` flag for reverse proxy --- packages/sirv-cli/boot.js | 14 +++++++++++- packages/sirv-cli/index.js | 2 ++ packages/sirv-cli/readme.md | 2 ++ packages/sirv/index.js | 44 +++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/sirv-cli/boot.js b/packages/sirv-cli/boot.js index e128d86..e881457 100644 --- a/packages/sirv-cli/boot.js +++ b/packages/sirv-cli/boot.js @@ -29,6 +29,18 @@ module.exports = function (dir, opts) { dir = resolve(dir || '.'); opts.maxAge = opts.m; + if (opts.proxy) { + let i = opts.proxy.indexOf(':'); + if (i == - 1) throw new Error('missing ":" in proxy argument'); + let url = new URL(opts.proxy.substring(i + 1)); + opts.proxy = { + prefix: opts.proxy.substring(0, i), + hostname: url.hostname, + port: url.port, + path: url.pathname + }; + } + if (opts.cors) { opts.setHeaders = res => { res.setHeader('Access-Control-Allow-Origin', '*'); @@ -48,7 +60,7 @@ module.exports = function (dir, opts) { let uri, dur, start, dash=colors.gray(' ─ '); server.on('request', (req, res) => { start = hrtime(); - req.once('end', _ => { + res.once('close', _ => { dur = hrtime(start); uri = req.originalUrl || req.url; stdout.write(PAD + toTime() + toCode(res.statusCode) + dash + toMS(dur) + dash + uri + '\n'); diff --git a/packages/sirv-cli/index.js b/packages/sirv-cli/index.js index 0e71c26..f08e776 100644 --- a/packages/sirv-cli/index.js +++ b/packages/sirv-cli/index.js @@ -11,6 +11,7 @@ sade('sirv') .example('start public -qeim 31536000') .example('--port 8080 --etag') .example('my-app --dev') + .example('my-app --dev --proxy /api/:http://localhost:4000/') .command('start [dir]', 'Start a static file server.', { default:true }) .option('-D, --dev', 'Enable "dev" mode') .option('-e, --etag', 'Enable "Etag" header') @@ -22,5 +23,6 @@ sade('sirv') .option('-q, --quiet', 'Disable logging to terminal') .option('-H, --host', 'Hostname to bind', 'localhost') .option('-p, --port', 'Port to bind', 5000) + .option('-P, --proxy', 'Enable reverse proxy (path:url)') .action(boot) .parse(process.argv); diff --git a/packages/sirv-cli/readme.md b/packages/sirv-cli/readme.md index f56b4d7..79d3766 100644 --- a/packages/sirv-cli/readme.md +++ b/packages/sirv-cli/readme.md @@ -61,6 +61,7 @@ $ sirv --help $ sirv public --quiet --etag --maxage 31536000 --immutable $ sirv start public -qeim 31536000 $ sirv --port 8080 --etag + $ sirv public --dev --proxy /api/:http://localhost:4000/ ``` ``` @@ -83,6 +84,7 @@ $ sirv start --help -q, --quiet Disable logging to terminal -H, --host Hostname to bind (default localhost) -p, --port Port to bind (default 5000) + -P, --proxy Enable reverse proxy (path:url) -h, --help Displays this message ``` diff --git a/packages/sirv/index.js b/packages/sirv/index.js index caa9be0..6354a00 100644 --- a/packages/sirv/index.js +++ b/packages/sirv/index.js @@ -2,6 +2,7 @@ const fs = require('fs'); const { join, resolve } = require('path'); const parser = require('@polka/url'); const mime = require('mime/lite'); +const http = require('http'); const FILES = {}; const noop = () => {}; @@ -69,6 +70,43 @@ function send(req, res, file, stats, headers={}) { fs.createReadStream(file, opts).pipe(res); } +function proxyMatch(url, opts) { + if (! url.startsWith(opts.prefix)) { + return undefined; + } + if (opts.prefix.endsWith('/')) { + return join(opts.path, url.substring(opts.prefix.length)); + } + if (url.length === opts.prefix.length || url.charAt(opts.prefix.length) === '?') { + return url; + } + return undefined; +} + +function proxy(req, res, opts) { + let url = proxyMatch(req.url, opts); + if (url === undefined) { + return false; + } + let options = { + hostname: opts.hostname, + port: opts.port, + path: url, + method: req.method, + headers: req.headers + }; + let proxyReq = http.request(options, proxyRes => { + res.writeHead(proxyRes.statusCode, proxyRes.headers); + proxyRes.pipe(res); + }); + proxyReq.on('error', _ => { + res.statusCode = 500; + res.end(); + }); + req.pipe(proxyReq); + return true; +} + module.exports = function (dir, opts={}) { dir = resolve(dir || '.'); @@ -78,6 +116,9 @@ module.exports = function (dir, opts={}) { if (opts.dev) { return function (req, res, next) { + if (opts.proxy !== undefined && proxy(req, res, opts.proxy)) { + return; + } let stats, file, uri=decodeURIComponent(req.path || req.pathname || parser(req).pathname); let arr = [uri].concat(toAssume(uri, extensions)).map(x => join(dir, x)).filter(fs.existsSync); while (file = arr.shift()) { @@ -115,6 +156,9 @@ module.exports = function (dir, opts={}) { }); return function (req, res, next) { + if (opts.proxy !== undefined && proxy(req, res, opts.proxy)) { + return; + } let pathname = decodeURIComponent(req.path || req.pathname || parser(req).pathname); let data = FILES[pathname] || find(pathname, extensions); if (!data) return next ? next() : isNotFound(req, res);