diff --git a/README.md b/README.md index 9f55599..f280c1f 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ serve({ // set custom mime types, usage https://github.com/broofa/mime#mimedefinetypemap-force--false mimeTypes: { 'application/javascript': ['js_commonjs-proxy'] - } + }, // execute function after server has begun listening onListening: function (server) { @@ -102,6 +102,13 @@ serve({ // by using a bound function, we can access options as `this` const protocol = this.https ? 'https' : 'http' console.log(`Server listening at ${protocol}://${host}:${address.port}/`) + }, + + // Set up simple proxy + // this will route all traffic starting with + // `/api` to http://localhost:8181/api + proxy: { + api: 'http://localhost:8181' } }) ``` diff --git a/src/index.js b/src/index.js index e10ed71..5af5d67 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { readFile } from 'fs' -import { createServer as createHttpsServer } from 'https' -import { createServer } from 'http' +import https, { createServer as createHttpsServer } from 'https' +import http, { createServer } from 'http' import { resolve, posix } from 'path' import mime from 'mime' @@ -20,14 +20,24 @@ function serve (options = { contentBase: '' }) { options.port = options.port || 10001 options.headers = options.headers || {} options.https = options.https || false - options.openPage = options.openPage || '' options.onListening = options.onListening || function noop () { } + options.openPage = options.openPage || '' + options.proxy = options.proxy || {} mime.default_type = 'text/plain' if (options.mimeTypes) { mime.define(options.mimeTypes, true) } + // Use http or https as needed + const http_s = options.https ? https : http + + const proxies = Object.keys(options.proxy).map(proxy => ({ + proxy, + destination: options.proxy[proxy], + test: new RegExp(`\/${proxy}`) + })) + const requestListener = (request, response) => { // Remove querystring const unsafePath = decodeURI(request.url.split('?')[0]) @@ -39,6 +49,38 @@ function serve (options = { contentBase: '' }) { response.setHeader(key, options.headers[key]) }) + // Find the appropriate proxy for the request if one exists + const proxy = proxies.find(({ test }) => request.url.match(test)) + + // If a proxy exists, forward the request to the appropriate server + if (proxy && proxy.destination) { + const { destination } = proxy + const newDestination = `${destination}${request.url}` + const { headers, method, statusCode } = request + + // Get the request contents + let body = ''; + request.on('data', chunk => body += chunk) + + // Forward the request + request.on('end', () => { + const proxyRequest = http_s + .request(newDestination, { headers, method }, (proxyResponse) => { + let data = '' + proxyResponse.on('data', chunk => data += chunk) + proxyResponse.on('end', () => { + Object.keys(proxyResponse.headers).forEach(key => response.setHeader(key, proxyResponse.headers[key])) + foundProxy(response, proxyResponse.statusCode, data) + }) + }) + .end(body, 'utf-8') + + proxyRequest.on('error', err => console.error(`There was a problem with the request for ${request.url}: ${err}`)) + proxyRequest.end() + }) + return + } + readFileFromContentBase(options.contentBase, urlPath, function (error, content, filePath) { if (!error) { return found(response, filePath, content) @@ -151,6 +193,11 @@ function found (response, filePath, content) { response.end(content, 'utf-8') } +function foundProxy (response, status, content) { + response.writeHead(status) + response.end(content, 'utf-8') +} + function green (text) { return '\u001b[1m\u001b[32m' + text + '\u001b[39m\u001b[22m' }