update
This commit is contained in:
@@ -116,4 +116,6 @@ export const httpProxy = (req: http.IncomingMessage, res: http.ServerResponse, p
|
||||
// 处理 POST 请求的请求体(传递数据到目标服务器),end:true 表示当请求体结束时,关闭请求
|
||||
// req.pipe(proxyReq, { end: true });
|
||||
pipeProxyReq(req, proxyReq, res);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import { isBun } from './utils.ts';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* 文件流管道传输函数
|
||||
@@ -25,7 +26,7 @@ export const pipeFileStream = (filePath: string, res: http.ServerResponse) => {
|
||||
* @param readStream 可读流对象
|
||||
* @param res HTTP服务器响应对象
|
||||
*/
|
||||
export const pipeStream = (readStream: fs.ReadStream, res: http.ServerResponse) => {
|
||||
export const pipeStream = (readStream: fs.ReadStream | Readable, res: http.ServerResponse) => {
|
||||
if (isBun) {
|
||||
// Bun环境下的流处理方式
|
||||
res.pipe(readStream as any);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import http from 'node:http';
|
||||
import { httpProxy } from './http-proxy.ts';
|
||||
import { s3Proxy } from './s3.ts';
|
||||
export type ProxyInfo = {
|
||||
/**
|
||||
* 代理路径, 比如/root/home, 匹配的路径
|
||||
@@ -10,7 +13,7 @@ export type ProxyInfo = {
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
type?: 'file' | 'dynamic' | 'minio' | 'http';
|
||||
type?: 'file' | 'dynamic' | 'minio' | 'http' | 's3';
|
||||
/**
|
||||
* 目标的 pathname, 默认为请求的url.pathname, 设置了pathname,则会使用pathname作为请求的url.pathname
|
||||
* @default undefined
|
||||
@@ -23,40 +26,30 @@ export type ProxyInfo = {
|
||||
*/
|
||||
ws?: boolean;
|
||||
/**
|
||||
* type为file时有效
|
||||
* 索引文件,比如index.html, type为fileProxy代理有用 设置了索引文件,如果文件不存在,则访问索引文件
|
||||
*/
|
||||
* type为file时有效
|
||||
* 索引文件,比如index.html, type为fileProxy代理有用 设置了索引文件,如果文件不存在,则访问索引文件
|
||||
*/
|
||||
indexPath?: string;
|
||||
/**
|
||||
* type为file时有效
|
||||
* 根路径, 默认是process.cwd(), type为fileProxy代理有用,必须为绝对路径
|
||||
*/
|
||||
rootPath?: string;
|
||||
s3?: {
|
||||
bucket: string;
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
endpoint?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type ApiList = {
|
||||
path: string;
|
||||
/**
|
||||
* url或者相对路径
|
||||
*/
|
||||
target: string;
|
||||
/**
|
||||
* 目标地址
|
||||
*/
|
||||
ws?: boolean;
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
type?: 'static' | 'dynamic' | 'minio';
|
||||
}[];
|
||||
|
||||
/**
|
||||
|
||||
[
|
||||
{
|
||||
path: '/api/v1/user',
|
||||
target: 'http://localhost:3000/api/v1/user',
|
||||
type: 'dynamic',
|
||||
},
|
||||
]
|
||||
*/
|
||||
export const proxy = (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
||||
if (proxyApi.type === 'http' || !proxyApi.type) {
|
||||
return httpProxy(req, res, proxyApi);
|
||||
}
|
||||
console.log('proxyApi', proxyApi);
|
||||
if (proxyApi.type === 's3') {
|
||||
return s3Proxy(req, res, proxyApi);
|
||||
}
|
||||
}
|
||||
71
assistant/src/module/assistant/proxy/s3.ts
Normal file
71
assistant/src/module/assistant/proxy/s3.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { S3Client } from '@aws-sdk/client-s3';
|
||||
import { ProxyInfo } from './proxy.ts';
|
||||
import http from 'http';
|
||||
import { OssBase } from '@kevisual/oss/s3.ts';
|
||||
import { pipeStream } from './pipe.ts';
|
||||
import { Readable } from 'stream';
|
||||
const mapS3 = new Map<string, S3Client>();
|
||||
|
||||
export const s3Proxy = async (req: http.IncomingMessage, res: http.ServerResponse, proxyApi: ProxyInfo) => {
|
||||
const s3 = proxyApi.s3;
|
||||
if (!s3) {
|
||||
res.statusCode = 500;
|
||||
res.end('S3 config not found');
|
||||
return;
|
||||
}
|
||||
let findClient = mapS3.get(s3.accessKeyId);
|
||||
let s3Client: S3Client;
|
||||
if (findClient) {
|
||||
s3Client = findClient;
|
||||
} else {
|
||||
s3Client = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: s3.accessKeyId,
|
||||
secretAccessKey: s3.secretAccessKey,
|
||||
},
|
||||
region: s3.region,
|
||||
endpoint: s3.endpoint,
|
||||
});
|
||||
mapS3.set(s3.accessKeyId, s3Client);
|
||||
}
|
||||
const oss = new OssBase({
|
||||
client: s3Client!,
|
||||
bucketName: s3.bucket,
|
||||
});
|
||||
|
||||
const requestUrl = new URL(req.url, 'http://localhost');
|
||||
const proxyPath = proxyApi.path || '';
|
||||
let objectPath = requestUrl.pathname.replace(proxyPath + '/', '');
|
||||
if (objectPath.startsWith(s3.bucket + '/')) {
|
||||
objectPath = objectPath.replace(s3.bucket + '/', '');
|
||||
}
|
||||
oss.getObject(objectPath).then((response) => {
|
||||
if (!response.Body) {
|
||||
res.statusCode = 404;
|
||||
res.end('Object not found');
|
||||
return;
|
||||
}
|
||||
// 设置响应头
|
||||
if (response.ContentType) {
|
||||
res.setHeader('Content-Type', response.ContentType);
|
||||
}
|
||||
if (response.ContentLength) {
|
||||
res.setHeader('Content-Length', response.ContentLength);
|
||||
}
|
||||
if (response.LastModified) {
|
||||
res.setHeader('Last-Modified', response.LastModified.toUTCString());
|
||||
}
|
||||
if (response.ETag) {
|
||||
res.setHeader('ETag', response.ETag);
|
||||
}
|
||||
|
||||
// response.Body 已经是 Readable 流,直接 pipe 到 res
|
||||
// (response.Body as Readable).pipe(res);
|
||||
pipeStream(response.Body as Readable, res);
|
||||
}).catch((err) => {
|
||||
console.error('S3 getObject error:', err);
|
||||
res.statusCode = 500;
|
||||
res.end(`S3 error: ${err.message}`);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user