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