Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,20 @@ $ dev proxy --port=12778 --target=12777 --ssl
# 将 12778 端口的 http 请求代理到 https://www.baidu.com
$ dev proxy --port=12778 --target=https://www.baidu.com

# 使用 HTTP/2 协议启动代理服务器
$ dev proxy --port=12778 --target=12777 --http2

```

**参数说明:**
* `--port`: 指定代理服务器监听端口,默认 12778
* `--target`: 指定后端目标地址,可以是完整 URL 或端口号
* `--ssl`: 启用 HTTPS(HTTP/1.1)
* `--http2`: 启用 HTTP/2 协议(自动启用 HTTPS,支持 HTTP/1.1 回退)
* `--ssl-cert`: 指定 SSL 证书文件路径(可选)
* `--ssl-key`: 指定 SSL 密钥文件路径(可选)



### where 命令:查找本地的全局命令位置
```shell
Expand Down
138 changes: 124 additions & 14 deletions packages/dev/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getIp } from './utils';
const httpProxy = require('http-proxy');
const https = require('https');
const http = require('http');
const http2 = require('http2');


export class ProxyPlugin extends BasePlugin {
Expand All @@ -23,33 +24,142 @@ export class ProxyPlugin extends BasePlugin {
async hanldeproxy() {
const port = this.options.port || 12778;
const ssl = this.options.ssl || false;
const http2Enabled = this.options.http2 || false;
const sslCert = this.options['ssl-cert'] || join(__dirname, '../static/default-cert.pem');
const sslKey = this.options['ssl-key'] || join(__dirname, '../static/default-key.pem');
let target = this.options.target || 'https://www.baidu.com';
if (/^\d+$/.test(`${target}`)) {
target = `http://127.0.0.1:${target}`;
}
const proxy = httpProxy.createProxyServer({
target,
changeOrigin: true,
});
const proxyCallback = function (req, res) {
console.log(`- [${req.method} - ${req.url}]`);
proxy.web(req, res);
}

if (ssl) {
if (http2Enabled) {
// HTTP/2 requires SSL/TLS
const options = {
key: readFileSync(sslKey),
cert: readFileSync(sslCert)
cert: readFileSync(sslCert),
allowHTTP1: true
};
https.createServer(options, proxyCallback).listen(port);
const server = http2.createSecureServer(options);

// Headers that are not allowed in HTTP/2
const forbiddenHeaders = [
'connection',
'keep-alive',
'proxy-connection',
'transfer-encoding',
'upgrade'
];

// Handle HTTP/2 streams
server.on('stream', (stream, headers) => {
// Extract HTTP/2 pseudo-headers
const method = headers[':method'];
const path = headers[':path'];
const authority = headers[':authority'];

console.log(`- [${method} - ${path}]`);

// Prepare request headers for backend
const requestHeaders = {};
for (const name in headers) {
if (!name.startsWith(':')) {
requestHeaders[name] = headers[name];
}
}

// Ensure host header exists
if (!requestHeaders.host && authority) {
requestHeaders.host = authority;
}

// Parse target URL
const targetUrl = new URL(target);
const isSecure = targetUrl.protocol === 'https:';
const backendModule = isSecure ? https : http;

// Make request to backend
const backendReq = backendModule.request({
hostname: targetUrl.hostname,
port: targetUrl.port || (isSecure ? 443 : 80),
path: path,
method: method,
headers: requestHeaders
}, (backendRes) => {
// Send response headers to client
const responseHeaders = { ':status': backendRes.statusCode };

// Filter out forbidden HTTP/2 headers
for (const name in backendRes.headers) {
if (!forbiddenHeaders.includes(name.toLowerCase())) {
responseHeaders[name] = backendRes.headers[name];
}
}

stream.respond(responseHeaders);

// Pipe response body to client
backendRes.pipe(stream);
});

// Handle errors
backendReq.on('error', (err) => {
console.error('Backend request error:', err.message);
if (!stream.headersSent) {
stream.respond({ ':status': 502 });
}
stream.end('Bad Gateway');
});

// Pipe request body to backend
stream.pipe(backendReq);
});

// Handle HTTP/1.1 requests (when allowHTTP1 is true)
const proxy = httpProxy.createProxyServer({
target,
changeOrigin: true,
});

server.on('request', (req, res) => {
// Only handle if it's truly HTTP/1.1 (not HTTP/2 compatibility mode)
// In HTTP/2 compatibility mode, httpVersion will be '2.0'
if (req.httpVersion === '2.0' || req.httpVersionMajor === 2) {
// This is an HTTP/2 request in compatibility mode, ignore it
// as it's already handled by the 'stream' event
return;
}
console.log(`- [${req.method} - ${req.url}]`);
proxy.web(req, res);
});

server.listen(port);
console.log(`[dev proxy server] started at https://127.0.0.1:${port} (HTTP/2)`);
console.log(`[dev proxy server] started at https://${getIp()}:${port} (HTTP/2)`);
} else {
http.createServer(proxyCallback).listen(port);
// Regular HTTP/1.1 proxy using http-proxy
const proxy = httpProxy.createProxyServer({
target,
changeOrigin: true,
});
const proxyCallback = function (req, res) {
console.log(`- [${req.method} - ${req.url}]`);
proxy.web(req, res);
}

if (ssl) {
const options = {
key: readFileSync(sslKey),
cert: readFileSync(sslCert)
};
https.createServer(options, proxyCallback).listen(port);
console.log(`[dev proxy server] started at https://127.0.0.1:${port}`);
console.log(`[dev proxy server] started at https://${getIp()}:${port}`);
} else {
http.createServer(proxyCallback).listen(port);
console.log(`[dev proxy server] started at http://127.0.0.1:${port}`);
console.log(`[dev proxy server] started at http://${getIp()}:${port}`);
}
}
console.log(`[dev proxy server] started at http${ssl ? 's': ''}://127.0.0.1:${port}`);
console.log(`[dev proxy server] started at http${ssl ? 's': ''}://${getIp()}:${port}`);
console.log(`[dev proxy server] proxy to ${target}`);
return new Promise(resolve => {});
}
Expand Down