Skip to content
Merged
73 changes: 8 additions & 65 deletions lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const os = require("node:os");
const path = require("node:path");
const url = require("node:url");
const util = require("node:util");
const fs = require("graceful-fs");
const ipaddr = require("ipaddr.js");
const { validate } = require("schema-utils");
Expand Down Expand Up @@ -138,14 +137,7 @@ const schema = require("./options.json");
*/

/**
* @callback ByPass
* @param {Request} req
* @param {Response} res
* @param {ProxyConfigArrayItem} proxyConfig
*/

/**
* @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & { bypass?: ByPass } & HttpProxyMiddlewareOptions } ProxyConfigArrayItem
* @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions } ProxyConfigArrayItem
*/

/**
Expand Down Expand Up @@ -2148,29 +2140,13 @@ class Server {
* @returns {RequestHandler | undefined} request handler
*/
const getProxyMiddleware = (proxyConfig) => {
// It is possible to use the `bypass` method without a `target` or `router`.
// However, the proxy middleware has no use in this case, and will fail to instantiate.
if (proxyConfig.target) {
const context = proxyConfig.context || proxyConfig.path;

return createProxyMiddleware({
...proxyConfig,
pathFilter: /** @type {string} */ (context),
});
}
const context =
proxyConfig.context || proxyConfig.path || proxyConfig.pathFilter;

if (proxyConfig.router) {
return createProxyMiddleware(proxyConfig);
}

// TODO improve me after drop `bypass` to always generate error when configuration is bad
if (!proxyConfig.bypass) {
util.deprecate(
() => {},
`Invalid proxy configuration:\n\n${JSON.stringify(proxyConfig, null, 2)}\n\nThe use of proxy object notation as proxy routes has been removed.\nPlease use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options`,
"DEP_WEBPACK_DEV_SERVER_PROXY_ROUTES_ARGUMENT",
)();
}
return createProxyMiddleware({
...proxyConfig,
pathFilter: /** @type {string} */ (context),
});
};

/**
Expand Down Expand Up @@ -2236,40 +2212,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";
if (isByPassFuncDefined) {
util.deprecate(
() => {},
"Using the 'bypass' option is deprecated. Please use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options",
"DEP_WEBPACK_DEV_SERVER_PROXY_BYPASS_ARGUMENT",
)();
}
const bypassUrl = isByPassFuncDefined
? await /** @type {ByPass} */ (proxyConfig.bypass)(
req,
res,
proxyConfig,
)
: null;

if (typeof bypassUrl === "boolean") {
// skip the proxy
res.statusCode = 404;
req.url = "";
next();
} else if (typeof bypassUrl === "string") {
// byPass to that url
req.url = bypassUrl;
next();
} else if (proxyMiddleware) {
return proxyMiddleware(req, res, next);
} else {
next();
}
return proxyMiddleware(req, res, next);
};

