diff --git a/package.json b/package.json index 48a775f..ef26112 100644 --- a/package.json +++ b/package.json @@ -69,10 +69,11 @@ "@kevisual/logger": "^0.0.4", "@kevisual/oss": "0.0.13", "@kevisual/permission": "^0.0.3", - "@kevisual/router": "0.0.42", + "@kevisual/router": "0.0.46", "@kevisual/types": "^0.0.10", "@kevisual/use-config": "^1.0.21", "@types/archiver": "^7.0.0", + "@types/bun": "^1.3.5", "@types/crypto-js": "^4.2.2", "@types/formidable": "^3.4.6", "@types/jsonwebtoken": "^9.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb58378..f982b7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,7 +41,7 @@ importers: version: 1.1.1 drizzle-orm: specifier: ^0.45.1 - version: 0.45.1(pg@8.16.3) + version: 0.45.1(bun-types@1.3.5)(pg@8.16.3) eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -86,8 +86,8 @@ importers: specifier: ^0.0.3 version: 0.0.3 '@kevisual/router': - specifier: 0.0.42 - version: 0.0.42 + specifier: 0.0.46 + version: 0.0.46 '@kevisual/types': specifier: ^0.0.10 version: 0.0.10 @@ -97,6 +97,9 @@ importers: '@types/archiver': specifier: ^7.0.0 version: 7.0.0 + '@types/bun': + specifier: ^1.3.5 + version: 1.3.5 '@types/crypto-js': specifier: ^4.2.2 version: 4.2.2 @@ -252,8 +255,8 @@ packages: '@kevisual/router@0.0.33': resolution: {integrity: sha512-9z7TkSzCIGbXn9SuHPBdZpGwHlAuwA8iN5jNAZBUvbEvBRkBxlrbdCSe9fBYiAHueLm2AceFNrW74uulOiAkqA==} - '@kevisual/router@0.0.42': - resolution: {integrity: sha512-6j254Hl1Q9uM4qKD4v6pcNSXVs7zwHZlyfSxUrNTWrgD7OCt/mrgBpzcNo0TM25/CsdrZCDs21kamienfYQ+lw==} + '@kevisual/router@0.0.46': + resolution: {integrity: sha512-KMsyVTQxCZdt35yRdeDJDIwXco6w7xSG3C90NlKMkrsj5OCZlIEJaRSs2ASIb3kYgrWFDl8NTUNbObDO03Q7bA==} '@kevisual/types@0.0.10': resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} @@ -386,6 +389,9 @@ packages: '@types/archiver@7.0.0': resolution: {integrity: sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==} + '@types/bun@1.3.5': + resolution: {integrity: sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w==} + '@types/busboy@1.5.4': resolution: {integrity: sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==} @@ -583,6 +589,9 @@ packages: bullmq@5.66.2: resolution: {integrity: sha512-0PrkpIakIntkBcPLltPIRWdLC1FTLUa/VhJkmEfobb5YUQjoUwJdmmf7HX+o/vMonS5048JpP+abf9lVRUFEjA==} + bun-types@1.3.5: + resolution: {integrity: sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2145,7 +2154,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@kevisual/router@0.0.42': + '@kevisual/router@0.0.46': dependencies: path-to-regexp: 8.3.0 selfsigned: 5.2.0 @@ -2353,6 +2362,10 @@ snapshots: dependencies: '@types/readdir-glob': 1.1.5 + '@types/bun@1.3.5': + dependencies: + bun-types: 1.3.5 + '@types/busboy@1.5.4': dependencies: '@types/node': 25.0.3 @@ -2562,6 +2575,10 @@ snapshots: transitivePeerDependencies: - supports-color + bun-types@1.3.5: + dependencies: + '@types/node': 25.0.3 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -2713,8 +2730,9 @@ snapshots: dottie@2.0.6: {} - drizzle-orm@0.45.1(pg@8.16.3): + drizzle-orm@0.45.1(bun-types@1.3.5)(pg@8.16.3): optionalDependencies: + bun-types: 1.3.5 pg: 8.16.3 eastasianwidth@0.2.0: {} diff --git a/src/app.ts b/src/app.ts index 762569a..36bf0db 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -import { App } from '@kevisual/router'; +import { App } from '@kevisual/router/src/app.ts'; import * as redisLib from './modules/redis.ts'; import * as minioLib from './modules/minio.ts'; import * as sequelizeLib from './modules/sequelize.ts'; @@ -29,17 +29,13 @@ export const minioClient = useContextKey('minioClient', () => minioLib.minioClie export const sequelize = useContextKey('sequelize', () => sequelizeLib.sequelize); const init = () => { - return new App<{ sequelize: typeof sequelize }>({ + return new App({ serverOptions: { cors: { origin: '*', }, - // httpType: 'https', - }, - io: true, - routerContext: { - sequelize, - }, + io: true, + } }); }; export const app = useContextKey('app', init); diff --git a/src/index.ts b/src/index.ts index 21a53d3..13c1a96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,62 +1,18 @@ -import { myConfig as config } from './modules/config.ts'; import { app } from './app.ts'; import './route.ts'; import { handleRequest } from './routes-simple/handle-request.ts'; import { port } from './modules/config.ts'; -import { WssApp } from './modules/ws-proxy/index.ts'; -import net from 'node:net'; -// if (import.meta.url === `file://${process.argv[1]}`) { -app.listen(port, () => { +import { wssFun } from './modules/ws-proxy/index.ts'; +console.log('Starting server...', port); +app.listen(port, '0.0.0.0', () => { console.log(`server is running at http://localhost:${port}`); }); -app.server.on(handleRequest); - -const wssApp = new WssApp(); -const main = () => { - console.log('Upgrade initialization started'); - - app.server.server.on('upgrade', async (req, socket, head) => { - const isUpgrade = wssApp.upgrade(req, socket, head); - if (isUpgrade) { - console.log('WebSocket upgrade successful for path:', req.url); - return; - } - 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); \ No newline at end of file +app.server.on([{ + id: 'handle-all', + fun: handleRequest, +}, { + id: 'wss', + io: true, + path: '/ws/proxy', + fun: wssFun, +}]); diff --git a/src/modules/auth.ts b/src/modules/auth.ts index b55753a..6d013db 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -38,6 +38,21 @@ export const checkAuth = async (req: http.IncomingMessage, res: http.ServerRespo return { tokenUser, token }; }; +export const getLoginUserByToken = async (token: string) => { + if (token) { + token = token.replace('Bearer ', ''); + } + if (!token) { + return null; + } + let tokenUser; + try { + tokenUser = await User.verifyToken(token); + return { tokenUser, token }; + } catch (e) { + return null; + } +} export const getLoginUser = async (req: http.IncomingMessage) => { let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || ''; const url = new URL(req.url || '', 'http://localhost'); diff --git a/src/modules/config.ts b/src/modules/config.ts index 8d31e39..61bdcc4 100644 --- a/src/modules/config.ts +++ b/src/modules/config.ts @@ -3,7 +3,7 @@ import { useFileStore } from '@kevisual/use-config/file-store'; import { minioResources } from './minio.ts'; export const config = useConfig() as any; -export const port = config.PORT || 4005; +export const port = config.PORT ? Number(config.PORT) : 4005; export const fileStore = useFileStore('pages'); type ConfigType = { api: { diff --git a/src/modules/fm-manager/index.ts b/src/modules/fm-manager/index.ts index b5a1170..539e103 100644 --- a/src/modules/fm-manager/index.ts +++ b/src/modules/fm-manager/index.ts @@ -7,4 +7,6 @@ export * from './get-router.ts' export * from './get-content-type.ts' -export * from './utils.ts' \ No newline at end of file +export * from './utils.ts' + +export { pipeFileStream, pipeStream } from './pipe.ts' \ No newline at end of file diff --git a/src/modules/fm-manager/pipe.ts b/src/modules/fm-manager/pipe.ts new file mode 100644 index 0000000..2afda41 --- /dev/null +++ b/src/modules/fm-manager/pipe.ts @@ -0,0 +1,19 @@ +import * as http from 'http'; +import * as fs from 'fs'; +import { isBun } from '../../utils/get-engine.ts'; + +export const pipeFileStream = (filePath: string, res: http.ServerResponse) => { + const readStream = fs.createReadStream(filePath); + if (isBun) { + res.pipe(readStream as any); + } else { + readStream.pipe(res, { end: true }); + } +} +export const pipeStream = (readStream: fs.ReadStream, res: http.ServerResponse) => { + if (isBun) { + res.pipe(readStream as any); + } else { + readStream.pipe(res, { end: true }); + } +} \ No newline at end of file diff --git a/src/modules/fm-manager/proxy/http-proxy.ts b/src/modules/fm-manager/proxy/http-proxy.ts index b1445f0..c914a8e 100644 --- a/src/modules/fm-manager/proxy/http-proxy.ts +++ b/src/modules/fm-manager/proxy/http-proxy.ts @@ -10,7 +10,7 @@ import { addStat } from '@/modules/html/stat/index.ts'; import path from 'path'; import { getTextContentType } from '@/modules/fm-manager/index.ts'; import { logger } from '@/modules/logger.ts'; - +import { pipeStream } from '../pipe.ts'; const pipelineAsync = promisify(pipeline); export async function downloadFileFromMinio(fileUrl: string, destFile: string) { @@ -74,7 +74,7 @@ export async function minioProxy( res.writeHead(200, { ...headers, }); - objectStream.pipe(res, { end: true }); + pipeStream(objectStream as any, res); } return true; } catch (error) { @@ -154,7 +154,7 @@ export const httpProxy = async ( res.writeHead(proxyRes.statusCode, { ...headers, }); - proxyRes.pipe(res, { end: true }); + pipeStream(proxyRes as any, res); } }); proxyReq.on('error', (err) => { diff --git a/src/modules/fm-manager/proxy/minio-proxy.ts b/src/modules/fm-manager/proxy/minio-proxy.ts index da7deca..bcca6a7 100644 --- a/src/modules/fm-manager/proxy/minio-proxy.ts +++ b/src/modules/fm-manager/proxy/minio-proxy.ts @@ -1,4 +1,4 @@ -import http from 'http'; +import http from 'node:http'; import { minioClient } from '@/modules/minio.ts'; type ProxyInfo = { path?: string; diff --git a/src/modules/fm-manager/utils.ts b/src/modules/fm-manager/utils.ts index bc564ad..f40b524 100644 --- a/src/modules/fm-manager/utils.ts +++ b/src/modules/fm-manager/utils.ts @@ -1,5 +1,6 @@ import { IncomingMessage } from 'node:http'; import http from 'node:http'; +import { logger } from '../logger.ts'; export const getUserFromRequest = (req: IncomingMessage) => { const url = new URL(req.url, `http://${req.headers.host}`); @@ -14,8 +15,8 @@ export const getUserFromRequest = (req: IncomingMessage) => { export const getDNS = (req: http.IncomingMessage) => { - const hostName = req.headers.host; - const ip = req.socket.remoteAddress; + const hostName = req.headers?.host; + const ip = req?.socket?.remoteAddress || ''; return { hostName, ip }; }; diff --git a/src/modules/ws-proxy/index.ts b/src/modules/ws-proxy/index.ts index e3dcac3..76cbc84 100644 --- a/src/modules/ws-proxy/index.ts +++ b/src/modules/ws-proxy/index.ts @@ -1,136 +1,29 @@ -import { WebSocketServer } from 'ws'; -import { nanoid } from 'nanoid'; import { WsProxyManager } from './manager.ts'; -import { getLoginUser } from '@/modules/auth.ts'; +import { getLoginUserByToken } from '@/modules/auth.ts'; import { logger } from '../logger.ts'; export const wsProxyManager = new WsProxyManager(); - -export const upgrade = (request: any, socket: any, head: any) => { - const req = request as any; - const url = new URL(req.url, 'http://localhost'); - const id = url.searchParams.get('id'); - if (url.pathname === '/ws/proxy') { - console.log('upgrade', request.url, id); - wss.handleUpgrade(req, socket, head, (ws) => { - // 这里手动触发 connection 事件 - console.log('emitting connection event'); - // @ts-ignore - wss.emit('connection', ws, req); - }); - return true; - } - return false; -}; -export const wss = new WebSocketServer({ - noServer: true, - path: '/ws/proxy', -}); - -wss.on('connection', async (ws, req) => { - console.log('connected', req.url); - const url = new URL(req.url, 'http://localhost'); - const _id = url?.searchParams?.get('id'); - const id = _id || nanoid(); - const loginUser = await getLoginUser(req); - if (!loginUser) { - console.log('未登录,断开连接'); - ws.send(JSON.stringify({ code: 401, message: '未登录' })); - setTimeout(() => { - ws.close(); - }, 1000); - return; - } - const user = loginUser.tokenUser.username; - const userApp = user + '-' + id; - console.log('注册 ws 连接', userApp); - wsProxyManager.register(userApp, { user, ws }); - ws.send( - JSON.stringify({ - type: 'connected', - user: user, - id, - }), - ); - ws.on('message', async (event: Buffer) => { - const eventData = event.toString(); - if (!eventData) { - return; - } - const data = JSON.parse(eventData); - logger.debug('message', data); - }); - ws.on('close', () => { - logger.debug('ws closed'); - wsProxyManager.unregister(userApp); - }); -}); - -export class WssApp { - wss: WebSocketServer; - bunWSS = websocket; - constructor() { - this.wss = wss; - } - upgrade(request: any, socket: any, head: any) { - // return upgrade(request, socket, head); - return bunUpgrade(request); - } -} - -export const bunUpgrade = (request: Request) => { - const url = new URL(request.url, 'http://localhost'); - const isUpgrade = url.pathname === '/ws/proxy'; - if (isUpgrade) { - console.log('upgrade', request.url); - - // 使用 Bun 原生 WebSocket - new Response(null, { - status: 101, - headers: { - 'Upgrade': 'websocket', - }, - }); - return true; - } - - return false; -}; -// Bun WebSocket 处理器 -export const websocket = { - async open(ws: any) { - console.log('WebSocket opened'); - const { url, token } = ws.data; - - const urlObj = new URL(url, 'http://localhost'); - const _id = urlObj.searchParams.get('id'); - const id = _id || nanoid(); - - // 创建一个模拟的 request 对象用于认证 - const mockReq: any = { - url: url, - headers: { - authorization: token ? `Bearer ${token}` : undefined, - }, - }; - - const loginUser = await getLoginUser(mockReq); - - if (!loginUser) { - console.log('未登录,断开连接'); +import { WebScoketListenerFun } from '@kevisual/router/src/server/server-type.ts' +export const wssFun: WebScoketListenerFun = async (req, res) => { + // do nothing, just to enable ws upgrade event + const { id, ws, token, data, emitter } = req; + logger.debug('ws proxy connected, id=', id, ' token=', token, ' data=', data); + // console.log('req', req) + const { type } = data || {}; + if (type === 'registryClient') { + const loginUser = await getLoginUserByToken(token); + if (!loginUser?.tokenUser) { + logger.debug('未登录,断开连接'); ws.send(JSON.stringify({ code: 401, message: '未登录' })); - ws.close(); + setTimeout(() => { + ws.close(401, 'Unauthorized'); + }, 1000); return; } - - const user = loginUser.tokenUser.username; + const user = loginUser?.tokenUser?.username; const userApp = user + '-' + id; - console.log('注册 ws 连接', userApp); - - ws.data.userApp = userApp; - ws.data.user = user; - + logger.debug('注册 ws 连接', userApp); + // @ts-ignore wsProxyManager.register(userApp, { user, ws }); - ws.send( JSON.stringify({ type: 'connected', @@ -138,26 +31,22 @@ export const websocket = { id, }), ); - }, - - async message(ws: any, message: string) { - try { - const data = JSON.parse(message); - logger.debug('message', data); - } catch (error) { - logger.error('Failed to parse message', error); - } - }, - - close(ws: any) { - const { userApp } = ws.data; - logger.debug('ws closed', userApp); - if (userApp) { + emitter.once('close--' + id, () => { + logger.debug('ws emitter closed'); wsProxyManager.unregister(userApp); - } - }, - - error(ws: any, error: Error) { - console.error('WebSocket error:', error); - }, -}; \ No newline at end of file + }); + // @ts-ignore + ws.data.userApp = userApp; + return; + } + // @ts-ignore + const userApp = ws.data.userApp; + logger.debug('message', data, ' userApp=', userApp); + const wsMessage = wsProxyManager.get(userApp); + if (wsMessage) { + wsMessage.sendResponse(data); + } else { + // @ts-ignore + logger.debug('账号应用未注册,无法处理消息。未授权?', ws.data); + } +} \ No newline at end of file diff --git a/src/modules/ws-proxy/manager.ts b/src/modules/ws-proxy/manager.ts index 4ea9324..566c93e 100644 --- a/src/modules/ws-proxy/manager.ts +++ b/src/modules/ws-proxy/manager.ts @@ -10,18 +10,11 @@ class WsMessage { this.ws = ws; this.user = user; this.emitter = new EventEmitter(); - this.listenMessage(); } - async listenMessage() { - this.ws.on('message', (event: Buffer) => { - const eventData = event.toString(); - if (!eventData) { - return; - } - const data = JSON.parse(eventData); - logger.debug('ws-proxy listenMessage', data); - this.emitter.emit(data.id, data.data); - }); + async sendResponse(data: any) { + if (data.id) { + this.emitter.emit(data.id, data?.data); + } } async sendData(data: any, opts?: { timeout?: number }) { if (this.ws.readyState !== WebSocket.OPEN) { @@ -65,6 +58,7 @@ export class WsProxyManager { value.ws.close(); } } + console.log('WsProxyManager register', id); const value = new WsMessage({ ws: opts?.ws, user: opts?.user }); this.wssMap.set(id, value); } diff --git a/src/modules/ws-proxy/proxy.ts b/src/modules/ws-proxy/proxy.ts index ebe2a3c..4ac7e5d 100644 --- a/src/modules/ws-proxy/proxy.ts +++ b/src/modules/ws-proxy/proxy.ts @@ -30,7 +30,7 @@ export const UserV1Proxy = async (req: IncomingMessage, res: ServerResponse, opt const client = wsProxyManager.get(userAppKey); const ids = wsProxyManager.getIds(); if (!client) { - opts?.createNotFoundPage?.(`未找到应用, ${userAppKey}, ${ids.join(',')}`); + opts?.createNotFoundPage?.(`未找到应用 [${userAppKey}], 当前应用列表: ${ids.join(',')}`); return false; } const value = await client.sendData(data); diff --git a/src/routes-simple/page-proxy.ts b/src/routes-simple/page-proxy.ts index 32d6291..42d2de4 100644 --- a/src/routes-simple/page-proxy.ts +++ b/src/routes-simple/page-proxy.ts @@ -1,4 +1,4 @@ -import { getDNS, isIpv4OrIpv6, isLocalhost } from '../modules/fm-manager/index.ts'; +import { getDNS, isIpv4OrIpv6, isLocalhost, pipeFileStream } from '../modules/fm-manager/index.ts'; import http from 'node:http'; import https from 'node:https'; import { UserApp } from '../modules/user-app/index.ts'; @@ -15,6 +15,7 @@ import { logger } from '../modules/logger.ts'; import { UserV1Proxy } from '../modules/ws-proxy/proxy.ts'; import { hasBadUser, userIsBanned, appIsBanned, userPathIsBanned } from '@/modules/off/index.ts'; import { robotsTxt } from '@/modules/html/index.ts'; +import { isBun } from '@/utils/get-engine.ts'; const domain = config?.proxy?.domain; const allowedOrigins = config?.proxy?.allowedOrigin || []; @@ -50,7 +51,7 @@ const checkNotAuthPath = (user, app) => { const forBadUser = (req: http.IncomingMessage, res: http.ServerResponse) => { // TODO: 记录日志,封禁IP等操作 const dns = getDNS(req); - logger.warn(`Bad user access from IP: ${dns.ip}, Host: ${dns.hostName}, URL: ${req.url}`); + logger.warn(`forBadUser: Bad user access from IP: ${dns.ip}, Host: ${dns.hostName}, URL: ${req.url}`); // 这里可以添加更多的处理逻辑,比如封禁IP等 } export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => { @@ -132,7 +133,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR const _orings = allowedOrigins || []; const host = dns.hostName; if ( - _orings.some((item) => { + host && _orings.some((item) => { return host.includes(item); }) ) { @@ -142,11 +143,9 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR let user, app; let domainApp = false; - if (isLocalhost(dns.hostName)) { - // 本地开发环境 测试 - // user = 'root'; - // app = 'codeflow'; - // domainApp = true; + const isDev = isLocalhost(dns.hostName); + if (isDev) { + console.debug('开发环境访问:', req.url, 'Host:', dns.hostName); } else { if (isIpv4OrIpv6(dns.hostName)) { // 打印出 req.url 和错误信息 @@ -184,14 +183,9 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR const url = pathname; if (!domainApp && noProxyUrl.includes(url)) { if (url === '/') { - // TODO: 获取一下登陆用户,如果没有登陆用户,重定向到ai-chat页面 - // 重定向到 - // res.writeHead(302, { Location: home }); - // return res.end(); rediretHome(req, res); return; } - // 不是域名代理,且是在不代理的url当中 res.write('No proxy for this URL\n'); return res.end(); } @@ -205,7 +199,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR res.end(robotsTxt); return; } - if(userPathIsBanned(_user)) { + if (userPathIsBanned(_user)) { logger.warn(`Bad user access from IP: ${dns.ip}, Host: ${dns.hostName}, URL: ${req.url}`); } else { console.log('urls error', urls, 'originUrl:', url); @@ -259,7 +253,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR let isExist = await userApp.getExist(); logger.debug('userApp', userApp, isExist); if (userIsBanned(user) || appIsBanned(app)) { - forBadUser(req, res); + if (!isDev) forBadUser(req, res); return createErrorPage(); } if (!isExist) { @@ -314,7 +308,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR if (!proxyUrl.startsWith('http')) { return createNotFoundPage('Invalid proxy url'); } - console.log('proxyUrl', appFileUrl, proxyUrl); + console.log('proxyUrl indexFile', appFileUrl, proxyUrl); httpProxy(req, res, { proxyUrl, userApp, @@ -332,8 +326,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR const filePath = path.join(fileStore, indexFilePath); if (!userApp.fileCheck(filePath)) { res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8', tips: 'App Cache expired, Please refresh' }); - res.write(createRefreshHtml(user, app)); - res.end(); + res.end(createRefreshHtml(user, app)); await userApp.clearCacheData(); return; } @@ -384,17 +377,14 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR headers.set('Content-Type', contentType); headers.set('Cache-Control', isHTML ? 'no-cache' : 'public, max-age=3600'); // 设置缓存时间为 1 小时 headers.set('ETag', eTag); - res?.setHeaders?.(headers); + res.writeHead(200, Object.fromEntries(headers)); if (isHTML) { const newHtml = await getTextFromStreamAndAddStat(fs.createReadStream(filePath)); resContent = newHtml.html; headers.set('Content-Length', newHtml.contentLength.toString()); - res.writeHead(200); res.end(resContent); } else { - res.writeHead(200); - const readStream = fs.createReadStream(filePath); - readStream.pipe(res); + pipeFileStream(filePath, res); } return; } @@ -402,3 +392,4 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR console.error('getFile error', error); } }; + diff --git a/src/utils/get-engine.ts b/src/utils/get-engine.ts new file mode 100644 index 0000000..df6c945 --- /dev/null +++ b/src/utils/get-engine.ts @@ -0,0 +1,6 @@ +export const isBun = typeof Bun !== 'undefined' && Bun?.version != null; + +export const isNode = typeof process !== 'undefined' && process?.versions != null && process.versions?.node != null; + +// @ts-ignore +export const isDeno = typeof Deno !== 'undefined' && Deno?.version != null && Deno?.version?.deno != null; \ No newline at end of file