/** * @author yutent * @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 = `
Http Status: ${code}
${msg}
` } /** * [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 = `` 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) } }