From 4331545853618ec7a58eb5d1a9e4030423349cf6 Mon Sep 17 00:00:00 2001 From: Caleb Williams Date: Wed, 24 Jul 2019 11:55:47 -0500 Subject: [PATCH] feat(proxy): Add proxy support --- README.md | 7 ++++ src/index.js | 103 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c1a8e19..54674aa 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,13 @@ serve({ headers: { 'Access-Control-Allow-Origin': '*', foo: 'bar' + }, + + // 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 ec9ee3c..8fae80d 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 } from 'path' import mime from 'mime' @@ -22,9 +22,20 @@ function serve (options = { contentBase: '' }) { options.headers = options.headers || {} options.https = options.https || false options.openPage = options.openPage || '' + options.proxy = options.proxy || {} mime.default_type = 'text/plain' - const requestListener = (request, response) => { + // 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 urlPath = decodeURI(request.url.split('?')[0]) @@ -32,31 +43,62 @@ function serve (options = { contentBase: '' }) { response.setHeader(key, options.headers[key]) }) - readFileFromContentBase(options.contentBase, urlPath, function (error, content, filePath) { - if (!error) { - return found(response, filePath, content) - } - if (error.code !== 'ENOENT') { - response.writeHead(500) - response.end('500 Internal Server Error' + - '\n\n' + filePath + - '\n\n' + Object.values(error).join('\n') + - '\n\n(rollup-plugin-serve)', 'utf-8') - return - } - if (options.historyApiFallback) { - var fallbackPath = typeof options.historyApiFallback === 'string' ? options.historyApiFallback : '/index.html' - readFileFromContentBase(options.contentBase, fallbackPath, function (error, content, filePath) { - if (error) { - notFound(response, filePath) - } else { - found(response, filePath, content) - } - }) - } else { - notFound(response, filePath) - } - }) + // 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() + }) + } else { + readFileFromContentBase(options.contentBase, urlPath, function (error, content, filePath) { + if (!error) { + return found(response, filePath, content) + } + if (error.code !== 'ENOENT') { + response.writeHead(500) + response.end('500 Internal Server Error' + + '\n\n' + filePath + + '\n\n' + Object.values(error).join('\n') + + '\n\n(rollup-plugin-serve)', 'utf-8') + return + } + if (options.historyApiFallback) { + var fallbackPath = typeof options.historyApiFallback === 'string' ? options.historyApiFallback : '/index.html' + readFileFromContentBase(options.contentBase, fallbackPath, function (error, content, filePath) { + if (error) { + notFound(response, filePath) + } else { + found(response, filePath, content) + } + }) + } else { + notFound(response, filePath) + } + }) + } } // release previous server instance if rollup is reloading configuration in watch mode @@ -127,6 +169,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' }