diff --git a/Readme.md b/Readme.md index 26b777b..e6fc683 100644 --- a/Readme.md +++ b/Readme.md @@ -99,11 +99,8 @@ Pass in a relative path to your own ssl cert. Default: `false`. #### `-S, --ssl-key=PATH` Pass in a relative path to your own ssl key. Default: `false`. -#### `-x, --proxy-prefix=PREFIX` -Proxy requests to paths starting with `PREFIX` to another server. Requires `--proxy-host` and should be a string like `/api`. Defaults to not proxying - -#### `-y, --proxy-host=HOST` -Proxy requests to another server running at `HOST`. Requires `--proxy-prefix` and should be a full URL, eg. `http://localhost:9000`. Defaults to not proxying +#### `--proxy=PREFIX:HOST` +Proxy requests for paths starting with the passed in prefix to another server. Should be a string like `/api:http://localhost:3000` #### `-d, --dir=PATH` The base for static content. Default: `.`. diff --git a/bin/elm-live.js b/bin/elm-live.js index e90c30f..3e9dd8c 100755 --- a/bin/elm-live.js +++ b/bin/elm-live.js @@ -5,6 +5,10 @@ const chalk = require('chalk') const mime = require('mime') const { hotReloadOn, flagError, flagErrorMsgs, help } = require('../lib/src/messages') +function collect(value, previous) { + return previous.concat([value]); +} + program .version(require('../package.json').version) .arguments('') @@ -22,6 +26,12 @@ program .option('-k, --ssl-key [key]', 'Pass in a relative path to your own ssl key.', false) // Proxy + .option( + '--proxy [proxy]', + `Proxy requests for paths starting with the passed in prefix to another server. Should be a string like ${chalk.cyan.underline('/api:http://localhost:3000')}.`, + collect, + [] + ) .option( '-x, --proxy-prefix [prefix]', `Proxy requests for paths starting with the passed in prefix to another server. Requires ${chalk.cyan.underline('--proxyHost')} and should be a string like ${chalk.cyan.underline('/api')}.` diff --git a/lib/src/init.js b/lib/src/init.js index 8de1d2f..bad1984 100644 --- a/lib/src/init.js +++ b/lib/src/init.js @@ -44,6 +44,7 @@ const init = program => ({ log: console.log, open: program.open || false, port: program.port || 8000, + proxies: program.proxy || [], proxyPrefix: program.proxyPrefix || false, proxyHost: program.proxyHost || false, pushstate: program.pushstate || false, @@ -56,6 +57,7 @@ const init = program => ({ verbose: program.verbose || false }) + /* |------------------------------------------------------------------------------- | Export diff --git a/lib/src/messages.js b/lib/src/messages.js index 31ce1df..7c71176 100644 --- a/lib/src/messages.js +++ b/lib/src/messages.js @@ -121,6 +121,9 @@ ${header} Server has been started! Server details below: - Website URL: ${chalk.blue.bold((model.ssl ? 'https://' : 'http://') + (model.host || 'localhost') + ':' + (model.port || 1234))} - Serving files from: ${chalk.blue.bold(model.dir || process.cwd())} + ${model.proxies && model.proxies.length + ? ` - Proxying requests ${chalk.blue.bold(model.proxies)}` + : ''} ${model.proxyPrefix && model.proxyHost ? ` - Proxying requests starting with ${chalk.blue.bold(model.proxyPrefix)} to ${chalk.blue.bold(model.proxyHost)}` : ''} diff --git a/lib/src/start.js b/lib/src/start.js index 63a8b83..2ebb218 100644 --- a/lib/src/start.js +++ b/lib/src/start.js @@ -60,13 +60,13 @@ const RELOAD_TYPE = 'reload' const STATIC_TYPE = 'static' /** - * getProxy :: { proxyPrefix: String, proxyHost: String } -> ProxyServer|Bool + * getProxyServer :: { proxyPrefix: String, proxyHost: String } -> ProxyServer|Bool * * This function takes in the proxy values and makes sure they are valid before returning the Proxy Server. If the values are invalid it returns false. * * TODO: Turn this into a Result ADT. **/ -const getProxy = ({ proxyPrefix, proxyHost }) => (typeof proxyPrefix === 'string' && typeof proxyHost === 'string') +const getProxyServer = ({ proxies, proxyPrefix, proxyHost }) => ((Array.isArray(proxies) && proxies.length) || typeof proxyPrefix === 'string' && typeof proxyHost === 'string') ? require('http-proxy').createProxyServer() : false @@ -80,13 +80,34 @@ const isRoot = ({ pathname, fileType, pushstate }) => pathname === '/' || pathname === '') +function extractPrefix(proxy) { + return proxy.substr(0, proxy.indexOf(':')) +} + +function extractHost(proxy) { + return proxy.substr(proxy.indexOf(':') + 1) +} + +function getProxy(pathname, proxies, proxyPrefix, proxyHost) { + const matches = proxies.filter((proxy) => + pathname.startsWith(extractPrefix(proxy)) + ); + if (matches.length) { + return [extractPrefix(matches[0]), extractHost(matches[0])]; + } + else if (pathname.startsWith(proxyPrefix)) { + return [proxyPrefix, proxyHost] + } + return []; +} + /** * getRequestType :: ({ pathname: String, fileType: String }, { proxyPrefix: String, pushstate: Bool }) -> String * * This function returns one of the request type constants defined above using some of the passed in variables from the request. **/ -function getRequestType ({ pathname, fileType }, { proxyPrefix, pushstate }) { - if (pathname.startsWith(proxyPrefix)) { +function getRequestType ({ pathname, fileType }, { proxies, proxyPrefix, proxyHost, pushstate }) { + if (getProxy(pathname, proxies, proxyPrefix, proxyHost).length) { return PROXY_TYPE } else if ( isRoot({ pathname, pushstate, fileType }) || @@ -177,16 +198,16 @@ function resolveReload (res) { * This function is the server handler function. It takes the model and the Request and Resolver and parses the Request and returns the correct files to the client **/ function handler (model) { - const proxy = getProxy(model) + const proxyServer = getProxyServer(model) const serve = serveStatic(model.dir, { index: ['index.html', 'index.htm'] }) return function (req, res) { const url = parseUrl(req.url, model) - - if (proxy && url.isType(PROXY_TYPE)) { - req.url = req.url.replace(model.proxyPrefix, '') - proxy.web(req, res, { - target: model.proxyHost, + if (proxyServer && url.isType(PROXY_TYPE)) { + const [proxyPrefix, proxyHost] = getProxy(req.url, model.proxies, model.proxyPath, model.proxyHost); + req.url = req.url.replace(proxyPrefix, '') + proxyServer.web(req, res, { + target: proxyHost, changeOrigin: true, hostRewrite: true, xfwd: true // Adds X-Forwarded-*: headers to upstream request