middlewares.push({
Expand Down
117 changes: 21 additions & 96 deletions test/server/proxy-option.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use strict";

const path = require("node:path");
const util = require("node:util");
const express = require("express");
const request = require("supertest");
const webpack = require("webpack");
Expand All @@ -19,51 +18,14 @@ const proxyOptionPathsAsProperties = [
target: `http://localhost:${port1}`,
},
{
context: "/api/proxy2",
path: "/api/proxy2",
target: `http://localhost:${port2}`,
pathRewrite: { "^/api": "" },
},
{
context: "/foo",
bypass(req) {
if (/\.html$/.test(req.path || req.url)) {
return "/index.html";
}

return null;
},
},
{
context: "proxyfalse",
bypass(req) {
if (/\/proxyfalse$/.test(req.path || req.url)) {
return false;
}
},
},
{
context: "/proxy/async",
bypass(req, res) {
if (/\/proxy\/async$/.test(req.path || req.url)) {
return new Promise((resolve) => {
setTimeout(() => {
res.end("proxy async response");
resolve(true);
}, 10);
});
}
},
},
{
context: "/bypass-with-target",
target: `http://localhost:${port1}`,
changeOrigin: true,
secure: false,
bypass(req) {
if (/\.(html)$/i.test(req.path || req.url)) {
return req.url;
}
},
pathFilter: ["/foo/*.html", "/baz/*.html", "/bypass-with-target/*.html"],
pathRewrite: () => "/index.html",
router: () => `http://localhost:${port3}`,
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rewrite these test to using new options from http-proxy-middleware

Also let's add in migration guide how to achive the same behaviour with new http proxy middleware

];

Expand All @@ -77,7 +39,7 @@ const proxyOption = [
let maxServerListeners = 0;
const proxyOptionOfArray = [
{ context: "/proxy1", target: `http://localhost:${port1}` },
function proxy(req, res, next) {
function proxy(req) {
if (req) {
const socket = req.socket || req.connection;
const server = socket ? socket.server : null;
Expand All @@ -92,19 +54,6 @@ const proxyOptionOfArray = [
context: "/api/proxy2",
target: `http://localhost:${port2}`,
pathRewrite: { "^/api": "" },
bypass: () => {
if (req) {
const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
const params = new URLSearchParams(resolveUrl.search);
const foo = params.get("foo");

if (foo) {
res.end(`foo+${next.name}+${typeof next}`);

return false;
}
}
},
};
},
];
Expand Down Expand Up @@ -167,6 +116,9 @@ describe("proxy option", () => {
proxyApp1.get("/api", (req, res) => {
res.send("api response from proxy1");
});
proxyApp1.get("/index.html", (req, res) => {
res.send("Hello");
});
proxyApp2.get("/proxy2", (req, res) => {
res.send("from proxy2");
});
Expand Down Expand Up @@ -247,68 +199,48 @@ describe("proxy option", () => {
});
});

describe("bypass", () => {
it("should log deprecation warning when bypass is used", async () => {
const utilSpy = jest.spyOn(util, "deprecate");

const response = await req.get("/foo/bar.html");

expect(response.status).toBe(200);
expect(response.text).toContain("Hello");

const lastCall = utilSpy.mock.calls[utilSpy.mock.calls.length - 1];

expect(lastCall[1]).toBe(
"Using the 'bypass' option is deprecated. Please use the 'router' or 'context' options. Read more at https://github.com/chimurai/http-proxy-middleware/tree/v2.0.6#http-proxy-middleware-options",
);
expect(lastCall[2]).toBe(
"DEP_WEBPACK_DEV_SERVER_PROXY_BYPASS_ARGUMENT",
);

utilSpy.mockRestore();
});

it("can rewrite a request path", async () => {
describe("pathFilter and pathRewrite", () => {
it("should rewrite matching paths using pathFilter", async () => {
const response = await req.get("/foo/bar.html");

expect(response.status).toBe(200);
expect(response.text).toContain("Hello");
});

it("can rewrite a request path regardless of the target defined a bypass option", async () => {
it("should rewrite paths using pathRewrite function", async () => {
const response = await req.get("/baz/hoge.html");

expect(response.status).toBe(200);
expect(response.text).toContain("Hello");
});

it("should pass through a proxy when a bypass function returns null", async () => {
it("should proxy requests that don't match pathFilter", async () => {
const response = await req.get("/foo.js");

expect(response.status).toBe(200);
expect(response.text).toContain("Hey");
});

it("should not pass through a proxy when a bypass function returns false", async () => {
const response = await req.get("/proxyfalse");
it("should serve static files when not matching proxy rules", async () => {
const response = await req.get("/index.html");

expect(response.status).toBe(404);
expect(response.status).toBe(200);
expect(response.text).toContain("Hello");
});

it("should wait if bypass returns promise", async () => {
const response = await req.get("/proxy/async");
it("should return 404 for unmatched paths", async () => {
const response = await req.get("/proxyfalse");

expect(response.status).toBe(200);
expect(response.text).toContain("proxy async response");
expect(response.status).toBe(404);
});

it("should work with the 'target' option", async () => {
it("should handle pathFilter with router option", async () => {
const response = await req.get("/bypass-with-target/foo.js");

expect(response.status).toBe(404);
});

it("should work with the 'target' option #2", async () => {
it("should rewrite matching pathFilter patterns with router", async () => {
const response = await req.get("/bypass-with-target/index.html");

expect(response.status).toBe(200);
Expand Down Expand Up @@ -498,13 +430,6 @@ describe("proxy option", () => {
expect(response.text).toContain("from proxy2");
});

it("should allow req, res, and next", async () => {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const response = await req.get("/api/proxy2?foo=true");

expect(response.statusCode).toBe(200);
expect(response.text).toBe("foo+next+function");
});

it("should not exist multiple close events registered", async () => {
expect(maxServerListeners).toBeLessThanOrEqual(1);
});
Expand Down
8 changes: 0 additions & 8 deletions types/lib/Server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,6 @@ declare namespace Server {
ClientConnection,
WebSocketServer,
WebSocketServerImplementation,
ByPass,
ProxyConfigArrayItem,
ProxyConfigArray,
OpenApp,
Expand Down Expand Up @@ -1656,16 +1655,9 @@ type WebSocketServerImplementation = {
implementation: WebSocketServer;
clients: ClientConnection[];
};
type ByPass = (
req: Request,
res: Response,
proxyConfig: ProxyConfigArrayItem,
) => any;
type ProxyConfigArrayItem = {
path?: HttpProxyMiddlewareOptionsFilter | undefined;
context?: HttpProxyMiddlewareOptionsFilter | undefined;
} & {
bypass?: ByPass;
} & HttpProxyMiddlewareOptions;
type ProxyConfigArray = (
| ProxyConfigArrayItem
Expand Down
Loading