diff --git a/lib/Server.js b/lib/Server.js index f351263f33..34320d23c2 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -492,8 +492,12 @@ class Server { * } */ if (typeof options.proxy !== "undefined") { + // TODO remove in the next major release, only accept `Array` if (!Array.isArray(options.proxy)) { - if (Object.prototype.hasOwnProperty.call(options.proxy, "target")) { + if ( + Object.prototype.hasOwnProperty.call(options.proxy, "target") || + Object.prototype.hasOwnProperty.call(options.proxy, "router") + ) { options.proxy = [options.proxy]; } else { options.proxy = Object.keys(options.proxy).map((context) => { @@ -513,38 +517,42 @@ class Server { proxyOptions.context = correctedContext; } - const getLogLevelForProxy = (level) => { - if (level === "none") { - return "silent"; - } + return proxyOptions; + }); + } + } - if (level === "log") { - return "info"; - } + options.proxy = options.proxy.map((item) => { + const getLogLevelForProxy = (level) => { + if (level === "none") { + return "silent"; + } - if (level === "verbose") { - return "debug"; - } + if (level === "log") { + return "info"; + } - return level; - }; + if (level === "verbose") { + return "debug"; + } - if (typeof proxyOptions.logLevel === "undefined") { - proxyOptions.logLevel = getLogLevelForProxy( - compilerOptions.infrastructureLogging - ? compilerOptions.infrastructureLogging.level - : "info" - ); - } + return level; + }; - if (typeof proxyOptions.logProvider === "undefined") { - proxyOptions.logProvider = () => this.logger; - } + if (typeof item.logLevel === "undefined") { + item.logLevel = getLogLevelForProxy( + compilerOptions.infrastructureLogging + ? compilerOptions.infrastructureLogging.level + : "info" + ); + } - return proxyOptions; - }); + if (typeof item.logProvider === "undefined") { + item.logProvider = () => this.logger; } - } + + return item; + }); } if (typeof options.setupExitSignals === "undefined") { @@ -875,22 +883,24 @@ class Server { // It is possible to use the `bypass` method without a `target`. // However, the proxy middleware has no use in this case, and will fail to instantiate. - if (proxyConfig.target) { + if (context) { return createProxyMiddleware(context, proxyConfig); } + + return createProxyMiddleware(proxyConfig); }; /** * Assume a proxy configuration specified as: * proxy: [ * { - * context: ..., - * ...options... + * context: "value", + * ...options, * }, * // or: * function() { * return { - * context: ..., - * ...options... + * context: "context", + * ...options, * }; * } * ] @@ -903,7 +913,9 @@ class Server { ? proxyConfigOrCallback() : proxyConfigOrCallback; - proxyMiddleware = getProxyMiddleware(proxyConfig); + if (!proxyConfig.bypass) { + proxyMiddleware = getProxyMiddleware(proxyConfig); + } if (proxyConfig.ws) { this.webSocketProxies.push(proxyMiddleware); @@ -922,6 +934,7 @@ class Server { // - Check if we have a bypass function defined // - In case the bypass function is defined we'll retrieve the // bypassUrl from it otherwise bypassUrl would be null + // TODO remove in the next major in favor `context` and `router` options const isByPassFuncDefined = typeof proxyConfig.bypass === "function"; const bypassUrl = isByPassFuncDefined ? await proxyConfig.bypass(req, res, proxyConfig) diff --git a/test/server/proxy-option.test.js b/test/server/proxy-option.test.js index 8f23637bd4..cc7f2e872a 100644 --- a/test/server/proxy-option.test.js +++ b/test/server/proxy-option.test.js @@ -66,6 +66,7 @@ const proxyOptionOfArray = [ bypass: () => { if (req && req.query.foo) { res.end(`foo+${next.name}+${typeof next}`); + return false; } }, @@ -73,6 +74,27 @@ const proxyOptionOfArray = [ }, ]; +const proxyOptionOfArrayWithoutTarget = [ + { + router: () => `http://localhost:${port1}`, + }, +]; + +const proxyWithPath = { + "/proxy1": { + path: `http://localhost:${port1}`, + target: `http://localhost:${port1}`, + }, +}; + +const proxyWithString = { + "/proxy1": `http://localhost:${port1}`, +}; + +const proxyWithRouterAsObject = { + router: () => `http://localhost:${port1}`, +}; + describe("proxy option", () => { let proxyServer1; let proxyServer2; @@ -204,7 +226,7 @@ describe("proxy option", () => { }); }); - describe("as an option is an object", () => { + describe("as an option is an object with the `context` option", () => { let server; let req; @@ -243,6 +265,123 @@ describe("proxy option", () => { }); }); + describe("as an option is an object with `context` and `target` as string", () => { + let server; + let req; + + beforeAll(async () => { + const compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false, + }, + proxy: proxyWithString, + port: port3, + }, + compiler + ); + + await server.start(); + + await listenProxyServers(); + + req = request(server.app); + }); + + afterAll(async () => { + await server.stop(); + await closeProxyServers(); + }); + + it("respects a proxy option", async () => { + const response = await req.get("/proxy1"); + + expect(response.status).toEqual(200); + expect(response.text).toContain("from proxy1"); + }); + }); + + describe("as an option is an object with the `path` option (`context` alias)", () => { + let server; + let req; + + beforeAll(async () => { + const compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false, + }, + proxy: proxyWithPath, + port: port3, + }, + compiler + ); + + await server.start(); + + await listenProxyServers(); + + req = request(server.app); + }); + + afterAll(async () => { + await server.stop(); + await closeProxyServers(); + }); + + it("respects a proxy option", async () => { + const response = await req.get("/proxy1"); + + expect(response.status).toEqual(200); + expect(response.text).toContain("from proxy1"); + }); + }); + + describe("as an option is an object with the `router` option", () => { + let server; + let req; + + beforeAll(async () => { + const compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false, + }, + proxy: proxyWithRouterAsObject, + port: port3, + }, + compiler + ); + + await server.start(); + + await listenProxyServers(); + + req = request(server.app); + }); + + afterAll(async () => { + await server.stop(); + await closeProxyServers(); + }); + + it("respects a proxy option", async () => { + const response = await req.get("/proxy1"); + + expect(response.status).toEqual(200); + expect(response.text).toContain("from proxy1"); + }); + }); + describe("as an array", () => { let server; let req; @@ -296,6 +435,45 @@ describe("proxy option", () => { }); }); + describe("as an array without the `route` option", () => { + let server; + let req; + + beforeAll(async () => { + const compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false, + }, + proxy: proxyOptionOfArrayWithoutTarget, + port: port3, + }, + compiler + ); + + await server.start(); + + await listenProxyServers(); + + req = request(server.app); + }); + + afterAll(async () => { + await server.stop(); + await closeProxyServers(); + }); + + it("respects a proxy option", async () => { + const response = await req.get("/proxy1"); + + expect(response.status).toEqual(200); + expect(response.text).toContain("from proxy1"); + }); + }); + describe("should sharing a proxy option", () => { let server; let req;