File size: 6,181 Bytes
6d0ad0e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
import os from 'os'
import fs from 'fs'
import path from 'path'
import util from 'util'
import express from 'express'
import Server from './Server.js'
import VueCache from './VueFileCache.js'
import { createServer } from 'http'
import { WebSocketServer } from 'ws'
import { Config, puppeteer, logger } from '#karin'
class HttpServer {
constructor (port) {
this.port = port
this.app = express()
this.server = createServer(this.app)
this.ws = new WebSocketServer({ server: this.server })
this.file = new Map()
}
init () {
/** 解析JSON */
this.app.use(express.json({ limit: '50mb' }))
/** 解析URL编码 */
this.app.use(express.urlencoded({ extended: true }))
/** 设置静态文件目录 */
this.app.use(express.static(process.cwd()))
/** 设置Vue模板目录 */
const vueProjectsPath = 'vueTemplate'
fs.readdirSync(vueProjectsPath).forEach(projectName => {
this.app.use(`/vue/${projectName}`, express.static(path.join(vueProjectsPath, projectName, 'dist')))
})
/** 开启服务 */
this.server.listen(this.port, '0.0.0.0')
this.StaticApi()
this.wsApi()
this.GetApi()
this.PostApi()
}
wsApi () {
this.ws.on('connection', async (socket, request) => {
const url = request.url
/** 非标Api禁止连接 */
if (url !== '/ws/render') return socket.close()
// /** 检查token */
// const token = request.headers['x-token']
// if (token !== '123456') return socket.close()
logger.mark(`[正向WS][新的连接]:${url}`)
return new Server(socket, request).init()
})
this.ws.on('close', (socket, request) => {
logger.error(`[正向WS][服务器关闭]:${request.url}`)
})
}
GetApi () {
/** GET接口 - 渲染 */
this.app.get('/api/render/', async (req, res) => {
try {
const { hash } = req.query
/** 设置响应头 buffer */
res.setHeader('Content-Type', 'text/html; charset=utf-8')
const data = this.file.get(hash)
if (!data) return res.send(JSON.stringify({ code: 404, msg: 'Not Found' }))
res.send(data.file)
} catch (e) {
logger.error('[服务器][GET] 未知错误', e)
res.status(500).send(JSON.stringify({ code: 500, msg: 'Internal Server Error' }))
}
})
}
PostApi () {
const vueCache = VueCache
this.app.post('/api/render', async (req, res) => {
logger.info('[POST][渲染] ', `host:${req.headers.host} url:${req.url}`)
/** 从请求头获取token */
const token = req.headers.authorization
const data = req.body
if (data.vue) {
const cacheId = vueCache.addCache(data.file, data.name, data.props)
data.file = `http://localhost:${this.port}/vue/${data.vueTemplate || 'default'}/?id=${cacheId}`
const image = await puppeteer.screenshot(data)
vueCache.deleteCache(cacheId)
res.send(JSON.stringify(image))
} else {
if (token !== Config.Config.http.token) {
return res.send(JSON.stringify({ code: 403, msg: 'Token错误' }))
}
const image = await puppeteer.screenshot(data)
res.send(JSON.stringify(image))
}
})
this.app.post('/vue/getTemplate', async (req, res) => {
const data = req.body
if (data.id) {
logger.info('[POST][Vue] ', `获取Vue模板数据:${data.id}`)
const vue = vueCache.getCache(data.id)
if (vue) {
res.send(JSON.stringify({ status: 'success', ...vue }))
} else {
res.send(JSON.stringify({ status: 'failed', msg: 'Vue Data Error' }))
}
} else {
res.status(500).send(JSON.stringify({ code: 500, status: 'failed', msg: 'Vue cache is not found' }))
}
})
}
StaticApi () {
/** 对静态资源重构,使用ws进行传输 */
this.app.use(async (req, res, next) => {
if (req.query.hash) return next()
/** 获取唯一id */
const hash = req.headers['x-renderer-id'] || req.get('Referer')?.match(/hash=([^&]+)/)?.[1] || req.get('referer')?.match(/hash=([^&]+)/)?.[1]
if (!hash) return next()
/** 如果是/favicon.ico 则返回本地 */
if (req.url === '/favicon.ico') {
const file = fs.readFileSync('./lib/resources/favicon.ico')
res.setHeader('Content-Type', 'image/x-icon')
return res.send(file)
}
/** 获取对应的ws */
const file = this.file.get(hash)
if (!file || !file.ws) return next()
const { SendApi } = file.ws
/** 发送请求 */
let data = SendApi.call(file.ws, 'static', { file: req.url })
logger.debug(`[正向WS] 访问静态资源: ${req.url}`)
/** 获取url后缀 */
const ext = path.extname(req.url).toLowerCase()
let contentType = 'application/octet-stream'
try {
contentType = Config.mime[ext]
} catch {
logger.error('[服务器][GET][ContentType] 获取 mime 错误')
}
if (util.types.isPromise(data)) data = await data
res.setHeader('Content-Type', contentType)
logger.debug(`[正向WS][返回成功] 静态资源: ${req.url}`)
res.send(Buffer.from(data.file.data))
return true
})
}
getIPS () {
const interfaces = os.networkInterfaces()
const list = []
Object.keys(interfaces).forEach(interfaceName => {
interfaces[interfaceName].forEach(iface => {
if (iface.family === 'IPv4' && !iface.internal) {
list.push(iface.address)
}
})
})
return list
}
}
const port = Config.Config.http.port
const isHttp = Config.Config.server.http
let http
if (isHttp) {
http = new HttpServer(port)
http.init()
logger.mark(`[服务器][正向WS][初始化] ws://localhost:${port}/ws/render`)
/** 获取所有ipv4地址 */
const ips = http.getIPS()
ips.forEach(ip => {
logger.mark(`[服务器][HTTP][post] http://${ip}:${port}/api/render/`)
})
}
export default http
|