2.0重构;补全完整的http状态码
parent
e32ca8c726
commit
a2a6ea850a
221
index.js
221
index.js
|
@ -5,18 +5,51 @@
|
||||||
|
|
||||||
import fs from 'iofs'
|
import fs from 'iofs'
|
||||||
import STATUS_TEXT from './lib/http-code.js'
|
import STATUS_TEXT from './lib/http-code.js'
|
||||||
|
import { MIME_TYPES, CHARSET_TYPES } from './lib/mime-tpyes.js'
|
||||||
import { serialize } from './lib/cookie.js'
|
import { serialize } from './lib/cookie.js'
|
||||||
|
|
||||||
export default class Response {
|
export default class Response {
|
||||||
|
#req = null
|
||||||
rendered = false
|
#res = null
|
||||||
|
|
||||||
|
#charset = 'utf-8'
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
#ended = false
|
||||||
|
|
||||||
constructor(req, res) {
|
constructor(req, res) {
|
||||||
|
|
||||||
this.#req = req
|
this.#req = req
|
||||||
this.#res = res
|
this.#res = res
|
||||||
|
|
||||||
this.rendered = !!res.rendered
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,20 +58,14 @@ export default class Response {
|
||||||
* @param {String} msg [错误提示信息]
|
* @param {String} msg [错误提示信息]
|
||||||
*/
|
*/
|
||||||
error(msg, code = 500) {
|
error(msg, code = 500) {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg = msg || STATUS_TEXT[code]
|
msg = msg || STATUS_TEXT[code]
|
||||||
|
|
||||||
this.status(code)
|
this.status = code
|
||||||
this.set('Content-Type', 'text/html; charset=utf-8')
|
this.type = 'html'
|
||||||
this.end(
|
this.body = `<fieldset><legend>Http Status: ${code}</legend><pre>${msg}</pre></fieldset>`
|
||||||
`<fieldset><legend>Http Status: ${code}</legend><pre>${msg}</pre></fieldset>`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
status(code = 404) {
|
|
||||||
this.statusCode = code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,23 +74,23 @@ export default class Response {
|
||||||
* @param {Boolean} f [是否永久重定向]
|
* @param {Boolean} f [是否永久重定向]
|
||||||
*/
|
*/
|
||||||
redirect(url, f = false) {
|
redirect(url, f = false) {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!/^(http[s]?|ftp):\/\//.test(url)) {
|
if (!/^(http[s]?|ftp):\/\//.test(url)) {
|
||||||
url = '//' + url
|
url = '//' + url
|
||||||
}
|
}
|
||||||
this.set('Location', url)
|
this.set('Location', url)
|
||||||
this.status(f ? 301 : 302)
|
this.status = f ? 301 : 302
|
||||||
this.end('')
|
this.body = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [location 页面跳转(前端的方式)]
|
* [location 页面跳转(前端的方式)]
|
||||||
*/
|
*/
|
||||||
location(url) {
|
location(url) {
|
||||||
var html = `<html><head><meta http-equiv="refresh" content="0;url=${url}"></head></html>`
|
let html = `<html><head><meta http-equiv="refresh" content="0;url=${url}"></head></html>`
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.render(html)
|
this.render(html)
|
||||||
|
@ -71,46 +98,77 @@ export default class Response {
|
||||||
|
|
||||||
// 以html格式向前端输出内容
|
// 以html格式向前端输出内容
|
||||||
render(data, code) {
|
render(data, code) {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data += ''
|
data += ''
|
||||||
data = data || STATUS_TEXT[code]
|
data = data || STATUS_TEXT[code]
|
||||||
this.set('Content-Type', 'text/html')
|
this.type = 'html'
|
||||||
this.set('Content-Length', Buffer.byteLength(data))
|
this.set('Content-Length', Buffer.byteLength(data))
|
||||||
if (code) {
|
if (code) {
|
||||||
this.status(code)
|
this.status = code
|
||||||
}
|
}
|
||||||
this.end(data)
|
this.body = data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件下载
|
// 文件下载
|
||||||
sendfile(target, filename) {
|
sendfile(target, filename = 'untitled') {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var data
|
let data, start, end
|
||||||
|
|
||||||
this.set('Content-Type', 'application/force-download')
|
if (!this.type) {
|
||||||
this.set('Accept-Ranges', 'bytes')
|
this.status = 206
|
||||||
this.set('Content-Disposition', `attachment;filename="${filename}"`)
|
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)) {
|
if (Buffer.isBuffer(target)) {
|
||||||
data = target
|
data = target
|
||||||
} else {
|
} else {
|
||||||
if (typeof target === 'string') {
|
if (typeof target === 'string') {
|
||||||
var stat = fs.stat(target)
|
let stat = fs.stat(target)
|
||||||
if (stat.isFile()) {
|
if (stat.isFile()) {
|
||||||
this.set('Content-Length', stat.size)
|
let size = stat.size
|
||||||
fs.origin.createReadStream(target).pipe(this.origin.res)
|
if (start !== void 0) {
|
||||||
return
|
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 + '')
|
data = Buffer.from(target + '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (start !== void 0) {
|
||||||
|
data = data.slice(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
this.set('Content-Length', data.length)
|
this.set('Content-Length', data.length)
|
||||||
this.end(data)
|
this.body = data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,91 +178,56 @@ export default class Response {
|
||||||
* @param {Str/Obj} data [额外数据]
|
* @param {Str/Obj} data [额外数据]
|
||||||
* @param {Str} callback [回调函数名]
|
* @param {Str} callback [回调函数名]
|
||||||
*/
|
*/
|
||||||
send(code = 200, msg = 'success', data = null, callback = null) {
|
send(code = 200, msg = '', data, callback) {
|
||||||
var output
|
let output
|
||||||
|
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (typeof code !== 'number') {
|
|
||||||
if (typeof code === 'object') {
|
if (msg && typeof msg === 'object') {
|
||||||
data = code
|
callback = data
|
||||||
code = 200
|
|
||||||
msg = STATUS_TEXT[code]
|
|
||||||
} else {
|
|
||||||
msg = code + ''
|
|
||||||
code = 400
|
|
||||||
}
|
|
||||||
} else if (typeof msg === 'object') {
|
|
||||||
data = msg
|
data = msg
|
||||||
code = code || 200
|
msg = STATUS_TEXT[code]
|
||||||
msg = STATUS_TEXT[code] || 'success'
|
} else {
|
||||||
|
msg = msg || STATUS_TEXT[code]
|
||||||
}
|
}
|
||||||
|
|
||||||
output = { code, msg, data }
|
output = { code, msg, data }
|
||||||
output = JSON.stringify(output)
|
output = JSON.stringify(output)
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback = callback.replace(/[^\w\-\.]/g, '')
|
callback = callback.replace(/[^\w\.]/g, '')
|
||||||
output = callback + '(' + output + ')'
|
output = callback + '(' + output + ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('Content-Type', 'application/json')
|
this.type = 'json'
|
||||||
this.set('Content-Length', Buffer.byteLength(output))
|
this.set('Content-Length', Buffer.byteLength(output))
|
||||||
|
|
||||||
// 只设置200以上的值
|
// 只设置200以上的值
|
||||||
if (code && code > 200) {
|
if (code && code >= 200 && code <= 599) {
|
||||||
this.status(code)
|
this.status = code
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end(output)
|
this.body = output
|
||||||
}
|
|
||||||
|
|
||||||
end(buf) {
|
|
||||||
var code = 200
|
|
||||||
if (this.rendered) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
if (this.statusCode) {
|
|
||||||
code = this.statusCode
|
|
||||||
delete this.statusCode
|
|
||||||
}
|
|
||||||
this.rendered = true
|
|
||||||
this.origin.res.writeHead(code, STATUS_TEXT[code])
|
|
||||||
this.origin.res.end(buf || '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [get 读取已写入的头信息]
|
* [get 读取已写入的头信息]
|
||||||
*/
|
*/
|
||||||
get(key) {
|
get(key) {
|
||||||
return this.origin.res.getHeader(key)
|
return this.#res.getHeader(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [set 设置头信息]
|
* [set 设置头信息]
|
||||||
*/
|
*/
|
||||||
set(key, val) {
|
set(key, val) {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
if (arguments.length === 2) {
|
let value = Array.isArray(val) ? val.map(String) : String(val)
|
||||||
var value = Array.isArray(val) ? val.map(String) : String(val)
|
this.#res.setHeader(key, value)
|
||||||
|
|
||||||
if (
|
|
||||||
key.toLowerCase() === 'content-type' &&
|
|
||||||
typeof value === 'string' &&
|
|
||||||
value.indexOf('charset') < 0
|
|
||||||
) {
|
|
||||||
value += '; charset=utf-8'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.origin.res.setHeader(key, value)
|
|
||||||
} else {
|
|
||||||
for (let i in key) {
|
|
||||||
this.set(i, key[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,26 +237,18 @@ export default class Response {
|
||||||
* @param {String} val [description]
|
* @param {String} val [description]
|
||||||
*/
|
*/
|
||||||
append(key, val) {
|
append(key, val) {
|
||||||
if (this.rendered) {
|
if (this.#ended) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var prev = this.get(key)
|
let prev = this.get(key) || []
|
||||||
var value
|
|
||||||
|
|
||||||
if (Array.isArray(val)) {
|
if (!Array.isArray(prev)) {
|
||||||
value = val
|
prev = [prev]
|
||||||
} else {
|
|
||||||
value = [val]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev) {
|
prev = prev.concat(val)
|
||||||
if (Array.isArray(prev)) {
|
|
||||||
value = prev.concat(val)
|
return this.set(key, prev)
|
||||||
} else {
|
|
||||||
value = [prev].concat(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.set(key, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,7 +259,7 @@ export default class Response {
|
||||||
*/
|
*/
|
||||||
cookie(key, val, opts = {}) {
|
cookie(key, val, opts = {}) {
|
||||||
//读取之前已经写过的cookie缓存
|
//读取之前已经写过的cookie缓存
|
||||||
var cache = this.get('set-cookie')
|
let cache = this.get('set-cookie')
|
||||||
if (cache) {
|
if (cache) {
|
||||||
if (!Array.isArray(cache)) {
|
if (!Array.isArray(cache)) {
|
||||||
cache = [cache]
|
cache = [cache]
|
||||||
|
|
|
@ -2,6 +2,7 @@ export default {
|
||||||
'100': 'Continue',
|
'100': 'Continue',
|
||||||
'101': 'Switching Protocols',
|
'101': 'Switching Protocols',
|
||||||
'102': 'Processing',
|
'102': 'Processing',
|
||||||
|
'103': 'Early Hints',
|
||||||
'200': 'OK',
|
'200': 'OK',
|
||||||
'201': 'Created',
|
'201': 'Created',
|
||||||
'202': 'Accepted',
|
'202': 'Accepted',
|
||||||
|
@ -11,6 +12,7 @@ export default {
|
||||||
'206': 'Partial Conten',
|
'206': 'Partial Conten',
|
||||||
'207': 'Multi-Status',
|
'207': 'Multi-Status',
|
||||||
'208': 'Already Reported',
|
'208': 'Already Reported',
|
||||||
|
'218': 'This is fine',
|
||||||
'226': 'IM Used',
|
'226': 'IM Used',
|
||||||
'300': 'Multiple Choices',
|
'300': 'Multiple Choices',
|
||||||
'301': 'Moved Permanently',
|
'301': 'Moved Permanently',
|
||||||
|
@ -40,20 +42,32 @@ export default {
|
||||||
'416': 'Requested Range Not Satisfiable',
|
'416': 'Requested Range Not Satisfiable',
|
||||||
'417': 'Expectation Failed',
|
'417': 'Expectation Failed',
|
||||||
'418': "I'm a teapot",
|
'418': "I'm a teapot",
|
||||||
|
'419': 'Page Expired',
|
||||||
'420': 'Enhance Your Caim',
|
'420': 'Enhance Your Caim',
|
||||||
'421': 'Misdirected Request',
|
'421': 'Misdirected Request',
|
||||||
'422': 'Unprocessable Entity',
|
'422': 'Unprocessable Entity',
|
||||||
'423': 'Locked',
|
'423': 'Locked',
|
||||||
'424': 'Failed Dependency',
|
'424': 'Failed Dependency',
|
||||||
'425': 'Unordered Collection',
|
'425': 'Too Early',
|
||||||
'426': 'Upgrade Required',
|
'426': 'Upgrade Required',
|
||||||
'428': 'Precondition Required',
|
'428': 'Precondition Required',
|
||||||
'429': 'Too Many Requests',
|
'429': 'Too Many Requests',
|
||||||
|
'430': 'Would Block',
|
||||||
'431': 'Request Header Fields Too Large',
|
'431': 'Request Header Fields Too Large',
|
||||||
|
'440': 'Login Time-Out',
|
||||||
'444': 'No Response',
|
'444': 'No Response',
|
||||||
|
'449': 'Retry With',
|
||||||
'450': 'Blocked by Windows Parental Controls',
|
'450': 'Blocked by Windows Parental Controls',
|
||||||
'451': 'Unavailable For Legal Reasons',
|
'451': 'Unavailable For Legal Reasons',
|
||||||
|
'460': 'Client Closed Connection Prematurely',
|
||||||
|
'463': 'Too Many Forwarded IP Addresses',
|
||||||
|
'464': 'Incompatible Protocol',
|
||||||
'494': 'Request Header Too Large',
|
'494': 'Request Header Too Large',
|
||||||
|
'495': 'SSL Certificate Error',
|
||||||
|
'496': 'SSL Certificate Required',
|
||||||
|
'497': 'HTTP Request Sent to HTTPS Port',
|
||||||
|
'498': 'Invalid Token',
|
||||||
|
'499': 'Token Required or Client Closed Request',
|
||||||
'500': 'Internal Server Error',
|
'500': 'Internal Server Error',
|
||||||
'501': 'Not Implemented',
|
'501': 'Not Implemented',
|
||||||
'502': 'Bad Gateway',
|
'502': 'Bad Gateway',
|
||||||
|
@ -63,6 +77,20 @@ export default {
|
||||||
'506': 'Variant Also Negotiates',
|
'506': 'Variant Also Negotiates',
|
||||||
'507': 'Insufficient Storage',
|
'507': 'Insufficient Storage',
|
||||||
'508': 'Loop Detected',
|
'508': 'Loop Detected',
|
||||||
|
'509': 'Bandwidth Limit Exceeded',
|
||||||
'510': 'Not Extended',
|
'510': 'Not Extended',
|
||||||
'511': 'Network Authentication Required'
|
'511': 'Network Authentication Required',
|
||||||
|
'520': 'Web Server Is Returning an Unknown Error',
|
||||||
|
'521': 'Web Server Is Down',
|
||||||
|
'522': 'Connection Timed Out',
|
||||||
|
'523': 'Origin Is Unreachable',
|
||||||
|
'524': 'A Timeout Occurred',
|
||||||
|
'525': 'SSL Handshake Failed',
|
||||||
|
'526': 'Invalid SSL Certificate',
|
||||||
|
'527': 'Railgun Listener to Origin',
|
||||||
|
'529': 'The Service Is Overloaded',
|
||||||
|
'530': 'Site Frozen',
|
||||||
|
'561': 'Unauthorized',
|
||||||
|
'598': 'Network Read Timeout Error',
|
||||||
|
'599': 'Network Connect Timeout Error',
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
|
||||||
|
export const CHARSET_TYPES = {
|
||||||
|
html:1,
|
||||||
|
txt:1,
|
||||||
|
css:1,
|
||||||
|
scss:1,
|
||||||
|
xml:1,
|
||||||
|
js:1,
|
||||||
|
json: 1,webmanifest:1
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIME_TYPES = {
|
||||||
|
html: 'text/html',
|
||||||
|
txt: 'text/plain',
|
||||||
|
css: 'text/css',
|
||||||
|
xml: 'text/xml',
|
||||||
|
gif: 'image/gif',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
webp: 'image/webp',
|
||||||
|
tiff: 'image/tiff',
|
||||||
|
png: 'image/png',
|
||||||
|
svg: 'image/svg+xml',
|
||||||
|
ico: 'image/x-icon',
|
||||||
|
bmp: 'image/x-ms-bmp',
|
||||||
|
js: 'application/javascript',
|
||||||
|
json: 'application/json',
|
||||||
|
webmanifest: 'application/json',
|
||||||
|
mp3: 'audio/mpeg',
|
||||||
|
ogg: 'audio/ogg',
|
||||||
|
m4a: 'audio/x-m4a',
|
||||||
|
mp4: 'video/mp4',
|
||||||
|
webm: 'video/webm',
|
||||||
|
ttf: 'font/font-ttf',
|
||||||
|
woff: 'font/font-woff',
|
||||||
|
woff2: 'font/font-woff2',
|
||||||
|
wast: 'application/wast',
|
||||||
|
wasm: 'application/wasm',
|
||||||
|
stream: 'application/octet-stream'
|
||||||
|
}
|
||||||
|
|
||||||
|
MIME_TYPES.vue = MIME_TYPES.js
|
||||||
|
MIME_TYPES.scss = MIME_TYPES.css
|
||||||
|
MIME_TYPES.htm = MIME_TYPES.html
|
||||||
|
MIME_TYPES.jpeg = MIME_TYPES.jpg
|
||||||
|
MIME_TYPES.tif = MIME_TYPES.tiff
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue