fix: 配置修改,添加socket代理

This commit is contained in:
abearxiong 2025-03-06 22:11:34 +08:00
parent d548d3ab04
commit bb86a7c507
10 changed files with 867 additions and 161 deletions

View File

@ -1,8 +1,18 @@
{
api: {
host: 'localhost:4002', // 后台代理
target: 'http://localhost:4002', // 后台代理
path: '/api/router',
},
apiList: [
{
path: '/api/router',
target: 'http://localhost:4002',
},
{
path: '/api/v1',
target: 'http://localhost:3005',
},
],
proxy: {
port: 3005,
domain: 'kevisual.xiongxiao.me',

View File

@ -1,6 +1,6 @@
{
"name": "page-proxy",
"version": "0.0.2-beta.1",
"version": "0.0.2-beta.3",
"description": "",
"main": "index.js",
"type": "module",
@ -35,22 +35,25 @@
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.1.2",
"@types/http-proxy": "^1.17.16",
"@types/node": "^22.13.5",
"@types/node": "^22.13.9",
"@types/send": "^0.17.4",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"nodemon": "^3.1.9",
"rollup": "^4.34.8",
"rollup": "^4.34.9",
"tslib": "^2.8.1",
"typescript": "^5.7.3"
"typescript": "^5.8.2"
},
"dependencies": {
"@kevisual/code-center-module": "0.0.11-alpha.1",
"@kevisual/router": "0.0.7",
"@kevisual/use-config": "^1.0.8",
"@kevisual/code-center-module": "0.0.11-alpha.3",
"@kevisual/router": "0.0.9",
"@kevisual/use-config": "^1.0.9",
"archiver": "^7.0.1",
"ioredis": "^5.5.0",
"ioredis": "^5.6.0",
"minio": "^8.0.4",
"nanoid": "^5.1.2",
"sequelize": "^6.37.5"
"send": "^1.1.0",
"sequelize": "^6.37.6"
},
"resolutions": {
"picomatch": "^4.0.2"

821
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ import { handleRequest } from './module/index.ts';
import { config } from './module/config.ts';
import { app } from './app.ts';
import './route/route.ts';
import net from 'net';
const port = config?.proxy?.port || 3005;
app
@ -19,3 +20,47 @@ app.listen(port, () => {
});
app.server.on(handleRequest);
const main = () => {
console.log('Upgrade initialization started');
app.server.server.on('upgrade', (req, socket, head) => {
const proxyApiList = config?.apiList || [];
const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path));
if (proxyApi) {
const _u = new URL(req.url, `${proxyApi.target}`);
const options = {
hostname: _u.hostname,
port: Number(_u.port) || 80,
path: _u.pathname,
headers: req.headers,
};
const proxySocket = net.connect(options.port, options.hostname, () => {
proxySocket.write(
`GET ${options.path} HTTP/1.1\r\n` +
`Host: ${options.hostname}\r\n` +
`Connection: Upgrade\r\n` +
`Upgrade: websocket\r\n` +
`Sec-WebSocket-Key: ${req.headers['sec-websocket-key']}\r\n` +
`Sec-WebSocket-Version: ${req.headers['sec-websocket-version']}\r\n` +
`\r\n`
);
proxySocket.pipe(socket);
socket.pipe(proxySocket);
});
proxySocket.on('error', (err) => {
console.error(`WebSocket proxy error: ${err.message}`);
socket.end();
});
} else {
socket.end();
}
});
};
setTimeout(() => {
main();
}, 1200);

View File

@ -11,6 +11,17 @@ type ConfigType = {
path?: string;
port?: number;
};
apiList: {
path: string;
/**
* url或者相对路径
*/
target: string;
/**
*
*/
type?: 'static' | 'dynamic' | 'minio';
}[];
proxy: {
port?: number;
/**

View File

@ -6,6 +6,8 @@ import path from 'path';
import fs from 'fs';
import { getContentType } from './get-content-type.ts';
import { createRefreshHtml } from './html/create-refresh-html.ts';
import { fileProxy } from './proxy/file-proxy.ts';
import net from 'net';
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
const domain = config?.proxy?.domain || 'kevisual.xiongxiao.me';
@ -20,11 +22,18 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
return;
}
if (req.url.startsWith('/api/proxy')) {
// 已经代理过了
return;
}
if (req.url.startsWith('/api')) {
// 代理到 http://codeflow.xiongxiao.me/api
const _u = new URL(req.url, `http://${api.host}`);
console.log('req', req.url, 'len', config?.apiList?.length);
const proxyApiList = config?.apiList || [];
const proxyApi = proxyApiList.find((item) => req.url.startsWith(item.path));
if (proxyApi && proxyApi?.type === 'static') {
return fileProxy(req, res, proxyApi);
}
if (proxyApi) {
const _u = new URL(req.url, `${proxyApi.target}`);
console.log('proxyApi', req.url, _u.href);
// 设置代理请求的目标 URL 和请求头
let header: any = {};
if (req.headers?.['Authorization']) {
@ -32,9 +41,11 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
} else if (req.headers?.['authorization']) {
header.authorization = req.headers['authorization'];
}
if (req.headers?.['Content-Type']) {
header['Content-Type'] = req.headers?.['Content-Type'];
}
// 提取req的headers中的非HOST的header
const headers = Object.keys(req.headers).filter((item) => item && item.toLowerCase() !== 'host');
headers.forEach((item) => {
header[item] = req.headers[item];
});
const options = {
host: _u.hostname,
path: req.url,
@ -64,7 +75,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
req.pipe(proxyReq, { end: true });
return;
}
if (req.url.startsWith('/api')) {
if (req.url.startsWith('/api') || req.url.startsWith('/v1')) {
res.end('not catch api');
return;
}
@ -244,3 +255,4 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
console.error('getFile error', error);
}
};

24
src/module/minio.ts Normal file
View File

@ -0,0 +1,24 @@
import { Client, ClientOptions } from 'minio';
import { useConfig } from '@kevisual/use-config';
type MinioConfig = {
minio: ClientOptions & { bucketName: string };
};
const config = useConfig<MinioConfig>();
const { bucketName, ...minioRest } = config.minio;
export const minioClient = new Client(minioRest);
export { bucketName };
if (!minioClient) {
throw new Error('Minio client not initialized');
}
// 验证权限
// (async () => {
// const bucketExists = await minioClient.bucketExists(bucketName);
// if (!bucketExists) {
// await minioClient.makeBucket(bucketName);
// }
// const res = await minioClient.putObject(bucketName, 'private/test/a.b', 'test');
// console.log('minio putObject', res);
// })();

View File

@ -0,0 +1,26 @@
import http from 'http';
import send from 'send';
import fs from 'fs';
import { fileIsExist } from '@kevisual/use-config';
import path from 'path';
export type ProxyInfo = {
path?: string;
target: string;
type?: 'static' | 'dynamic' | 'minio';
};
export const fileProxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
// url开头的文件
const url = new URL(req.url, 'http://localhost');
const pathname = url.pathname;
// 检测文件是否存在如果文件不存在则返回404
const filePath = path.join(process.cwd(), proxyApi.target, pathname);
if (!fileIsExist(filePath)) {
res.statusCode = 404;
res.end('Not Found File');
return;
}
const file = send(req, pathname, {
root: proxyApi.target,
});
file.pipe(res);
};

View File

@ -0,0 +1,24 @@
import http from 'http';
import { minioClient } from '../minio.ts';
export type ProxyInfo = {
path?: string;
target: string;
type?: 'static' | 'dynamic' | 'minio';
};
export const minioProxy = async (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
try {
const requestUrl = new URL(req.url, 'http://localhost');
const objectPath = requestUrl.pathname;
const bucketName = proxyApi.target;
let objectName = objectPath.slice(1);
if (objectName.startsWith(bucketName)) {
objectName = objectName.slice(bucketName.length);
}
const objectStream = await minioClient.getObject(bucketName, objectName);
objectStream.pipe(res);
} catch (error) {
console.error('Error fetching object from MinIO:', error);
res.statusCode = 500;
res.end('Internal Server Error');
}
};

View File

@ -0,0 +1,18 @@
import net from 'net';
const main = () => {
const options = {
port: 3003,
hostname: '192.168.31.220',
path: '/api/v1',
};
const proxySocket = net.connect(options.port, options.hostname, () => {
proxySocket.write(`GET ${options.path} HTTP/1.1\r\n` + `Host: ${options.hostname}\r\n` + `Connection: Upgrade\r\n` + `Upgrade: websocket\r\n` + `\r\n`);
});
proxySocket.on('data', (data) => {
console.log('data', data.toString());
});
};
main();