request/index.js

255 lines
5.9 KiB
JavaScript

/**
* @author yutent<yutent.io@gmail.com>
* @date 2020/09/16 16:05:57
*/
import 'es.shim'
import Parser from './lib/index.js'
import { parseCookie } from './lib/cookie.js'
import fs from 'iofs'
import { fileURLToPath, parse } from 'node:url'
import QS from 'node:querystring'
import { dirname, resolve } from 'node:path'
const DEFAULT_FORM_TYPE = 'application/x-www-form-urlencoded'
const __dirname = dirname(fileURLToPath(import.meta.url))
const tmpdir = resolve(__dirname, '.tmp/')
const encode = encodeURIComponent
const decode = decodeURIComponent
if (fs.isdir(tmpdir)) {
fs.rm(tmpdir, true)
}
fs.mkdir(tmpdir)
function hideProperty(host, name, value) {
Object.defineProperty(host, name, {
value: value,
writable: true,
enumerable: false,
configurable: true
})
}
export default class Request {
#req = null
#res = null
#query = null
#body = null
#cookies = Object.create(null)
method = 'GET'
path = []
url = ''
host = '127.0.0.1'
constructor(req, res) {
this.method = req.method.toUpperCase()
this.#req = req
this.#res = res
this.host = req.headers['host']
this.#cookies = parseCookie(this.headers['cookie'] || '')
this.#init()
}
// 修正请求的url
#init() {
let _url = parse(this.#req.url)
.pathname.slice(1)
.replace(/[\/]+$/, '')
let app = '' // 将作为主控制器(即apps目录下的应用)
let pathArr = []
// URL上不允许有非法字符
if (/[^\w-/.,@~!$&:+'"=]/.test(decode(_url))) {
this.#res.rendered = true
this.#res.writeHead(400, {
'X-debug': `url [/${encode(_url)}] contains invalid characters`
})
return this.#res.end(`Invalid characters: /${_url}`)
}
// 修正url中可能出现的"多斜杠"
_url = _url.replace(/[\/]+/g, '/').replace(/^\//, '')
pathArr = _url.split('/')
if (!pathArr[0] || pathArr[0] === '') {
pathArr[0] = 'index'
}
if (pathArr[0].indexOf('.') !== -1) {
app = pathArr[0].slice(0, pathArr[0].indexOf('.'))
// 如果app为空(这种情况一般是url前面带了个"."造成的),则自动默认为index
if (!app || app === '') {
app = 'index'
}
} else {
app = pathArr[0]
}
pathArr.shift()
this.app = app
this.url = _url
this.path = pathArr
}
/**
* [解析请求体, 需要 await ]
* @param {Str} key [字段]
*/
#parseBody() {
let out = Promise.defer()
let form, contentType
this.#body = {}
contentType = this.header('content-type') || DEFAULT_FORM_TYPE
form = new Parser(this.#req, { uploadDir: tmpdir })
form
.on('field', (name, value) => {
if (name === false) {
this.#body = value
return
}
if (~contentType.indexOf('urlencoded')) {
if (
name.slice(0, 2) === '{"' &&
(name.slice(-2) === '"}' || value.slice(-2) === '"}')
) {
name = name.replace(/\s/g, '+')
if (value.slice(0, 1) === '=') value = '=' + value
return Object.assign(this.#body, JSON.parse(name + value))
}
}
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)
name = name.slice(0, name.lastIndexOf('['))
//多解析一层对象(也仅支持到这一层)
if (name.slice(-1) === ']') {
let pkey = name.slice(name.lastIndexOf('[') + 1, -1)
name = name.slice(0, name.lastIndexOf('['))
if (!this.#body.hasOwnProperty(name)) {
this.#body[name] = {}
}
if (!this.#body[name].hasOwnProperty(pkey)) {
this.#body[name][pkey] = {}
}
this.#body[name][pkey][key] = value
} else {
if (!this.#body.hasOwnProperty(name)) {
this.#body[name] = {}
}
this.#body[name][key] = value
}
return
}
this.#body[name] = value
})
.on('file', (name, file) => {
if (name === false) {
this.#body = file
} else {
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)
}
}
})
.on('error', out.reject)
.on('end', _ => {
if (contentType.includes('urlencoded')) {
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])
}
}
}
out.resolve(this.#body)
})
return out.promise
}
//获取响应头
header(key = '') {
key = key ? (key + '').toLowerCase() : null
return !!key ? this.#req.headers[key] : this.#req.headers
}
// 读取cookie
cookie(key) {
if (key) {
return this.#cookies[key]
}
return this.#cookies
}
get query() {
if (!this.#query) {
let para = parse(this.#req.url).query
this.#query = {}
para = Object.assign(this.#query, QS.parse(para))
}
return this.#query
}
get body() {
if (this.#body) {
return this.#body
}
return this.#parseBody()
}
get cookies() {
return this.#cookies
}
get headers() {
return this.#req.headers
}
//获取客户端IP
get ip() {
return (
this.headers['x-real-ip'] ||
this.headers['x-forwarded-for'] ||
this.#req.connection.remoteAddress.replace('::ffff:', '')
)
}
}
对Http的request进一步封装, 提供常用的API
JavaScript 100%