diff --git a/README.md b/README.md index 945802a5..ea0ac89c 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,9 @@ Providing an alternative way to decide which requests should be proxied; In case } ``` +- **option.fallthrough**: boolean, sets whether execution should fall through to middleware chain when a 404 is encountered at the proxy target. If set, calls `next()` on the middleware chain. +Default: `false` + - **option.logLevel**: string, ['debug', 'info', 'warn', 'error', 'silent']. Default: `'info'` - **option.logProvider**: function, modify or replace log provider. Default: `console`. diff --git a/package.json b/package.json index 3490e3ff..a1ccadb2 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@commitlint/cli": "^8.0.0", "@commitlint/config-conventional": "^8.0.0", "@types/express": "^4.17.0", - "@types/http-proxy": "^1.17.0", + "@types/http-proxy": "^1.17.3", "@types/is-glob": "^4.0.0", "@types/jest": "^24.0.15", "@types/lodash": "^4.14.136", @@ -72,7 +72,7 @@ "ws": "^7.1.0" }, "dependencies": { - "http-proxy": "^1.17.0", + "http-proxy": "../node-http-proxy/", "is-glob": "^4.0.1", "lodash": "^4.17.14", "micromatch": "^4.0.2" diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts index f3f6634f..d3cc73c6 100644 --- a/src/http-proxy-middleware.ts +++ b/src/http-proxy-middleware.ts @@ -19,8 +19,13 @@ export class HttpProxyMiddleware { this.config = createConfig(context, opts); this.proxyOptions = this.config.options; + let httpProxyOptions = {} + if (context.fallthrough) { + httpProxyOptions.fallthrough = true + } + // create proxy - this.proxy = httpProxy.createProxyServer({}); + this.proxy = httpProxy.createProxyServer(httpProxyOptions); this.logger.info( `[HPM] Proxy created: ${this.config.context} -> ${this.proxyOptions.target}` ); @@ -48,6 +53,18 @@ export class HttpProxyMiddleware { public middleware = async (req, res, next) => { if (this.shouldProxy(this.config.context, req)) { const activeProxyOptions = await this.prepareProxyRequest(req); + + // fallthrough to the middleware chain if a resource + // cannot be found in the source being proxied + if (this.proxyOptions.fallthrough) { + this.proxy.once('proxyRes', (proxyRes) => { + if (proxyRes.statusCode === 404) { + this.logger.info('[HPM] Falling through to middleware chain on 404'); + next(); + } + }); + } + this.proxy.web(req, res, activeProxyOptions); } else { next(); diff --git a/test/e2e/_utils.ts b/test/e2e/_utils.ts index 5eaed460..fc944ef3 100644 --- a/test/e2e/_utils.ts +++ b/test/e2e/_utils.ts @@ -16,3 +16,16 @@ export function createServer(portNumber, middleware, path?) { return server; } + +export function createServerWithFallthrough(portNumber, middleware) { + const app = express(); + + app.use(middleware); + app.use((request, response, next) => { + response.end('fell through'); + }) + + const server = app.listen(portNumber); + + return server; +} diff --git a/test/e2e/http-proxy-middleware.spec.ts b/test/e2e/http-proxy-middleware.spec.ts index 196e0de8..3a2e45ec 100644 --- a/test/e2e/http-proxy-middleware.spec.ts +++ b/test/e2e/http-proxy-middleware.spec.ts @@ -1,5 +1,5 @@ import * as http from 'http'; -import { createServer, proxyMiddleware } from './_utils'; +import { createServer, proxyMiddleware, createServerWithFallthrough } from './_utils'; describe('E2E http-proxy-middleware', () => { describe('http-proxy-middleware creation', () => { @@ -658,5 +658,90 @@ describe('E2E http-proxy-middleware', () => { expect(logMessage).not.toBeUndefined(); }); }); + + describe('404 fallthrough when requested', () => { + let proxyServer; + let targetServer; + let response; + let responseBody; + + beforeEach(() => { + const mwProxy = proxyMiddleware('/', { + target: 'http://localhost:8000', + fallthrough: true + }); + + const mwTarget = (req, res, next) => { + // return 404 error + res.statusCode = 404; + res.end(); + }; + + proxyServer = createServerWithFallthrough(3000, mwProxy); + targetServer = createServer(8000, mwTarget); + }); + + beforeEach(done => { + http.get('http://localhost:3000/', res => { + response = res; + res.on('data', chunk => { + responseBody = chunk.toString(); + done(); + }); + }); + }); + + afterEach(() => { + proxyServer.close(); + targetServer.close(); + }); + + it('should fall through', () => { + expect(response.statusCode).toBe(200); + expect(responseBody).toBe('fell through'); + }); + }); + + describe('404 do not fallthrough when not requested', () => { + let proxyServer; + let targetServer; + let response; + let responseBody; + + beforeEach(() => { + const mwProxy = proxyMiddleware('/', { + target: 'http://localhost:8000', + }); + + const mwTarget = (req, res, next) => { + // return 404 error + res.statusCode = 404; + res.end('404 error'); + }; + + proxyServer = createServerWithFallthrough(3000, mwProxy); + targetServer = createServer(8000, mwTarget); + }); + + beforeEach(done => { + http.get('http://localhost:3000/', res => { + response = res; + res.on('data', chunk => { + responseBody = chunk.toString(); + done(); + }); + }); + }); + + afterEach(() => { + proxyServer.close(); + targetServer.close(); + }); + + it('should not fall through', () => { + expect(response.statusCode).toBe(404); + expect(responseBody).toBe('404 error'); + }); + }); }); }); diff --git a/yarn.lock b/yarn.lock index 0b0cf96f..245218ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,10 +494,10 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" -"@types/http-proxy@^1.17.0": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.0.tgz#baf82ff6aa2723fd29f90e3ba1384e665006863e" - integrity sha512-l+s0IoxSHqhLFJPDHRfO235kgrCkvFD8JmdV/T9C4BKBYPIjrQopGFH4r7h2e3jQqgJRCthRCAZIxDoFnj1zwQ== +"@types/http-proxy@^1.17.3": + version "1.17.3" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.3.tgz#348e1b808ff9585423cb909e9992d89ccdbf4c14" + integrity sha512-wIPqXANye5BbORbuh74exbwNzj+UWCwWyeEFJzUQ7Fq3W2NSAy+7x7nX1fgbEypr2/TdKqpeuxLnXWgzN533/Q== dependencies: "@types/node" "*" @@ -1840,10 +1840,10 @@ eventemitter3@1.x.x: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" integrity sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg= -eventemitter3@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +eventemitter3@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" + integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== exec-sh@^0.3.2: version "0.3.2" @@ -2393,6 +2393,13 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-proxy@../node-http-proxy/: + version "1.18.0" + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + http-proxy@1.15.2: version "1.15.2" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.15.2.tgz#642fdcaffe52d3448d2bda3b0079e9409064da31" @@ -2401,15 +2408,6 @@ http-proxy@1.15.2: eventemitter3 "1.x.x" requires-port "1.x.x" -http-proxy@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" - integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== - dependencies: - eventemitter3 "^3.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"