Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
yutent | 7f6e506f4a | |
yutent | a395c87101 | |
yutent | 8afca9afaa | |
yutent | 9d66a77b4d | |
yutent | 2203b683f5 | |
yutent | face3185f7 | |
yutent | 5ecf94b5f3 | |
yutent | 53b0e2443e |
110
Readme.md
110
Readme.md
|
@ -1,4 +1,6 @@
|
|||
![module info](https://nodei.co/npm/@gm5/request.png?downloads=true&downloadRank=true&stars=true)
|
||||
|
||||
![downloads](https://img.shields.io/npm/dt/@gm5/request.svg)
|
||||
![version](https://img.shields.io/npm/v/@gm5/request.svg)
|
||||
|
||||
# @gm5/equest
|
||||
> 对Http的request进一步封装, 提供常用的API.
|
||||
|
@ -19,115 +21,13 @@ http
|
|||
.createServer((req, res) => {
|
||||
let request = new Request(req, res)
|
||||
|
||||
console.log(request.origin) // {req, res}
|
||||
|
||||
// print the fixed url
|
||||
console.log(request.url)
|
||||
|
||||
request.ip() // get client ip address
|
||||
request.ip // get client ip address
|
||||
|
||||
// http://test.com/?foo=bar
|
||||
request.get('foo') // bar
|
||||
request.query['foo'] // bar
|
||||
})
|
||||
.listen(3000)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### origin
|
||||
> 返回原始的response & request对象
|
||||
|
||||
```js
|
||||
console.log(request.origin) // {req: request, res: response}
|
||||
```
|
||||
|
||||
|
||||
### app
|
||||
> 返回一级路由的名字
|
||||
|
||||
```js
|
||||
// abc.com/foo/bar
|
||||
console.log(request.app) // foo
|
||||
```
|
||||
|
||||
|
||||
### path
|
||||
> 以数组形式,返回除一级路由之外剩下的路径
|
||||
|
||||
```js
|
||||
// abc.com/foo/bar/aa/bb
|
||||
console.log(request.path) // ['bar', 'aa', 'bb']
|
||||
```
|
||||
|
||||
### url
|
||||
> 返回修正过的url路径
|
||||
|
||||
```js
|
||||
// abc.com/foo/bar/aa/bb
|
||||
// abc.com////foo///bar/aa/bb
|
||||
console.log(request.url) // foo/bar/aa/bb
|
||||
```
|
||||
|
||||
|
||||
|
||||
### get([key[,xss]])
|
||||
|
||||
* key `<String>` 字段名 [可选], 不则返回全部参数
|
||||
* xss `<Boolean>` 是否进行xss过滤 [可选], 默认为ture
|
||||
|
||||
> 返回URL上的query参数, 类似于`$_GET[]`;
|
||||
|
||||
|
||||
```javascript
|
||||
// http://test.com?name=foo&age=18
|
||||
request.get('name') // foo
|
||||
request.get('age') // 18
|
||||
|
||||
request.get() // {name: 'foo', age: 18}
|
||||
request.get('weight') // return null if not exists
|
||||
```
|
||||
|
||||
### post([key[,xss]])
|
||||
|
||||
* key `<String>` optional
|
||||
* xss `<Boolean>` optional
|
||||
|
||||
> 读取post请求的body, 类似于 `$_POST[]`.
|
||||
|
||||
> **该方法返回的是Promise对象**
|
||||
|
||||
```javascript
|
||||
// http://test.com
|
||||
await request.post('name') // foo
|
||||
await request.post('age') // 18
|
||||
|
||||
// return all if not yet argument given
|
||||
await request.post() // {name: 'foo', age: 18}
|
||||
await request.post('weight') // return null if not exists
|
||||
```
|
||||
|
||||
### header([key])
|
||||
|
||||
* key `<String>` 字段名[可选], 不传则返回全部
|
||||
|
||||
> 返回请求头
|
||||
|
||||
```javascript
|
||||
request.header('user-agent') // Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ...
|
||||
|
||||
// return all if not yet argument given
|
||||
request.header() // {'user-agent': '...'[, ...]}
|
||||
```
|
||||
|
||||
### ip()
|
||||
|
||||
> 获取客户端IP地址.
|
||||
>
|
||||
> It would return '127.0.0.1' maybe if in local area network.
|
||||
|
||||
|
||||
### cookie(key)
|
||||
|
||||
> 获取客户端带上的cookie.
|
||||
> 不传key时返回所有的
|
||||
|
||||
|
|
86
index.js
86
index.js
|
@ -5,12 +5,13 @@
|
|||
|
||||
import 'es.shim'
|
||||
|
||||
import { fileURLToPath, parse } from 'node:url'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import fs from 'iofs'
|
||||
|
||||
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'
|
||||
import { querystring } from './lib/helper.js'
|
||||
|
||||
const DEFAULT_FORM_TYPE = 'application/x-www-form-urlencoded'
|
||||
|
||||
|
@ -45,11 +46,14 @@ export default class Request {
|
|||
#body = null
|
||||
#cookies = Object.create(null)
|
||||
|
||||
controller = 'index'
|
||||
method = 'GET'
|
||||
path = []
|
||||
|
||||
url = ''
|
||||
host = '127.0.0.1'
|
||||
hostname = '127.0.0.1'
|
||||
protocol = 'http'
|
||||
|
||||
constructor(req, res, opts = {}) {
|
||||
this.method = req.method.toUpperCase()
|
||||
|
@ -57,7 +61,10 @@ export default class Request {
|
|||
this.#req = req
|
||||
this.#res = res
|
||||
|
||||
this.host = req.headers['host']
|
||||
this.host = req.headers['host'] || '127.0.0.1'
|
||||
this.hostname = this.host.split(':')[0]
|
||||
this.protocol = req.headers['x-forwarded-proto'] || 'http'
|
||||
|
||||
this.#cookies = parseCookie(this.headers['cookie'] || '')
|
||||
|
||||
Object.assign(this.#opts, opts)
|
||||
|
@ -67,44 +74,44 @@ export default class Request {
|
|||
|
||||
// 修正请求的url
|
||||
#init() {
|
||||
let _url = parse(this.#req.url)
|
||||
let url = parse(this.#req.url)
|
||||
.pathname.slice(1)
|
||||
.replace(/[\/]+$/, '')
|
||||
let app = '' // 将作为主控制器(即apps目录下的应用)
|
||||
let pathArr = []
|
||||
let controller = '' // 将作为主控制器(即apps目录下的应用)
|
||||
let path = []
|
||||
|
||||
// URL上不允许有非法字符
|
||||
if (/[^\w-/.,@~!$&:+'"=]/.test(decode(_url))) {
|
||||
if (/[^\w-/.,@~!$&:+'"=]/.test(decode(url))) {
|
||||
this.#res.rendered = true
|
||||
this.#res.writeHead(400, {
|
||||
'X-debug': `url [/${encode(_url)}] contains invalid characters`
|
||||
'X-debug': `url [/${encode(url)}] contains invalid characters`
|
||||
})
|
||||
return this.#res.end(`Invalid characters: /${_url}`)
|
||||
return this.#res.end(`Invalid characters: /${url}`)
|
||||
}
|
||||
|
||||
// 修正url中可能出现的"多斜杠"
|
||||
_url = _url.replace(/[\/]+/g, '/').replace(/^\//, '')
|
||||
url = url.replace(/[\/]+/g, '/').replace(/^\//, '')
|
||||
|
||||
pathArr = _url.split('/')
|
||||
if (!pathArr[0] || pathArr[0] === '') {
|
||||
pathArr[0] = 'index'
|
||||
path = url.split('/')
|
||||
if (!path[0] || path[0] === '') {
|
||||
path[0] = 'index'
|
||||
}
|
||||
|
||||
if (pathArr[0].indexOf('.') !== -1) {
|
||||
app = pathArr[0].slice(0, pathArr[0].indexOf('.'))
|
||||
if (path[0].includes('.')) {
|
||||
controller = path[0].slice(0, path[0].indexOf('.'))
|
||||
// 如果app为空(这种情况一般是url前面带了个"."造成的),则自动默认为index
|
||||
if (!app || app === '') {
|
||||
app = 'index'
|
||||
if (!controller || controller === '') {
|
||||
controller = 'index'
|
||||
}
|
||||
} else {
|
||||
app = pathArr[0]
|
||||
controller = path[0]
|
||||
}
|
||||
|
||||
pathArr.shift()
|
||||
path.shift()
|
||||
|
||||
this.app = app
|
||||
this.url = _url
|
||||
this.path = pathArr
|
||||
this.controller = controller
|
||||
this.url = url
|
||||
this.path = path
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,32 +133,22 @@ export default class Request {
|
|||
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) === '[]') {
|
||||
if (name.endsWith('[]')) {
|
||||
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('['))
|
||||
let idx = name.lastIndexOf('[')
|
||||
let key = name.slice(idx + 1, -1)
|
||||
name = name.slice(0, idx)
|
||||
|
||||
//多解析一层对象(也仅支持到这一层)
|
||||
if (name.slice(-1) === ']') {
|
||||
let pkey = name.slice(name.lastIndexOf('[') + 1, -1)
|
||||
name = name.slice(0, name.lastIndexOf('['))
|
||||
idx = name.lastIndexOf('[')
|
||||
let pkey = name.slice(idx + 1, -1)
|
||||
name = name.slice(0, idx)
|
||||
|
||||
if (!this.#body.hasOwnProperty(name)) {
|
||||
this.#body[name] = {}
|
||||
|
@ -191,6 +188,9 @@ export default class Request {
|
|||
}
|
||||
}
|
||||
})
|
||||
.on('buffer', buf => {
|
||||
this.#body = buf
|
||||
})
|
||||
.on('error', out.reject)
|
||||
.on('end', _ => {
|
||||
if (contentType.includes('urlencoded')) {
|
||||
|
@ -225,8 +225,8 @@ export default class Request {
|
|||
|
||||
get query() {
|
||||
if (!this.#query) {
|
||||
let para = parse(this.#req.url).query
|
||||
this.#query = QS.parse(para)
|
||||
let data = parse(this.#req.url).query
|
||||
this.#query = querystring(data)
|
||||
}
|
||||
return this.#query
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* {}
|
||||
* @author yutent<yutent.io@gmail.com>
|
||||
* @date 2024/07/16 17:01:43
|
||||
*/
|
||||
|
||||
import { parse } from 'node:querystring'
|
||||
|
||||
export function querystring(str) {
|
||||
let query = parse(str)
|
||||
for (let k of Object.keys(query)) {
|
||||
let val = query[k]
|
||||
|
||||
if (k.endsWith('[]')) {
|
||||
let _k = k.slice(0, -2)
|
||||
query[_k] = val
|
||||
delete query[k]
|
||||
} else if (k.endsWith(']')) {
|
||||
let idx = k.lastIndexOf('[')
|
||||
let _pk = k.slice(0, idx)
|
||||
let _k = k.slice(idx + 1, -1)
|
||||
|
||||
if (query[_pk]) {
|
||||
query[_pk][_k] = val
|
||||
} else {
|
||||
query[_pk] = { [_k]: val }
|
||||
}
|
||||
delete query[k]
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
29
lib/index.js
29
lib/index.js
|
@ -6,7 +6,7 @@ import { EventEmitter } from 'node:events'
|
|||
import File from './file.js'
|
||||
import { MultipartParser } from './multipart_parser.js'
|
||||
import { UrlencodedParser } from './urlencoded_parser.js'
|
||||
import { OctetParser, EmptyParser } from './octet_parser.js'
|
||||
import { OctetParser, BufferParser, EmptyParser } from './octet_parser.js'
|
||||
import { JSONParser } from './json_parser.js'
|
||||
|
||||
function randomPath(uploadDir) {
|
||||
|
@ -98,8 +98,8 @@ export default class IncomingForm extends EventEmitter {
|
|||
let value = Buffer.from('')
|
||||
|
||||
part
|
||||
.on('data', buff => {
|
||||
value = Buffer.concat([value, buff])
|
||||
.on('data', buf => {
|
||||
value = Buffer.concat([value, buf])
|
||||
})
|
||||
.on('end', () => {
|
||||
this.emit('field', part.name, value.toString(this.encoding))
|
||||
|
@ -138,7 +138,7 @@ export default class IncomingForm extends EventEmitter {
|
|||
}
|
||||
|
||||
#parseContentType() {
|
||||
let contentType = this.headers['content-type']
|
||||
let contentType = this.headers['content-type'] || ''
|
||||
let lower = contentType.toLowerCase()
|
||||
|
||||
if (this.bytesExpected === 0) {
|
||||
|
@ -167,7 +167,7 @@ export default class IncomingForm extends EventEmitter {
|
|||
return this.#createJsonParser()
|
||||
}
|
||||
|
||||
this.#handleError(new TypeError('unknown content-type: ' + contentType))
|
||||
this.#createBufferParser()
|
||||
}
|
||||
|
||||
#parseContentLength() {
|
||||
|
@ -278,6 +278,10 @@ export default class IncomingForm extends EventEmitter {
|
|||
#createUrlencodedParser() {
|
||||
this.#parser = new UrlencodedParser()
|
||||
|
||||
if (this.bytesExpected) {
|
||||
this.#parser.initLength(this.bytesExpected)
|
||||
}
|
||||
|
||||
this.#parser
|
||||
.on('field', fields => this.emit('field', false, fields))
|
||||
.on('end', () => this.#handleEnd())
|
||||
|
@ -316,6 +320,21 @@ export default class IncomingForm extends EventEmitter {
|
|||
.on('error', err => this.#handleError(err))
|
||||
}
|
||||
|
||||
#createBufferParser() {
|
||||
this.#parser = new BufferParser()
|
||||
|
||||
if (this.bytesExpected) {
|
||||
this.#parser.initLength(this.bytesExpected)
|
||||
}
|
||||
|
||||
this.#parser
|
||||
.on('buffer', buf => {
|
||||
this.emit('buffer', buf)
|
||||
})
|
||||
.on('end', () => this.#handleEnd())
|
||||
.on('error', err => this.#handleError(err))
|
||||
}
|
||||
|
||||
#clearUploads() {
|
||||
while (this.#openedFiles.length) {
|
||||
let file = this.#openedFiles.pop()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { EventEmitter } from 'node:events'
|
||||
|
||||
export class JSONParser extends EventEmitter {
|
||||
#buff = Buffer.from('')
|
||||
#buf = Buffer.from('')
|
||||
#byteLen = 0
|
||||
|
||||
initLength(length) {
|
||||
|
@ -9,12 +9,12 @@ export class JSONParser extends EventEmitter {
|
|||
}
|
||||
|
||||
write(buffer) {
|
||||
this.#buff = Buffer.concat([this.#buff, buffer])
|
||||
this.#buf = Buffer.concat([this.#buf, buffer])
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.#buff.length === this.#byteLen) {
|
||||
let data = this.#buff.toString()
|
||||
if (this.#buf.length === this.#byteLen) {
|
||||
let data = this.#buf.toString()
|
||||
let fields = data
|
||||
try {
|
||||
fields = JSON.parse(data)
|
||||
|
@ -28,14 +28,14 @@ export class JSONParser extends EventEmitter {
|
|||
this.emit('field', false, fields)
|
||||
this.emit('end')
|
||||
|
||||
this.#buff = null
|
||||
this.#buf = null
|
||||
} else {
|
||||
this.emit(
|
||||
'error',
|
||||
new Error(
|
||||
`The uploaded data is incomplete. Expected ${
|
||||
this.#byteLen
|
||||
}, Received ${this.#buff.length} .`
|
||||
}, Received ${this.#buf.length} .`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -73,11 +73,11 @@ export class MultipartParser {
|
|||
this[k + 'Mark'] = v
|
||||
}
|
||||
|
||||
#emit(name, buff, idx, cleanup) {
|
||||
#emit(name, buf, idx, cleanup) {
|
||||
let mark = name + 'Mark'
|
||||
if (this[mark] !== void 0) {
|
||||
let start = this[mark]
|
||||
let end = buff.length
|
||||
let end = buf.length
|
||||
|
||||
if (cleanup) {
|
||||
end = idx
|
||||
|
@ -89,7 +89,7 @@ export class MultipartParser {
|
|||
if (start === end) {
|
||||
return
|
||||
}
|
||||
this['$' + name](buff.slice(start, end))
|
||||
this['$' + name](buf.slice(start, end))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,36 @@ export class OctetParser extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
export class BufferParser extends EventEmitter {
|
||||
#buf = Buffer.from('')
|
||||
#byteLen = 0
|
||||
|
||||
initLength(length) {
|
||||
this.#byteLen = length
|
||||
}
|
||||
|
||||
write(buffer) {
|
||||
this.#buf = Buffer.concat([this.#buf, buffer])
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.#buf.length === this.#byteLen) {
|
||||
this.emit('buffer', this.#buf)
|
||||
this.emit('end')
|
||||
this.#buf = null
|
||||
} else {
|
||||
this.emit(
|
||||
'error',
|
||||
new Error(
|
||||
`The uploaded data is incomplete. Expected ${
|
||||
this.#byteLen
|
||||
}, Received ${this.#buf.length} .`
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyParser extends EventEmitter {
|
||||
write() {}
|
||||
|
||||
|
|
|
@ -4,23 +4,40 @@
|
|||
* @date 2023/10/27 12:14:05
|
||||
*/
|
||||
|
||||
import { parse } from 'node:querystring'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { querystring } from './helper.js'
|
||||
|
||||
export class UrlencodedParser extends EventEmitter {
|
||||
#buff = Buffer.from('')
|
||||
#buf = Buffer.from('')
|
||||
#byteLen = 0
|
||||
|
||||
initLength(length) {
|
||||
this.#byteLen = length
|
||||
}
|
||||
|
||||
write(buffer) {
|
||||
this.#buff = Buffer.concat([this.#buff, buffer])
|
||||
this.#buf = Buffer.concat([this.#buf, buffer])
|
||||
}
|
||||
|
||||
end() {
|
||||
let data = this.#buff.toString()
|
||||
let fields = parse(data)
|
||||
if (this.#buf.length === this.#byteLen) {
|
||||
let data = this.#buf.toString()
|
||||
let fields = querystring(data)
|
||||
|
||||
this.#buff = null
|
||||
this.#buf = null
|
||||
|
||||
this.emit('field', fields)
|
||||
this.emit('end')
|
||||
this.emit('field', fields)
|
||||
this.emit('end')
|
||||
this.#buf = null
|
||||
} else {
|
||||
this.emit(
|
||||
'error',
|
||||
new Error(
|
||||
`The uploaded data is incomplete. Expected ${
|
||||
this.#byteLen
|
||||
}, Received ${this.#buf.length} .`
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@gm5/request",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.7",
|
||||
"description": "对Http的Request进一步封装, 提供常用的API",
|
||||
"main": "index.js",
|
||||
"author": "yutent",
|
||||
|
@ -15,9 +15,9 @@
|
|||
"http"
|
||||
],
|
||||
"dependencies": {
|
||||
"es.shim": "^2.0.1",
|
||||
"iofs": "^1.5.0"
|
||||
"es.shim": "^2.2.0",
|
||||
"iofs": "^1.5.3"
|
||||
},
|
||||
"repository": "https://github.com/bytedo/gmf.request.git",
|
||||
"repository": "https://git.wkit.fun/gm5/request.git",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue