您现在的位置是:网站首页 > 网络代理实现文章详情
网络代理实现
陈川
【
Node.js
】
18136人已围观
11678字
网络代理的基本概念
网络代理是一种中间服务器,充当客户端和目标服务器之间的中介。客户端向代理发送请求,代理再将请求转发给目标服务器,并将响应返回给客户端。这种架构可以实现多种功能,如负载均衡、缓存、访问控制、内容过滤等。在Node.js中,我们可以利用其强大的网络模块和丰富的第三方库轻松实现各种代理功能。
HTTP代理的实现
使用Node.js的http
模块可以快速创建一个基本的HTTP代理服务器。以下是一个简单示例:
const http = require('http');
const url = require('url');
const proxy = http.createServer((req, res) => {
const options = {
hostname: url.parse(req.url).hostname,
port: 80,
path: url.parse(req.url).path,
method: req.method,
headers: req.headers
};
const proxyReq = http.request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
req.pipe(proxyReq);
});
proxy.listen(8080, () => {
console.log('Proxy server running on port 8080');
});
这个代理服务器会监听8080端口,将所有接收到的请求转发到目标服务器,并将响应返回给客户端。它保留了原始请求的HTTP方法和头部信息。
HTTPS代理的实现
处理HTTPS请求需要额外的工作,因为HTTPS使用SSL/TLS加密。以下是HTTPS代理的实现示例:
const https = require('https');
const http = require('http');
const url = require('url');
const { createSecureContext } = require('tls');
const proxy = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url);
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || 443,
path: parsedUrl.path,
method: req.method,
headers: req.headers,
rejectUnauthorized: false // 忽略证书验证错误
};
const proxyReq = https.request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
req.pipe(proxyReq);
});
proxy.listen(8080, () => {
console.log('HTTPS Proxy server running on port 8080');
});
使用第三方库实现高级代理
http-proxy
是一个流行的Node.js代理库,提供了更多高级功能:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({});
const server = http.createServer((req, res) => {
// 可以根据请求路径转发到不同服务器
if (req.url.startsWith('/api')) {
proxy.web(req, res, { target: 'http://api.example.com' });
} else {
proxy.web(req, res, { target: 'http://web.example.com' });
}
});
server.listen(8080, () => {
console.log('Advanced proxy server running on port 8080');
});
WebSocket代理实现
代理WebSocket连接需要特殊处理,因为它是持久连接。使用http-proxy
可以轻松实现:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({
target: 'ws://echo.websocket.org',
ws: true
});
const server = http.createServer((req, res) => {
// 处理普通HTTP请求
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Proxy server is running');
});
server.on('upgrade', (req, socket, head) => {
// 处理WebSocket升级请求
proxy.ws(req, socket, head);
});
server.listen(8080, () => {
console.log('WebSocket proxy server running on port 8080');
});
反向代理与负载均衡
反向代理常用于负载均衡,将请求分发到多个后端服务器:
const http = require('http');
const httpProxy = require('http-proxy');
const servers = [
{ host: 'localhost', port: 3001 },
{ host: 'localhost', port: 3002 },
{ host: 'localhost', port: 3003 }
];
const proxy = httpProxy.createProxyServer();
let current = 0;
const server = http.createServer((req, res) => {
// 简单的轮询负载均衡
const target = servers[current];
current = (current + 1) % servers.length;
proxy.web(req, res, { target: `http://${target.host}:${target.port}` });
});
server.listen(8080, () => {
console.log('Load balancing proxy running on port 8080');
});
代理请求修改
代理服务器可以修改请求和响应内容。以下示例展示了如何修改响应内容:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();
proxy.on('proxyRes', (proxyRes, req, res) => {
let body = [];
proxyRes.on('data', (chunk) => {
body.push(chunk);
});
proxyRes.on('end', () => {
body = Buffer.concat(body).toString();
// 修改响应内容
body = body.replace(/example/g, 'proxy-example');
res.setHeader('content-length', Buffer.byteLength(body));
res.end(body);
});
});
const server = http.createServer((req, res) => {
proxy.web(req, res, { target: 'http://example.com' });
});
server.listen(8080, () => {
console.log('Content modifying proxy running on port 8080');
});
代理认证和访问控制
实现基本的HTTP认证代理:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();
const server = http.createServer((req, res) => {
const auth = req.headers['proxy-authorization'];
if (!auth || !auth.startsWith('Basic ')) {
res.writeHead(407, {
'Proxy-Authenticate': 'Basic realm="Proxy Authentication Required"'
});
return res.end();
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString();
const [username, password] = credentials.split(':');
if (username !== 'admin' || password !== 'secret') {
res.writeHead(403);
return res.end('Forbidden');
}
proxy.web(req, res, { target: 'http://example.com' });
});
server.listen(8080, () => {
console.log('Authenticating proxy running on port 8080');
});
透明代理实现
透明代理不需要客户端配置,通常用于网络中间层:
const net = require('net');
const http = require('http');
const server = net.createServer((clientSocket) => {
clientSocket.once('data', (data) => {
// 检查是否是HTTP请求
if (data.toString().indexOf('HTTP') === 0) {
const proxyReq = http.request({
host: 'example.com',
port: 80,
method: 'GET',
path: '/',
headers: {
'Host': 'example.com',
'Connection': 'keep-alive'
}
}, (proxyRes) => {
clientSocket.write('HTTP/1.1 200 OK\r\n');
proxyRes.pipe(clientSocket);
});
proxyReq.end();
} else {
// 处理非HTTP流量
const targetSocket = net.connect(80, 'example.com', () => {
targetSocket.write(data);
clientSocket.pipe(targetSocket);
targetSocket.pipe(clientSocket);
});
}
});
});
server.listen(8080, () => {
console.log('Transparent proxy running on port 8080');
});
代理缓存实现
实现一个带有缓存功能的代理服务器:
const http = require('http');
const httpProxy = require('http-proxy');
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 60 }); // 缓存60秒
const proxy = httpProxy.createProxyServer();
const server = http.createServer((req, res) => {
const cacheKey = req.url;
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
console.log('Serving from cache');
res.writeHead(200, cachedResponse.headers);
return res.end(cachedResponse.body);
}
proxy.web(req, res, { target: 'http://example.com' }, (err) => {
res.writeHead(500);
res.end('Proxy error');
});
});
proxy.on('proxyRes', (proxyRes, req, res) => {
let body = [];
proxyRes.on('data', (chunk) => {
body.push(chunk);
});
proxyRes.on('end', () => {
body = Buffer.concat(body);
const cacheData = {
headers: proxyRes.headers,
body: body
};
cache.set(req.url, cacheData);
});
});
server.listen(8080, () => {
console.log('Caching proxy running on port 8080');
});
代理服务器的性能优化
优化代理服务器性能的几个关键点:
- 连接池管理:重用与目标服务器的连接
- 流式处理:避免缓冲整个请求/响应
- 事件驱动:充分利用Node.js的非阻塞特性
const http = require('http');
const httpProxy = require('http-proxy');
const agent = require('agentkeepalive');
const keepaliveAgent = new agent({
maxSockets: 100,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000
});
const proxy = httpProxy.createProxyServer({
agent: keepaliveAgent,
xfwd: true, // 添加X-Forwarded-*头
prependPath: false,
changeOrigin: true
});
const server = http.createServer((req, res) => {
proxy.web(req, res, { target: 'http://example.com' });
});
server.listen(8080, () => {
console.log('Optimized proxy running on port 8080');
});
代理服务器的错误处理
健壮的代理服务器需要完善的错误处理机制:
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();
proxy.on('error', (err, req, res) => {
console.error('Proxy error:', err);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Proxy server encountered an error');
});
proxy.on('proxyReq', (proxyReq, req, res, options) => {
proxyReq.on('error', (err) => {
console.error('Request error:', err);
});
});
proxy.on('proxyRes', (proxyRes, req, res) => {
proxyRes.on('error', (err) => {
console.error('Response error:', err);
});
});
const server = http.createServer((req, res) => {
proxy.web(req, res, { target: 'http://example.com' });
});
server.listen(8080, () => {
console.log('Error-handling proxy running on port 8080');
});
代理服务器的日志记录
记录代理服务器的活动对于调试和监控很重要:
const http = require('http');
const httpProxy = require('http-proxy');
const fs = require('fs');
const morgan = require('morgan');
const accessLogStream = fs.createWriteStream('proxy-access.log', { flags: 'a' });
const proxy = httpProxy.createProxyServer();
const server = http.createServer((req, res) => {
const start = Date.now();
morgan('combined', { stream: accessLogStream })(req, res, () => {});
proxy.web(req, res, { target: 'http://example.com' }, (err) => {
const duration = Date.now() - start;
console.error(`Request failed after ${duration}ms:`, err);
});
proxy.on('proxyRes', (proxyRes) => {
const duration = Date.now() - start;
console.log(`Request completed in ${duration}ms with status ${proxyRes.statusCode}`);
});
});
server.listen(8080, () => {
console.log('Logging proxy running on port 8080');
});
代理服务器的安全考虑
实现代理服务器时需要考虑的安全问题:
- 防止代理被滥用(开放代理问题)
- 请求头过滤
- 响应头过滤
- 请求大小限制
- 速率限制
const http = require('http');
const httpProxy = require('http-proxy');
const rateLimit = require('express-rate-limit');
const proxy = httpProxy.createProxyServer();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100个请求
});
const server = http.createServer((req, res) => {
// 应用速率限制
limiter(req, res, () => {
// 检查目标主机是否允许
const targetHost = req.headers['x-target-host'] || 'example.com';
if (!isAllowedHost(targetHost)) {
res.writeHead(403);
return res.end('Forbidden target host');
}
// 过滤敏感请求头
delete req.headers['cookie'];
delete req.headers['authorization'];
proxy.web(req, res, {
target: `http://${targetHost}`,
changeOrigin: true
});
});
});
function isAllowedHost(host) {
const allowedHosts = ['example.com', 'api.example.com'];
return allowedHosts.includes(host);
}
server.listen(8080, () => {
console.log('Secure proxy running on port 8080');
});
代理服务器的扩展功能
代理服务器可以扩展更多高级功能:
- 请求重写
- 响应重写
- A/B测试
- 灰度发布
- API聚合
const http = require('http');
const httpProxy = require('http-proxy');
const URL = require('url');
const proxy = httpProxy.createProxyServer();
const server = http.createServer((req, res) => {
const parsedUrl = URL.parse(req.url);
// API版本控制
if (parsedUrl.pathname.startsWith('/api/v1')) {
return proxy.web(req, res, { target: 'http://api-v1.example.com' });
}
if (parsedUrl.pathname.startsWith('/api/v2')) {
return proxy.web(req, res, { target: 'http://api-v2.example.com' });
}
// A/B测试
if (parsedUrl.pathname === '/feature') {
const group = Math.random() > 0.5 ? 'a' : 'b';
return proxy.web(req, res, {
target: `http://feature-${group}.example.com`,
path: parsedUrl.pathname
});
}
// 默认路由
proxy.web(req, res, { target: 'http://web.example.com' });
});
server.listen(8080, () => {
console.log('Extended feature proxy running on port 8080');
});
上一篇: WebSocket实现
下一篇: DNS解析