File size: 5,343 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 |
import crypto from 'crypto'
import http from './express.js'
import { Config, puppeteer, logger } from '#karin'
import VueCache from './VueFileCache.js'
const port = Config.Config.http.port
export default class Server {
constructor (socket, request) {
this.id = 0
this.socket = socket
this.request = request
this.host = this.getHost()
this.vueCache = VueCache
}
init () {
this.socket.on('message', data => this.message(data))
this.socket.on('close', () => {
logger.error(`[Server][连接关闭]:${this.request.url}`)
})
}
getHost () {
const isSecure = this.request.connection.encrypted
const protocol = isSecure ? 'wss' : 'ws'
const host = this.request.headers.host
const path = this.request.url
return `${protocol}://${host}${path}`
}
/**
* 事件处理
* @param {puppeteer} data - 请求参数
* @param {string} data.echo - 唯一标识符
* @param {'heartbeat'|'render'|'static'} data.action - 请求类型
* @param {object} data.data - 请求参数
* @param {string} data.data.file - 请求文件
* @param {'png'|'jpeg'|'webp'} [data.data.type] - 图片类型
* @param {number} [data.data.quality] - 图片质量
* @param {boolean} [data.data.omitBackground] - 是否忽略背景
* @param {object} [data.data.setViewport] - 设置视口
* @param {number} data.data.setViewport.width - 视口宽度
* @param {number} data.data.setViewport.height - 视口高度
* @param {number} [data.data.setViewport.deviceScaleFactor] - 设备比例
* @param {boolean|number} data.data.multiPage - 是否多页 如果传递数字则视为视窗高度
* @param {'load'|'domcontentloaded'|'networkidle0'|'networkidle2'} [data.data.waitUntil] - 等待时间
* @param {any} data.data.pageGotoParams - 新建页面时传递给page.goto的参数
*/
async message (data) {
data = JSON.parse(data)
switch (data.action) {
case 'heartbeat':
return logger.debug(`[Server][心跳]:${this.request.url}`)
case 'render': {
/** 这里接收到的是file://或者http地址 */
data.data.file = decodeURIComponent(data.data.file)
logger.debug(`[正向WS][渲染] URL: ${this.host} html:${data.data.file}`)
let res
if (data.data.vue && Config.Config.server.http) {
const cacheId = this.vueCache.addCache(data.data.file, data.data.name, data.data.props)
data.data.file = `http://localhost:${Config.Config.http.port}/vue/${data.data.vueTemplate || 'default'}/?id=${cacheId}`
res = await puppeteer.screenshot(data.data)
this.vueCache.deleteCache(cacheId)
} else {
res = await puppeteer.screenshot(data.data)
}
res.echo = data.echo
res.action = 'renderRes'
this.socket.send(JSON.stringify(res))
break
}
case 'renderHtml': {
/** 这里接收到的是完整html buffer */
const file = decodeURIComponent(data.data.file)
logger.debug(`[正向WS][渲染] URL: ${this.host} html:${file}`)
/** 构建唯一id */
const hash = `render-${this.id}-${crypto.randomUUID()}`
const host = `http://localhost:${port}/api/render?hash=${hash}`
http.file.set(hash, { ws: this, file })
/** 替换为本地http */
data.data.file = host
/** http响应头 */
data.data.hash = hash
const res = await puppeteer.screenshot(data.data)
res.echo = data.echo
res.action = 'renderRes'
this.socket.send(JSON.stringify(res))
break
}
case 'static': {
/** 这里接收的全是静态资源 由pupp主动请求客户端 客户端上报完成 */
this.socket.emit(data.echo, data)
break
}
default:
break
}
}
/**
* 发送数据
* @param {{
* type: string,
* action: 'heartbeat'|'static',
* data: {
* file: string
* }
* }} params 请求参数
* @param {number} time 超时时间
* @returns {Promise<void>}
*/
async SendApi (action, params, time = 120) {
const echo = crypto.randomUUID()
const request = JSON.stringify({ echo, action, params })
return new Promise((resolve, reject) => {
this.socket.send(request)
this.socket.once(echo, (data) => {
if (data.status === 'ok') {
resolve(data.data)
} else {
logger.error(`[Api请求错误] ${JSON.stringify(data, null, 2)}`)
reject(data)
}
})
/** 设置一个超时计时器 */
setTimeout(() => {
reject(new Error('API请求超时'))
}, time * 1000)
})
}
}
/**
* @typedef {{
* echo: string,
* action: 'heartbeat'|'render'|'static',
* data: data
* }} puppeteer
*/
/**
* @typedef {{
* file: string,
* name: string,
* type?: 'png'|'jpeg'|'webp',
* quality?: number,
* omitBackground?: boolean,
* setViewport?: {
* width: number,
* height: number,
* deviceScaleFactor?: number,
* },
* multiPage: boolean|number,
* waitUntil?: 'load'|'domcontentloaded'|'networkidle0'|'networkidle2',
* pageGotoParams: any
* }} data
*/
|