http-proxy-middleware
使 Node.js 代理变得简单。为 connect、express、next.js 等轻松配置代理中间件。
由流行的Nodejitsu http-proxy 提供支持。
TL;DR
太长未读(too long; didn’t read 的缩写,多用于在社交媒体等上对某条消息或某篇文章进行评论)
代理 /api
请求到 http://www.example.org
// typescript
import * as express from 'express';
import type { Request, Response, NextFunction } from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import type { Filter, Options, RequestHandler } from 'http-proxy-middleware';
const app = express();
const proxyMiddleware = createProxyMiddleware<Request, Response>({
target: 'http://www.example.org/api',
changeOrigin: true,
}),
app.use('/api', proxyMiddleware);
app.listen(3000);
// proxy and keep the same base path "/api"
// http://127.0.0.1:3000/api/foo/bar -> http://www.example.org/api/foo/bar
http-proxy 所有的 选项 都可以使用,同时还有一些额外的 http-proxy-middleware
的 选项
目录
安装
npm install --save-dev http-proxy-middleware
基础使用
创建和配置一个代理中间件:createProxyMiddleware(config)
const { createProxyMiddleware } = require('http-proxy-middleware');
const apiProxy = createProxyMiddleware({
target: 'http://www.example.org',
changeOrigin: true,
});
// 'apiProxy' is now ready to be used as middleware in a server.
- options. target : 要代理到的目标主机 (协议 + 端口)
- options. changeOrigin: 对于虚拟主机
- 查看
http-proxy-middleware
配置选项
Express Server 示例
以 express 服务器为例
// include dependencies
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// create the proxy
/** @type {import('http-proxy-middleware/dist/types').RequestHandler<express.Request, express.Response>} */
const exampleProxy = createProxyMiddleware({
target: 'http://www.example.org/api', // target host with the same base path
changeOrigin: true, // needed for virtual hosted sites
});
// mount `exampleProxy` in web server
app.use('/api', exampleProxy);
app.listen(3000);
app.use(path,proxy)
如果想要使用 服务器 app.use 的 path 参数来匹配请求,还可以使用 pathFilter 选项来进一步包含或排除你想要代理的请求
app.use(
createProxyMiddleware({
target: 'http://www.example.org/api',
changeOrigin: true,
pathFilter: '/api/proxy-only-this-path',
}),
);
app.use
文档
- express:http://expressjs.com/en/4x/api.html#app.use
- connect:https://github.com/senchalabs/connect#mount-middleware
- polka:https://github.com/lukeed/polka#usebase-fn
选项
http-proxy-middleware 选项
pathFilter (string, []string, glob, []glob, function)
进一步指定要代理的请求,用于过滤的 path 是 request.url 的路径部分(pathname)。在 Express 中,这是相对于代理中间件挂载点的路径。
- 路径匹配
-
- createProxyMiddlewar({…}) - 如果没有配置 pathFilter 将会匹配所有路径,所有的请求都将会代理
-
- createProxyMiddleware({ pathFilter: ‘/api’, …}) 匹配所有
/api
开头的请求
- createProxyMiddleware({ pathFilter: ‘/api’, …}) 匹配所有
- 多个路径匹配
-
- createProxyMiddleware({ pathFilter: [‘/api’, ‘/ajax’, ‘/someotherpath’], …})
- 通配符路径匹配
为了实现更精细的控制,你可以使用通配符匹配。通配符模式匹配是通过 micromatch 实现的。访问 micromatch 或 glob 以查看更多通配符匹配的示例 -
- createProxyMiddleware({ pathFilter: ‘**’, …}) 匹配所有的路径,所有的请求都将会被代理
-
- createProxyMiddleware({ pathFilter: ‘**/*.html’, …}) 匹配所有以
.html
结尾的请求
- createProxyMiddleware({ pathFilter: ‘**/*.html’, …}) 匹配所有以
-
- createProxyMiddleware({ pathFilter: ‘/*.html’, …}) 会匹配直接位于绝对路径下的
.html
结尾的请求
- createProxyMiddleware({ pathFilter: ‘/*.html’, …}) 会匹配直接位于绝对路径下的
-
- createProxyMiddleware({ pathFilter: ‘/api/**/*.html’, …}) 匹配
/api
路径下的以.html
为结尾的请求
- createProxyMiddleware({ pathFilter: ‘/api/**/*.html’, …}) 匹配
-
- createProxyMiddleware({ pathFilter: [‘/api/**’, ‘/ajax/**’], …}) 组合匹配
-
- createProxyMiddleware({ pathFilter: [‘/api/**’, ‘!**/bad.json’], …}) 排除
Note: 在多个路径组合匹配时,不可以同时使用 字符串路径和通配符路径
- 自定义匹配
为了完全控制代理,可以提供一个自定义的函数来确定那些请求应该被代理或者不应该被代理
/**
* @return {Boolean}
*/
const pathFilter = function (path, req) {
return path.match('^/api') && req.method === 'GET';
};
const apiProxy = createProxyMiddleware({
target: 'http://www.example.org',
pathFilter: pathFilter,
});
pathRewrite (object/function)
重写目标 url 的路径,对象属性将会使用正则表达式匹配。
// rewrite path
pathRewrite: {'^/old/api' : '/new/api'}
// remove path
pathRewrite: {'^/remove/api' : ''}
// add base path
pathRewrite: {'^/' : '/basepath/'}
// custom rewriting
pathRewrite: function (path, req) { return path.replace('/api', '/base/api') }
// custom rewriting, returning Promise
pathRewrite: async function (path, req) {
const should_add_something = await httpRequestToDecideSomething(path);
if (should_add_something) path += "something";
return path;
}
router(object/function)
给特定请求重新定向 options.target 属性
// Use `host` and/or `path` to match requests. First match will be used.
// The order of the configuration matters.
router: {
'integration.localhost:3000' : 'http://127.0.0.1:8001', // host only
'staging.localhost:3000' : 'http://127.0.0.1:8002', // host only
'localhost:3000/api' : 'http://127.0.0.1:8003', // host + path
'/rest' : 'http://127.0.0.1:8004' // path only
}
// Custom router function (string target)
router: function(req) {
return 'http://127.0.0.1:8004';
}
// Custom router function (target object)
router: function(req) {
return {
protocol: 'https:', // The : is required
host: '127.0.0.1',
port: 8004
};
}
// Asynchronous router function which returns promise
router: async function(req) {
const url = await doSomeIO();
return url;
}
plugins(Array)
const simpleRequestLogger = (proxyServer, options) => {
proxyServer.on('proxyReq', (proxyReq, req, res) => {
console.log(`[HPM] [${req.method}] ${req.url}`); // outputs: [HPM] GET /users
});
},
const config = {
target: `http://example.org`,
changeOrigin: true,
plugins: [simpleRequestLogger],
};
ejectPlugins(Boolean) default:false
如果你对预配置的插件不满意,你可以通过配置ejectPlugins: true来弹出它们。
注意:需要注册自己的错误处理程序以防止服务器崩溃
// eject default plugins and manually add them back
const {
debugProxyErrorsPlugin, // subscribe to proxy errors to prevent server from crashing
loggerPlugin, // log proxy events to a logger (ie. console)
errorResponsePlugin, // return 5xx response on proxy error
proxyEventsPlugin, // implements the "on:" option
} = require('http-proxy-middleware');
createProxyMiddleware({
target: `http://example.org`,
changeOrigin: true,
ejectPlugins: true,
plugins: [debugProxyErrorsPlugin, loggerPlugin, errorResponsePlugin, proxyEventsPlugin],
});
logger (Object)
配置 logger 用于 http-proxy-middleware 的输出日志记录,例如:console, winston, pino , bunyan, log4js 等等,
内部只是用 info, warn ,error 来实现不同记录器之间的兼容性
如果使用 winston 确保启用了插值,
https://github.com/winstonjs/winston#string-interpolation
查看logger 详情 (recipes/logger.md)获取更多信息。
createProxyMiddleware({
logger: console,
});
http-proxy 事件
通过 on
属性订阅 http-proxy
事件。
createProxyMiddleware({
target: 'http://www.example.org',
on: {
proxyReq: (proxyReq, req, res) => {
/* handle proxyReq */
},
proxyRes: (proxyRes, req, res) => {
/* handle proxyRes */
},
error: (err, req, res) => {
/* handle error */
},
},
});
- option.on.error: function 订阅 http-proxy 的 错误事件,用于自定义错误处理
function onError(err, req, res, target) {
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end('Something went wrong. And we are reporting a custom error message.');
}
- option.on.proxyRes function 订阅 http-proxy 的 proxyRes 事件
function onProxyRes(proxyRes, req, res) {
proxyRes.headers['x-added'] = 'foobar'; // add new header to response
delete proxyRes.headers['x-removed']; // remove header from response
}
- option.on.proxyReq: function 订阅 http-proxy 的 proxyReq 事件
function onProxyReq(proxyReq, req, res) {
// add custom header to request
proxyReq.setHeader('x-added', 'foobar');
// or log the req
}
- option.on.open: function 订阅 http-proxy 的 open 事件
function onOpen(proxySocket) {
// listen for messages coming FROM the target here
proxySocket.on('data', hybridParseAndLogMessage);
}
- option.on.close function 订阅 http-proxy 的close 事件
function onClose(res, socket, head) {
// view disconnected websocket connections
console.log('Client disconnected');
}
http-proxy 属性
下面的选项由 http-proxy
库提供
-
option.target:一个 URL 字符串,会通过 url 模块进行解析。
-
option.forward:一个 URL 字符串,会通过 url 模块进行解析。
-
option.agent:一个对象,会被传递给 http(s).request(参见 Node.js 的 https agent 和 http agent 对象)。
-
option.ssl:一个对象,会被传递给 https.createServer()。
-
option.ws:true/false,是否代理 WebSocket 请求。
-
option.xfwd:true/false,是否添加 x-forward 头。
-
option.secure:true/false,是否验证 SSL 证书。
-
option.toProxy:true/false,是否将绝对 URL 作为路径传递(适用于代理到其他代理服务器)。
-
option.prependPath:true/false,默认值为 true,指定是否将目标路径添加到代理路径前。
-
option.ignorePath:true/false,默认值为 false,指定是否忽略传入请求的代理路径(注意:如果需要,必须手动添加 /)。
-
option.localAddress:用于绑定传出连接的本地接口字符串。
-
option.changeOrigin:true/false,默认值为 false,是否将 Host 头的来源更改为目标 URL。
-
option.preserveHeaderKeyCase:true/false,默认值为 false,指定是否保留响应头键的大小写。
-
option.auth:基本身份验证信息,例如 ‘user:password’,用于计算 Authorization 头。
-
option.hostRewrite:重写重定向(301/302/307/308)中的 location 主机名。
-
option.autoRewrite:根据请求的主机/端口重写重定向(301/302/307/308)中的 location 主机/端口。默认值为 false。
-
option.protocolRewrite:将重定向(301/302/307/308)中的 location 协议重写为 ‘http’ 或 ‘https’。默认值为 null。
-
option.cookieDomainRewrite: 重写Set-Cookie头部中的域名。可能值:
-
- false(默认):禁用Cookie重写。
-
*String类型:新域名,例如cookieDomainRewrite: “new.domain”。要移除域名,使用cookieDomainRewrite: “”。
-
Object类型:域名到新域名的映射,使用""匹配所有域名。例如,保持一个域名不变,重写一个域名,移除其他域名
cookieDomainRewrite: {
"unchanged.domain": "unchanged.domain",
"old.domain": "new.domain",
"*": ""
}
- option.cookiePathRewrite: 重写Set-Cookie头部中的路径。可能值:
-
- false(默认):禁用Cookie重写。
-
- String类型:新路径,例如cookiePathRewrite: “/newPath/”。要移除路径,使用cookiePathRewrite: “”。要将路径设置为根路径,使用cookiePathRewrite: “/”。
-
- Object类型:路径到新路径的映射,使用"*"匹配所有路径。例如,保持一个路径不变,重写一个路径,移除其他路径:
cookiePathRewrite: {
"/unchanged.path/": "/unchanged.path/",
"/old.path/": "/new.path/",
"*": ""
}
-
option.headers: 对象,添加请求头部。(示例:{host:‘www.example.org’})
-
option.proxyTimeout: 代理在收到目标响应前的超时时间(毫秒)。
-
option.timeout: 传入请求的超时时间(毫秒)。
-
option.followRedirects: 布尔值,默认:false - 指定是否要跟随重定向。
-
option.selfHandleResponse: 布尔值,如果设置为true,则不会调用任何webOutgoing传递,并且您有责任通过监听并对proxyRes事件作出反应来适当地返回响应。
-
option.buffer: 作为请求体发送的数据流。可能您在代理之前有一些中间件消耗了请求流,例如,如果您将请求体读取到一个名为req.rawbody的字段中,您可以在buffer选项中重新流化这个字段:
'use strict';
const streamify = require('stream-array');
const HttpProxy = require('http-proxy');
const proxy = new HttpProxy();
module.exports = (req, res, next) => {
proxy.web(
req,
res,
{
target: 'http://127.0.0.1:4003/',
buffer: streamify([req.rawbody]), // 注意:streamify通常需要数组作为输入,因此这里用[req.rawbody]包装了req.rawbody
},
next,
);
};
WebSocket
// verbose api
createProxyMiddleware({ pathFilter: '/', target: 'http://echo.websocket.org', ws: true });
- 外部 WebSocket 升级
在之前的 WebSocket 示例中,http-proxy-middleware 依赖于一个初始的 HTTP 请求来监听 HTTP 升级事件。如果你需要在没有初始 HTTP 请求的情况下代理 WebSocket,你可以手动订阅服务器的 HTTP 升级事件。
const wsProxy = createProxyMiddleware({ target: 'ws://echo.websocket.org', changeOrigin: true });
const app = express();
app.use(wsProxy);
const server = app.listen(3000);
server.on('upgrade', wsProxy.upgrade); // <-- subscribe to http 'upgrade'
拦截和操作请求
通过定义 createProxyMiddleware 中的 onProxyReq 来拦截来自下游的请求。
目前唯一预先提供的请求拦截器是 fixRequestBody,它用于在 bodyParser 在此中间件之前应用时修复代理的 POST 请求。
例如:
const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware');
const proxy = createProxyMiddleware({
/**
* Fix bodyParser
**/
on: {
proxyReq: fixRequestBody,
},
});
拦截和操作返回
使用 responseInterceptor 拦截来自上游的响应。(请确保设置 selfHandleResponse: true)
使用 brotli、gzip 和 deflate 压缩的响应将自动解压缩。响应将以缓冲区(buffer)的形式返回(文档),你可以对其进行操作。
通过缓冲区,响应的操作不仅限于文本响应(如 HTML/CSS/JS 等),还可以对图像进行操作。(示例)
注意:responseInterceptor 会禁用目标响应的流式传输
const { createProxyMiddleware, responseInterceptor } = require('http-proxy-middleware');
const proxy = createProxyMiddleware({
/**
* IMPORTANT: avoid res.end being called automatically
**/
selfHandleResponse: true, // res.end() will be called internally by responseInterceptor()
/**
* Intercept response and replace 'Hello' with 'Goodbye'
**/
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const response = responseBuffer.toString('utf8'); // convert buffer to string
return response.replace('Hello', 'Goodbye'); // manipulate response and return the result
}),
},
});
查看 interception recipes 获取更多的例子
Node.js 17+: IPv6 和 localhost 的 ECONNREFUSED 问题 (#705)
Node.js 17+ 在 DNS 查询时不再优先使用 IPv4 而非 IPv6。例如,localhost 不一定会被解析为 127.0.0.1 —— 它也可能被解析为 ::1(或其他 IP 地址)。
如果你的目标服务器仅接受 IPv4 连接,当 localhost 被解析为 ::1(IPv6)时,尝试代理到 localhost 将会失败。
解决方法:
将 target: “http://localhost” 改为 target: “http://127.0.0.1”(IPv4)。
修改目标服务器以(同时)接受 IPv6 连接。
在运行 Node.js 时添加此标志:node index.js --dns-result-order=ipv4first。(不推荐。)
注意:有一种称为 Happy Eyeballs 的机制,意味着同时并行连接 IPv4 和 IPv6,Node.js 并不支持此机制,但这解释了为什么例如 curl 可以成功连接。
Debugging
配置 DEBUG 环境变量来启用 debug 输出
查看 debug 项目更多属性
DEBUG=http-proxy-middleware* node server.js
$ http-proxy-middleware proxy created +0ms
$ http-proxy-middleware proxying request to target: 'http://www.example.org' +359ms
操作实例
iew and play around with working examples
Browser-Sync (example source)
express (example source)
connect (example source)
WebSocket (example source)
Response Manipulation (example source)
秘籍
查看更多秘籍 recipes .
兼容服务器
http-proxy-middleware 兼容下列服务器
connect
express
next.js
fastify
browser-sync
lite-server
polka
grunt-contrib-connect
grunt-browser-sync
gulp-connect
gulp-webserver