2020-09-16 20:07:28 +08:00
|
|
|
/**
|
|
|
|
* @author yutent<yutent.io@gmail.com>
|
|
|
|
* @date 2020/09/16 16:05:57
|
|
|
|
*/
|
|
|
|
|
|
|
|
import 'es.shim'
|
|
|
|
|
|
|
|
import Parser from './lib/index.js'
|
2020-09-21 17:57:42 +08:00
|
|
|
import { parseCookie } from './lib/cookie.js'
|
2020-09-16 20:07:28 +08:00
|
|
|
import fs from 'iofs'
|
2023-10-26 19:02:46 +08:00
|
|
|
import { fileURLToPath, parse } from 'node:url'
|
|
|
|
import QS from 'node:querystring'
|
|
|
|
import { dirname, resolve } from 'node:path'
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2020-12-17 10:36:48 +08:00
|
|
|
const DEFAULT_FORM_TYPE = 'application/x-www-form-urlencoded'
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
2020-09-28 09:48:20 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
const tmpdir = resolve(__dirname, '.tmp/')
|
2023-10-25 18:45:16 +08:00
|
|
|
const encode = encodeURIComponent
|
|
|
|
const decode = decodeURIComponent
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2022-01-08 16:59:11 +08:00
|
|
|
if (fs.isdir(tmpdir)) {
|
|
|
|
fs.rm(tmpdir, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.mkdir(tmpdir)
|
|
|
|
|
2020-09-16 20:07:28 +08:00
|
|
|
function hideProperty(host, name, value) {
|
|
|
|
Object.defineProperty(host, name, {
|
|
|
|
value: value,
|
|
|
|
writable: true,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Request {
|
2023-10-26 19:02:46 +08:00
|
|
|
#req = null
|
|
|
|
#res = null
|
|
|
|
|
2023-10-30 16:41:37 +08:00
|
|
|
#opts = {}
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
#query = null
|
|
|
|
#body = null
|
|
|
|
#cookies = Object.create(null)
|
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
controller = 'index'
|
2023-10-26 19:02:46 +08:00
|
|
|
method = 'GET'
|
|
|
|
path = []
|
|
|
|
|
|
|
|
url = ''
|
|
|
|
host = '127.0.0.1'
|
2023-11-01 14:19:00 +08:00
|
|
|
hostname = '127.0.0.1'
|
2023-10-31 14:28:15 +08:00
|
|
|
protocol = 'http'
|
2023-10-26 19:02:46 +08:00
|
|
|
|
2023-10-30 16:41:37 +08:00
|
|
|
constructor(req, res, opts = {}) {
|
2020-09-16 20:07:28 +08:00
|
|
|
this.method = req.method.toUpperCase()
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#req = req
|
|
|
|
this.#res = res
|
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
this.host = req.headers['host'] || '127.0.0.1'
|
2023-11-01 14:19:00 +08:00
|
|
|
this.hostname = this.host.split(':')[0]
|
2023-10-31 14:28:15 +08:00
|
|
|
this.protocol = req.headers['x-forwarded-proto'] || 'http'
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#cookies = parseCookie(this.headers['cookie'] || '')
|
|
|
|
|
2023-10-30 16:41:37 +08:00
|
|
|
Object.assign(this.#opts, opts)
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#init()
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 修正请求的url
|
2023-10-26 19:02:46 +08:00
|
|
|
#init() {
|
2023-10-31 14:28:15 +08:00
|
|
|
let url = parse(this.#req.url)
|
2020-09-16 20:07:28 +08:00
|
|
|
.pathname.slice(1)
|
|
|
|
.replace(/[\/]+$/, '')
|
2023-10-31 14:28:15 +08:00
|
|
|
let controller = '' // 将作为主控制器(即apps目录下的应用)
|
|
|
|
let path = []
|
2020-09-16 20:07:28 +08:00
|
|
|
|
|
|
|
// URL上不允许有非法字符
|
2023-10-31 14:28:15 +08:00
|
|
|
if (/[^\w-/.,@~!$&:+'"=]/.test(decode(url))) {
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#res.rendered = true
|
|
|
|
this.#res.writeHead(400, {
|
2023-10-31 14:28:15 +08:00
|
|
|
'X-debug': `url [/${encode(url)}] contains invalid characters`
|
2020-09-16 20:07:28 +08:00
|
|
|
})
|
2023-10-31 14:28:15 +08:00
|
|
|
return this.#res.end(`Invalid characters: /${url}`)
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 修正url中可能出现的"多斜杠"
|
2023-10-31 14:28:15 +08:00
|
|
|
url = url.replace(/[\/]+/g, '/').replace(/^\//, '')
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
path = url.split('/')
|
|
|
|
if (!path[0] || path[0] === '') {
|
|
|
|
path[0] = 'index'
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
if (path[0].indexOf('.') !== -1) {
|
|
|
|
controller = path[0].slice(0, path[0].indexOf('.'))
|
2020-09-16 20:07:28 +08:00
|
|
|
// 如果app为空(这种情况一般是url前面带了个"."造成的),则自动默认为index
|
2023-10-31 14:28:15 +08:00
|
|
|
if (!controller || controller === '') {
|
|
|
|
controller = 'index'
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
} else {
|
2023-10-31 14:28:15 +08:00
|
|
|
controller = path[0]
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
path.shift()
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-31 14:28:15 +08:00
|
|
|
this.controller = controller
|
|
|
|
this.url = url
|
|
|
|
this.path = path
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-10-26 19:02:46 +08:00
|
|
|
* [解析请求体, 需要 await ]
|
2020-09-16 20:07:28 +08:00
|
|
|
* @param {Str} key [字段]
|
|
|
|
*/
|
2023-10-26 19:02:46 +08:00
|
|
|
#parseBody() {
|
2020-09-16 20:07:28 +08:00
|
|
|
let out = Promise.defer()
|
2020-12-17 10:36:48 +08:00
|
|
|
let form, contentType
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#body = {}
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2020-12-17 10:36:48 +08:00
|
|
|
contentType = this.header('content-type') || DEFAULT_FORM_TYPE
|
|
|
|
|
2023-10-30 16:41:37 +08:00
|
|
|
form = new Parser(this.#req, { ...this.#opts, uploadDir: tmpdir })
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
form
|
|
|
|
.on('field', (name, value) => {
|
|
|
|
if (name === false) {
|
|
|
|
this.#body = value
|
|
|
|
return
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
2023-10-26 19:02:46 +08:00
|
|
|
if (~contentType.indexOf('urlencoded')) {
|
|
|
|
if (
|
|
|
|
name.slice(0, 2) === '{"' &&
|
|
|
|
(name.slice(-2) === '"}' || value.slice(-2) === '"}')
|
|
|
|
) {
|
|
|
|
name = name.replace(/\s/g, '+')
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
if (value.slice(0, 1) === '=') value = '=' + value
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
return Object.assign(this.#body, JSON.parse(name + value))
|
|
|
|
}
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
if (name.slice(-2) === '[]') {
|
|
|
|
name = name.slice(0, -2)
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = [value]
|
|
|
|
}
|
|
|
|
} else if (name.slice(-1) === ']') {
|
|
|
|
let key = name.slice(name.lastIndexOf('[') + 1, -1)
|
2020-09-16 20:07:28 +08:00
|
|
|
name = name.slice(0, name.lastIndexOf('['))
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
//多解析一层对象(也仅支持到这一层)
|
|
|
|
if (name.slice(-1) === ']') {
|
|
|
|
let pkey = name.slice(name.lastIndexOf('[') + 1, -1)
|
|
|
|
name = name.slice(0, name.lastIndexOf('['))
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
if (!this.#body.hasOwnProperty(name)) {
|
|
|
|
this.#body[name] = {}
|
|
|
|
}
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
if (!this.#body[name].hasOwnProperty(pkey)) {
|
|
|
|
this.#body[name][pkey] = {}
|
|
|
|
}
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#body[name][pkey][key] = value
|
|
|
|
} else {
|
|
|
|
if (!this.#body.hasOwnProperty(name)) {
|
|
|
|
this.#body[name] = {}
|
|
|
|
}
|
2020-09-16 20:07:28 +08:00
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#body[name][key] = value
|
|
|
|
}
|
|
|
|
return
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
2023-10-26 19:02:46 +08:00
|
|
|
this.#body[name] = value
|
|
|
|
})
|
|
|
|
.on('file', (name, file) => {
|
2023-10-27 19:16:32 +08:00
|
|
|
if (name === false) {
|
|
|
|
this.#body = file
|
2023-10-26 19:02:46 +08:00
|
|
|
} else {
|
2023-10-27 19:16:32 +08:00
|
|
|
if (name.slice(-2) === '[]') {
|
|
|
|
name = name.slice(0, -2)
|
|
|
|
}
|
|
|
|
if (!this.#body.hasOwnProperty(name)) {
|
|
|
|
this.#body[name] = file
|
|
|
|
} else {
|
|
|
|
if (!Array.isArray(this.#body[name])) {
|
|
|
|
this.#body[name] = [this.#body[name]]
|
|
|
|
}
|
|
|
|
this.#body[name].push(file)
|
2023-10-26 19:02:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2024-07-10 16:44:17 +08:00
|
|
|
.on('buffer', buf => {
|
|
|
|
this.#body = buf
|
|
|
|
})
|
2023-10-26 19:02:46 +08:00
|
|
|
.on('error', out.reject)
|
2023-10-27 19:16:32 +08:00
|
|
|
.on('end', _ => {
|
|
|
|
if (contentType.includes('urlencoded')) {
|
2023-10-26 19:02:46 +08:00
|
|
|
for (let i in this.#body) {
|
|
|
|
if (typeof this.#body[i] === 'string') {
|
|
|
|
if (!this.#body[i]) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
this.#body[i] = Number.parse(this.#body[i])
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-26 19:02:46 +08:00
|
|
|
|
|
|
|
out.resolve(this.#body)
|
|
|
|
})
|
2020-09-16 20:07:28 +08:00
|
|
|
return out.promise
|
|
|
|
}
|
|
|
|
|
|
|
|
//获取响应头
|
|
|
|
header(key = '') {
|
|
|
|
key = key ? (key + '').toLowerCase() : null
|
2023-10-26 19:02:46 +08:00
|
|
|
return !!key ? this.#req.headers[key] : this.#req.headers
|
2020-09-16 20:07:28 +08:00
|
|
|
}
|
|
|
|
|
2020-09-21 17:57:42 +08:00
|
|
|
// 读取cookie
|
|
|
|
cookie(key) {
|
|
|
|
if (key) {
|
2023-10-26 19:02:46 +08:00
|
|
|
return this.#cookies[key]
|
|
|
|
}
|
|
|
|
return this.#cookies
|
|
|
|
}
|
|
|
|
|
|
|
|
get query() {
|
|
|
|
if (!this.#query) {
|
|
|
|
let para = parse(this.#req.url).query
|
2023-10-30 17:04:07 +08:00
|
|
|
this.#query = QS.parse(para)
|
2023-10-26 19:02:46 +08:00
|
|
|
}
|
|
|
|
return this.#query
|
|
|
|
}
|
|
|
|
|
|
|
|
get body() {
|
|
|
|
if (this.#body) {
|
|
|
|
return this.#body
|
2020-09-21 17:57:42 +08:00
|
|
|
}
|
2023-10-26 19:02:46 +08:00
|
|
|
return this.#parseBody()
|
|
|
|
}
|
|
|
|
|
|
|
|
get cookies() {
|
|
|
|
return this.#cookies
|
|
|
|
}
|
|
|
|
|
|
|
|
get headers() {
|
|
|
|
return this.#req.headers
|
2020-09-21 17:57:42 +08:00
|
|
|
}
|
|
|
|
|
2020-09-16 20:07:28 +08:00
|
|
|
//获取客户端IP
|
2023-10-26 19:02:46 +08:00
|
|
|
get ip() {
|
2020-09-16 20:07:28 +08:00
|
|
|
return (
|
2023-10-26 19:02:46 +08:00
|
|
|
this.headers['x-real-ip'] ||
|
|
|
|
this.headers['x-forwarded-for'] ||
|
|
|
|
this.#req.connection.remoteAddress.replace('::ffff:', '')
|
2020-09-16 20:07:28 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|