|
1 | 1 | /* eslint-disable no-console */
|
2 | 2 | import { request, createServer } from 'node:http'
|
| 3 | +import { pathToFileURL } from 'node:url' |
3 | 4 | import { logger } from '@libp2p/logger'
|
4 | 5 |
|
5 |
| -const log = logger('reverse-proxy') |
6 |
| - |
7 |
| -const TARGET_HOST = process.env.TARGET_HOST ?? 'localhost' |
8 |
| -const backendPort = Number(process.env.BACKEND_PORT ?? 3000) |
9 |
| -const proxyPort = Number(process.env.PROXY_PORT ?? 3333) |
10 |
| -const subdomain = process.env.SUBDOMAIN |
11 |
| -const prefixPath = process.env.PREFIX_PATH |
12 |
| -const disableTryFiles = process.env.DISABLE_TRY_FILES === 'true' |
13 |
| -const X_FORWARDED_HOST = process.env.X_FORWARDED_HOST |
14 |
| - |
15 | 6 | const setCommonHeaders = (res) => {
|
16 | 7 | res.setHeader('Access-Control-Allow-Origin', '*')
|
17 | 8 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Range, User-Agent, X-Requested-With')
|
18 | 9 | res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
|
19 | 10 | }
|
20 | 11 |
|
21 |
| -const makeRequest = (options, req, res, attemptRootFallback = false) => { |
22 |
| - options.headers.Host = TARGET_HOST |
23 |
| - const clientIp = req.connection.remoteAddress |
24 |
| - options.headers['X-Forwarded-For'] = clientIp |
| 12 | +/** |
| 13 | + * Creates and starts a reverse proxy server |
| 14 | + * |
| 15 | + * @param {object} options - Configuration options |
| 16 | + * @param {string} [options.targetHost] - Target host to proxy to (defaults to process.env.TARGET_HOST or 'localhost') |
| 17 | + * @param {number} [options.backendPort] - Port of the backend service (defaults to process.env.BACKEND_PORT or 3000) |
| 18 | + * @param {number} [options.proxyPort] - Port for the proxy to listen on (defaults to process.env.PROXY_PORT or 3333) |
| 19 | + * @param {string} [options.subdomain] - Subdomain to use (defaults to process.env.SUBDOMAIN) |
| 20 | + * @param {string} [options.prefixPath] - Path prefix to add to requests (defaults to process.env.PREFIX_PATH) |
| 21 | + * @param {boolean} [options.disableTryFiles] - Whether to disable try_files behavior (defaults to process.env.DISABLE_TRY_FILES === 'true') |
| 22 | + * @param {string} [options.xForwardedHost] - Value for X-Forwarded-Host header (defaults to process.env.X_FORWARDED_HOST) |
| 23 | + * @param {object} [options.log] - Logger instance to use (defaults to logger('reverse-proxy')) |
| 24 | + * @returns {object} The HTTP server instance |
| 25 | + */ |
| 26 | +export function createReverseProxy ({ |
| 27 | + targetHost = process.env.TARGET_HOST ?? 'localhost', |
| 28 | + backendPort = Number(process.env.BACKEND_PORT ?? 3000), |
| 29 | + proxyPort = Number(process.env.PROXY_PORT ?? 3333), |
| 30 | + subdomain = process.env.SUBDOMAIN, |
| 31 | + prefixPath = process.env.PREFIX_PATH, |
| 32 | + disableTryFiles = process.env.DISABLE_TRY_FILES === 'true', |
| 33 | + xForwardedHost = process.env.X_FORWARDED_HOST, |
| 34 | + log = logger('reverse-proxy') |
| 35 | +} = {}) { |
| 36 | + const makeRequest = (options, req, res, attemptRootFallback = false) => { |
| 37 | + options.headers.Host = targetHost |
| 38 | + const clientIp = req.connection.remoteAddress |
| 39 | + options.headers['X-Forwarded-For'] = clientIp |
25 | 40 |
|
26 |
| - // override path to include prefixPath if set |
27 |
| - if (prefixPath != null) { |
28 |
| - options.path = `${prefixPath}${options.path}` |
29 |
| - } |
30 |
| - if (subdomain != null) { |
31 |
| - options.headers.Host = `${subdomain}.${TARGET_HOST}` |
32 |
| - } |
33 |
| - if (X_FORWARDED_HOST != null) { |
34 |
| - options.headers['X-Forwarded-Host'] = X_FORWARDED_HOST |
35 |
| - } |
| 41 | + // override path to include prefixPath if set |
| 42 | + if (prefixPath != null) { |
| 43 | + options.path = `${prefixPath}${options.path}` |
| 44 | + } |
| 45 | + if (subdomain != null) { |
| 46 | + options.headers.Host = `${subdomain}.${targetHost}` |
| 47 | + } |
| 48 | + if (xForwardedHost != null) { |
| 49 | + options.headers['X-Forwarded-Host'] = xForwardedHost |
| 50 | + } |
36 | 51 |
|
37 |
| - // log where we're making the request to |
38 |
| - log('Proxying request from %s:%s to %s:%s%s', req.headers.host, req.url, options.headers.Host, options.port, options.path) |
| 52 | + // log where we're making the request to |
| 53 | + log('Proxying request from %s:%s to %s:%s%s', req.headers.host, req.url, options.headers.Host, options.port, options.path) |
39 | 54 |
|
40 |
| - const proxyReq = request(options, proxyRes => { |
41 |
| - if (!disableTryFiles && proxyRes.statusCode === 404) { // poor mans attempt to implement nginx style try_files |
42 |
| - if (!attemptRootFallback) { |
43 |
| - // Split the path and pop the last segment |
44 |
| - const pathSegments = options.path.split('/') |
45 |
| - const lastSegment = pathSegments.pop() || '' |
| 55 | + const proxyReq = request(options, proxyRes => { |
| 56 | + if (!disableTryFiles && proxyRes.statusCode === 404) { // poor mans attempt to implement nginx style try_files |
| 57 | + if (!attemptRootFallback) { |
| 58 | + // Split the path and pop the last segment |
| 59 | + const pathSegments = options.path.split('/') |
| 60 | + const lastSegment = pathSegments.pop() || '' |
46 | 61 |
|
47 |
| - // Attempt to request the last segment at the root |
48 |
| - makeRequest({ ...options, path: `/${lastSegment}` }, req, res, true) |
| 62 | + // Attempt to request the last segment at the root |
| 63 | + makeRequest({ ...options, path: `/${lastSegment}` }, req, res, true) |
| 64 | + } else { |
| 65 | + // If already attempted a root fallback, serve index.html |
| 66 | + makeRequest({ ...options, path: '/index.html' }, req, res) |
| 67 | + } |
49 | 68 | } else {
|
50 |
| - // If already attempted a root fallback, serve index.html |
51 |
| - makeRequest({ ...options, path: '/index.html' }, req, res) |
| 69 | + setCommonHeaders(res) |
| 70 | + res.writeHead(proxyRes.statusCode, proxyRes.headers) |
| 71 | + proxyRes.pipe(res, { end: true }) |
52 | 72 | }
|
53 |
| - } else { |
| 73 | + }) |
| 74 | + |
| 75 | + req.pipe(proxyReq, { end: true }) |
| 76 | + |
| 77 | + proxyReq.on('error', (e) => { |
| 78 | + log.error(`Problem with request: ${e.message}`) |
| 79 | + setCommonHeaders(res) |
| 80 | + res.writeHead(500) |
| 81 | + res.end(`Internal Server Error: ${e.message}`) |
| 82 | + }) |
| 83 | + } |
| 84 | + |
| 85 | + const proxyServer = createServer((req, res) => { |
| 86 | + if (req.method === 'OPTIONS') { |
54 | 87 | setCommonHeaders(res)
|
55 |
| - res.writeHead(proxyRes.statusCode, proxyRes.headers) |
56 |
| - proxyRes.pipe(res, { end: true }) |
| 88 | + res.writeHead(200) |
| 89 | + res.end() |
| 90 | + return |
57 | 91 | }
|
58 |
| - }) |
59 | 92 |
|
60 |
| - req.pipe(proxyReq, { end: true }) |
| 93 | + const options = { |
| 94 | + hostname: targetHost, |
| 95 | + port: backendPort, |
| 96 | + path: req.url, |
| 97 | + method: req.method, |
| 98 | + headers: { ...req.headers } |
| 99 | + } |
61 | 100 |
|
62 |
| - proxyReq.on('error', (e) => { |
63 |
| - log.error(`Problem with request: ${e.message}`) |
64 |
| - setCommonHeaders(res) |
65 |
| - res.writeHead(500) |
66 |
| - res.end(`Internal Server Error: ${e.message}`) |
| 101 | + makeRequest(options, req, res) |
67 | 102 | })
|
68 |
| -} |
69 | 103 |
|
70 |
| -const proxyServer = createServer((req, res) => { |
71 |
| - if (req.method === 'OPTIONS') { |
72 |
| - setCommonHeaders(res) |
73 |
| - res.writeHead(200) |
74 |
| - res.end() |
75 |
| - return |
76 |
| - } |
77 |
| - |
78 |
| - const options = { |
79 |
| - hostname: TARGET_HOST, |
80 |
| - port: backendPort, |
81 |
| - path: req.url, |
82 |
| - method: req.method, |
83 |
| - headers: { ...req.headers } |
84 |
| - } |
| 104 | + proxyServer.listen(proxyPort, () => { |
| 105 | + log(`Proxy server listening on port ${proxyPort}`) |
| 106 | + }) |
85 | 107 |
|
86 |
| - makeRequest(options, req, res) |
87 |
| -}) |
| 108 | + return proxyServer |
| 109 | +} |
88 | 110 |
|
89 |
| -proxyServer.listen(proxyPort, () => { |
90 |
| - log(`Proxy server listening on port ${proxyPort}`) |
91 |
| -}) |
| 111 | +// Run main function if this file is being executed directly |
| 112 | +if (import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 113 | + createReverseProxy() |
| 114 | +} |
0 commit comments