request/index.js

264 lines
6.1 KiB
JavaScript
Raw Normal View History

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)
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'
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
this.host = req.headers['host'] || '127.0.0.1'
2023-11-01 14:19:00 +08:00
this.hostname = this.host.split(':')[0]
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() {
let url = parse(this.#req.url)
2020-09-16 20:07:28 +08:00
.pathname.slice(1)
.replace(/[\/]+$/, '')
let controller = '' // 将作为主控制器(即apps目录下的应用)
let path = []
2020-09-16 20:07:28 +08:00
// URL上不允许有非法字符
if (/[^\w-/.,@~!$&:+'"=]/.test(decode(url))) {
2023-10-26 19:02:46 +08:00
this.#res.rendered = true
this.#res.writeHead(400, {
'X-debug': `url [/${encode(url)}] contains invalid characters`
2020-09-16 20:07:28 +08:00
})
return this.#res.end(`Invalid characters: /${url}`)
2020-09-16 20:07:28 +08:00
}
// 修正url中可能出现的"多斜杠"
url = url.replace(/[\/]+/g, '/').replace(/^\//, '')
2020-09-16 20:07:28 +08:00
path = url.split('/')
if (!path[0] || path[0] === '') {
path[0] = 'index'
2020-09-16 20:07:28 +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
if (!controller || controller === '') {
controller = 'index'
2020-09-16 20:07:28 +08:00
}
} else {
controller = path[0]
2020-09-16 20:07:28 +08:00
}
path.shift()
2020-09-16 20:07:28 +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
}
}
})
.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
)
}
}