/** * @author yutent * @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 #opts = {} #query = null #body = null #cookies = Object.create(null) method = 'GET' path = [] url = '' host = '127.0.0.1' constructor(req, res, opts = {}) { this.method = req.method.toUpperCase() this.#req = req this.#res = res this.host = req.headers['host'] this.#cookies = parseCookie(this.headers['cookie'] || '') Object.assign(this.#opts, opts) 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, { ...this.#opts, 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 = 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:', '') ) } }