feat: add minio proxy
This commit is contained in:
parent
bb86a7c507
commit
07cfa1dded
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,3 +11,6 @@ release/*
|
||||
!release/.gitkeep
|
||||
|
||||
/*.tgz
|
||||
|
||||
proxy-upload/*
|
||||
proxy-upload/.gitkeep
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
api: {
|
||||
target: 'http://localhost:4002', // 后台代理
|
||||
host: 'http://localhost:4002', // 后台代理
|
||||
path: '/api/router',
|
||||
},
|
||||
apiList: [
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "page-proxy",
|
||||
"version": "0.0.2-beta.3",
|
||||
"version": "0.0.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
@ -62,7 +62,15 @@ export class UserApp {
|
||||
const user = this.user;
|
||||
const key = 'user:app:exist:' + app + ':' + user;
|
||||
const value = await redis.get(key);
|
||||
return value;
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const [indexFilePath, etag, proxy] = value.split('||');
|
||||
return {
|
||||
indexFilePath,
|
||||
etag,
|
||||
proxy: proxy === 'true',
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 获取缓存数据,不存在不会加载
|
||||
@ -83,6 +91,8 @@ export class UserApp {
|
||||
const user = this.user;
|
||||
const key = 'user:app:set:' + app + ':' + user;
|
||||
const value = await redis.hget(key, appFileUrl);
|
||||
// const values = await redis.hgetall(key);
|
||||
// console.log('getFile', values);
|
||||
return value;
|
||||
}
|
||||
static async getDomainApp(domain: string) {
|
||||
@ -170,7 +180,28 @@ export class UserApp {
|
||||
// return false;
|
||||
fetchData.type = 'oss';
|
||||
}
|
||||
console.log('fetchData', JSON.stringify(fetchData.data.files, null, 2));
|
||||
|
||||
this.setLoaded('loading', 'loading');
|
||||
const loadProxy = async () => {
|
||||
const value = fetchData;
|
||||
await redis.set(key, JSON.stringify(value));
|
||||
const version = value.version;
|
||||
let indexHtml = resources + '/' + user + '/' + app + '/' + version + '/index.html';
|
||||
const files = value?.data?.files || [];
|
||||
const data = {};
|
||||
|
||||
// 将文件名和路径添加到 `data` 对象中
|
||||
files.forEach((file) => {
|
||||
if (file.name === 'index.html') {
|
||||
indexHtml = resources + '/' + file.path;
|
||||
}
|
||||
data[file.name] = resources + '/' + file.path;
|
||||
});
|
||||
await redis.set('user:app:exist:' + app + ':' + user, indexHtml + '||etag||true', 'EX', 60 * 60 * 24 * 7); // 7天
|
||||
await redis.hset('user:app:set:' + app + ':' + user, data);
|
||||
this.setLoaded('running', 'loaded');
|
||||
};
|
||||
const loadFilesFn = async () => {
|
||||
const value = await downloadUserAppFiles(user, app, fetchData);
|
||||
if (value.data.files.length === 0) {
|
||||
@ -191,15 +222,8 @@ export class UserApp {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
}
|
||||
let valueIndexHtml = value.data.files.find((file) => file.name === 'index.html');
|
||||
if (!valueIndexHtml) {
|
||||
valueIndexHtml = value.data.files.find((file) => file.name === 'index.js');
|
||||
if (!valueIndexHtml) {
|
||||
valueIndexHtml = value.data.files[0];
|
||||
}
|
||||
}
|
||||
await redis.set(key, JSON.stringify(value));
|
||||
await redis.set('user:app:exist:' + app + ':' + user, valueIndexHtml.path, 'EX', 60 * 60 * 24 * 7); // 7天
|
||||
await redis.set('user:app:exist:' + app + ':' + user, 'index.html||etag||false', 'EX', 60 * 60 * 24 * 7); // 7天
|
||||
const files = value.data.files;
|
||||
const data = {};
|
||||
|
||||
@ -211,7 +235,15 @@ export class UserApp {
|
||||
this.setLoaded('running', 'loaded');
|
||||
};
|
||||
try {
|
||||
loadFilesFn();
|
||||
if (fetchData.proxy === true) {
|
||||
await loadProxy();
|
||||
return {
|
||||
code: 200,
|
||||
data: 'loaded',
|
||||
};
|
||||
} else {
|
||||
loadFilesFn();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('loadFilesFn error', e);
|
||||
this.setLoaded('error', 'loadFilesFn error');
|
||||
@ -293,11 +325,15 @@ export const downloadUserAppFiles = async (user: string, app: string, data: type
|
||||
}
|
||||
if (data.type === 'oss') {
|
||||
let serverPath = new URL(resources).href + '/';
|
||||
let hasIndexHtml = false;
|
||||
// server download file
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const destFile = path.join(uploadFiles, file.name);
|
||||
const destDir = path.dirname(destFile); // 获取目标文件所在的目录路径
|
||||
if (file.name === 'index.html') {
|
||||
hasIndexHtml = true;
|
||||
}
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
if (!checkFileExistsSync(destDir)) {
|
||||
fs.mkdirSync(destDir, { recursive: true }); // 递归创建目录
|
||||
@ -310,6 +346,15 @@ export const downloadUserAppFiles = async (user: string, app: string, data: type
|
||||
path: destFile.replace(fileStore, '') + '||' + etag,
|
||||
});
|
||||
}
|
||||
if (!hasIndexHtml) {
|
||||
newFiles.push({
|
||||
name: 'index.html',
|
||||
path: path.join(uploadFiles, 'index.html'),
|
||||
});
|
||||
fs.writeFileSync(path.join(uploadFiles, 'index.html'), JSON.stringify(files), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getDNS, isLocalhost } from '@/utils/dns.ts';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { UserApp } from './get-user-app.ts';
|
||||
import { config, fileStore } from '../module/config.ts';
|
||||
import path from 'path';
|
||||
@ -25,7 +26,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
||||
// 已经代理过了
|
||||
return;
|
||||
}
|
||||
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') {
|
||||
@ -33,7 +33,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
||||
}
|
||||
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']) {
|
||||
@ -160,30 +159,40 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
||||
|
||||
const userApp = new UserApp({ user, app });
|
||||
let isExist = await userApp.getExist();
|
||||
const createRefreshPage = () => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(createRefreshHtml(user, app));
|
||||
};
|
||||
const createErrorPage = () => {
|
||||
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.write('Server Error\n');
|
||||
res.end();
|
||||
};
|
||||
const createNotFoundPage = (msg?: string) => {
|
||||
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.write(msg || 'Not Found App\n');
|
||||
res.end();
|
||||
};
|
||||
if (!isExist) {
|
||||
try {
|
||||
const { code, loading } = await userApp.setCacheData();
|
||||
if (loading || code === 20000) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
|
||||
res.end(createRefreshHtml(user, app));
|
||||
return;
|
||||
return createRefreshPage();
|
||||
} else if (code !== 200) {
|
||||
return createErrorPage();
|
||||
}
|
||||
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.write('Not Found App\n');
|
||||
res.end();
|
||||
// 不存在就一定先返回loading状态。
|
||||
return;
|
||||
isExist = await userApp.getExist();
|
||||
} catch (error) {
|
||||
console.error('setCacheData error', error);
|
||||
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.write('Server Error\n');
|
||||
res.end();
|
||||
createErrorPage();
|
||||
userApp.setLoaded('error', 'setCacheData error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const indexFile = isExist; // 已经必定存在了
|
||||
if (!isExist) {
|
||||
return createNotFoundPage();
|
||||
}
|
||||
const indexFile = isExist.indexFilePath; // 已经必定存在了
|
||||
try {
|
||||
let appFileUrl: string;
|
||||
if (domainApp) {
|
||||
@ -191,15 +200,47 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
||||
} else {
|
||||
appFileUrl = (url + '').replace(`/${user}/${app}/`, '');
|
||||
}
|
||||
|
||||
const appFile = await userApp.getFile(appFileUrl);
|
||||
if (isExist.proxy) {
|
||||
let proxyUrl = appFile || isExist.indexFilePath;
|
||||
if (!proxyUrl.startsWith('http')) {
|
||||
return createNotFoundPage('Invalid proxy url');
|
||||
}
|
||||
let protocol = proxyUrl.startsWith('https') ? https : http;
|
||||
// 代理
|
||||
const proxyReq = protocol.request(proxyUrl, (proxyRes) => {
|
||||
res.writeHead(proxyRes.statusCode, {
|
||||
...proxyRes.headers,
|
||||
});
|
||||
if (proxyRes.statusCode === 404) {
|
||||
userApp.clearCacheData();
|
||||
return createNotFoundPage('Invalid proxy url');
|
||||
}
|
||||
if (proxyRes.statusCode === 302) {
|
||||
res.writeHead(302, { Location: proxyRes.headers.location });
|
||||
return res.end();
|
||||
}
|
||||
proxyRes.pipe(res, { end: true });
|
||||
});
|
||||
proxyReq.on('error', (err) => {
|
||||
console.error(`Proxy request error: ${err.message}`);
|
||||
userApp.clearCacheData();
|
||||
});
|
||||
proxyReq.end();
|
||||
// userApp.clearCacheData()
|
||||
return;
|
||||
}
|
||||
console.log('appFile', appFile, appFileUrl);
|
||||
if (!appFile) {
|
||||
const [indexFilePath, etag] = indexFile.split('||');
|
||||
const contentType = getContentType(indexFilePath);
|
||||
const isHTML = contentType.includes('html');
|
||||
const filePath = path.join(fileStore, indexFilePath);
|
||||
if (!userApp.fileCheck(filePath)) {
|
||||
// 动态删除文件
|
||||
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.write('File expired, Not Found\n');
|
||||
res.write('App Cache expired, Please refresh\n');
|
||||
res.end();
|
||||
await userApp.clearCacheData();
|
||||
return;
|
||||
@ -255,4 +296,3 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
||||
console.error('getFile error', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { config } from '../config.ts';
|
||||
|
||||
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
|
||||
const api = config?.api || { host: 'https://kevisual.xiongxiao.me', path: '/api/router' };
|
||||
const apiPath = api.path || '/api/router';
|
||||
export const fetchTest = async (id: string) => {
|
||||
const fetchUrl = 'http://' + api.host + apiPath;
|
||||
const fetchUrl = api.host + apiPath;
|
||||
const fetchRes = await fetch(fetchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -19,7 +19,7 @@ export const fetchTest = async (id: string) => {
|
||||
};
|
||||
|
||||
export const fetchDomain = async (domain: string) => {
|
||||
const fetchUrl = 'http://' + api.host + apiPath;
|
||||
const fetchUrl = api.host + apiPath;
|
||||
const fetchRes = await fetch(fetchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -37,7 +37,7 @@ export const fetchDomain = async (domain: string) => {
|
||||
};
|
||||
|
||||
export const fetchApp = async ({ user, app }) => {
|
||||
const fetchUrl = 'http://' + api.host + apiPath;
|
||||
const fetchUrl = api.host + apiPath;
|
||||
const fetchRes = await fetch(fetchUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user