284 lines
6.0 KiB
JavaScript
284 lines
6.0 KiB
JavaScript
/**
|
|
* @author yutent<yutent.io@gmail.com>
|
|
* @date 2020/09/16 14:52:58
|
|
*/
|
|
|
|
import fs from 'iofs'
|
|
import STATUS_TEXT from './lib/http-code.js'
|
|
import { MIME_TYPES, CHARSET_TYPES } from './lib/mime-tpyes.js'
|
|
import { serialize } from './lib/cookie.js'
|
|
|
|
export default class Response {
|
|
#req = null
|
|
#res = null
|
|
|
|
#charset = 'utf-8'
|
|
status = 200
|
|
|
|
#ended = false
|
|
|
|
constructor(req, res) {
|
|
this.#req = req
|
|
this.#res = res
|
|
|
|
this.#ended = !!res.ended
|
|
}
|
|
|
|
get ended() {
|
|
return this.#ended
|
|
}
|
|
|
|
set charset(v) {
|
|
this.#charset = v || 'utf-8'
|
|
}
|
|
|
|
get type() {
|
|
return this.#res.getHeader('content-type')
|
|
}
|
|
|
|
set type(v) {
|
|
let mime = MIME_TYPES[v] || MIME_TYPES.stream
|
|
mime += CHARSET_TYPES[v] ? '; charset=' + this.#charset : ''
|
|
this.set('Content-Type', mime)
|
|
}
|
|
|
|
set body(buf = null) {
|
|
if (this.#ended) {
|
|
return this
|
|
}
|
|
|
|
this.#ended = true
|
|
this.#res.writeHead(this.status, STATUS_TEXT[this.status])
|
|
this.#res.end(buf)
|
|
}
|
|
|
|
/**
|
|
* [error http 错误显示]
|
|
* @param {Number} code [http错误码]
|
|
* @param {String} msg [错误提示信息]
|
|
*/
|
|
error(msg, code = 500) {
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
msg = msg || STATUS_TEXT[code]
|
|
|
|
this.status = code
|
|
this.type = 'html'
|
|
this.body = `<fieldset><legend>Http Status: ${code}</legend><pre>${msg}</pre></fieldset>`
|
|
}
|
|
|
|
/**
|
|
* [redirect 页面跳转]
|
|
* @param {String} url [要跳转的URL]
|
|
* @param {Boolean} f [是否永久重定向]
|
|
*/
|
|
redirect(url, f = false) {
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
if (!/^(http[s]?|ftp):\/\//.test(url)) {
|
|
url = '//' + url
|
|
}
|
|
this.set('Location', url)
|
|
this.status = f ? 301 : 302
|
|
this.body = null
|
|
}
|
|
|
|
/**
|
|
* [location 页面跳转(前端的方式)]
|
|
*/
|
|
location(url) {
|
|
let html = `<html><head><meta http-equiv="refresh" content="0;url=${url}"></head></html>`
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
this.render(html)
|
|
}
|
|
|
|
// 以html格式向前端输出内容
|
|
render(data, code) {
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
data += ''
|
|
data = data || STATUS_TEXT[code]
|
|
this.type = 'html'
|
|
this.set('Content-Length', Buffer.byteLength(data))
|
|
if (code) {
|
|
this.status = code
|
|
}
|
|
this.body = data
|
|
}
|
|
|
|
// 文件下载
|
|
sendfile(target, filename = 'untitled') {
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
let data, start, end
|
|
|
|
if (!this.type) {
|
|
this.status = 206
|
|
this.type = 'stream'
|
|
this.set('Content-Disposition', `attachment;filename="${filename}"`)
|
|
this.set('Accept-Ranges', 'bytes')
|
|
|
|
let range = this.#req.headers['range'] || ''
|
|
if (range) {
|
|
range = range.replace('bytes=', '')
|
|
if (range.includes(',')) {
|
|
// 多重范围的range请求, 暂时不支持, 直接返回整个文件
|
|
} else {
|
|
range = range.split('-').map(n => +n)
|
|
;[start, end] = range
|
|
|
|
if (end === 0) {
|
|
end = void 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Buffer.isBuffer(target)) {
|
|
data = target
|
|
} else {
|
|
if (typeof target === 'string') {
|
|
let stat = fs.stat(target)
|
|
if (stat.isFile()) {
|
|
let size = stat.size
|
|
if (start !== void 0) {
|
|
if (end === void 0) {
|
|
size -= start
|
|
} else {
|
|
size = end - start
|
|
}
|
|
}
|
|
this.set('Content-Length', size)
|
|
return fs.origin
|
|
.createReadStream(target, { start, end })
|
|
.pipe(this.#res)
|
|
}
|
|
}
|
|
data = Buffer.from(target + '')
|
|
}
|
|
|
|
if (start !== void 0) {
|
|
data = data.slice(start, end)
|
|
}
|
|
|
|
this.set('Content-Length', data.length)
|
|
this.body = data
|
|
}
|
|
|
|
/**
|
|
* [send json格式输出]
|
|
* @param {Num} code [返回码]
|
|
* @param {Str} msg [提示信息]
|
|
* @param {Str/Obj} data [额外数据]
|
|
* @param {Str} callback [回调函数名]
|
|
*/
|
|
send(code = 200, msg = '', data, callback) {
|
|
let output
|
|
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
|
|
if (msg && typeof msg === 'object') {
|
|
callback = data
|
|
data = msg
|
|
msg = STATUS_TEXT[code]
|
|
} else {
|
|
msg = msg || STATUS_TEXT[code]
|
|
}
|
|
|
|
output = { code, msg, data }
|
|
output = JSON.stringify(output)
|
|
|
|
if (callback) {
|
|
callback = callback.replace(/[^\w\.]/g, '')
|
|
output = callback + '(' + output + ')'
|
|
}
|
|
|
|
this.type = 'json'
|
|
this.set('Content-Length', Buffer.byteLength(output))
|
|
|
|
// 只设置200以上的值
|
|
if (code && code >= 200 && code <= 599) {
|
|
this.status = code
|
|
}
|
|
|
|
this.body = output
|
|
}
|
|
|
|
/**
|
|
* [get 读取已写入的头信息]
|
|
*/
|
|
get(key) {
|
|
return this.#res.getHeader(key)
|
|
}
|
|
|
|
/**
|
|
* [set 设置头信息]
|
|
*/
|
|
set(key, val) {
|
|
if (this.#ended) {
|
|
return this
|
|
}
|
|
let value = Array.isArray(val) ? val.map(String) : String(val)
|
|
this.#res.setHeader(key, value)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* [append 往header插入信息]
|
|
* @param {String} key [description]
|
|
* @param {String} val [description]
|
|
*/
|
|
append(key, val) {
|
|
if (this.#ended) {
|
|
return
|
|
}
|
|
let prev = this.get(key) || []
|
|
|
|
if (!Array.isArray(prev)) {
|
|
prev = [prev]
|
|
}
|
|
|
|
prev = prev.concat(val)
|
|
|
|
return this.set(key, prev)
|
|
}
|
|
|
|
/**
|
|
* [set 设置cookie]
|
|
* @param {[string]} key
|
|
* @param {[string/number]} val
|
|
* @param {[object]} opts [设置cookie的额外信息,如域,有效期等]
|
|
*/
|
|
cookie(key, val, opts = {}) {
|
|
//读取之前已经写过的cookie缓存
|
|
let cache = this.get('set-cookie')
|
|
if (cache) {
|
|
if (!Array.isArray(cache)) {
|
|
cache = [cache]
|
|
}
|
|
} else {
|
|
cache = []
|
|
}
|
|
|
|
if (cache.length > 0) {
|
|
// 如果之前已经写了一个相同的cookie, 则删除之前的
|
|
cache = cache.filter(it => {
|
|
let _key = it.split('=')[0].trim()
|
|
return key !== _key
|
|
})
|
|
}
|
|
|
|
cache.push(serialize(key, val, opts))
|
|
|
|
this.set('set-cookie', cache)
|
|
}
|
|
}
|