response/index.js

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)
}
}
对Http的response进一步封装, 提供常用的API
JavaScript 100%