Skip to content

Commit f17aa6b

Browse files
Merge pull request #22 from tinyhttp/limits
feat: Request limits
2 parents 037ae81 + aba7e16 commit f17aa6b

File tree

13 files changed

+1164
-795
lines changed

13 files changed

+1164
-795
lines changed

.vscode/settings.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
{
2+
"npm.packageManager": "pnpm",
3+
"editor.formatOnSave": true,
24
"biome.enabled": true,
3-
"editor.defaultFormatter": "biomejs.biome",
4-
"prettier.enable": false,
55
"eslint.enable": false,
6+
"prettier.enable": false,
67
"editor.codeActionsOnSave": {
7-
"source.fixAll": "always"
8-
},
9-
"typescript.tsdk": "node_modules/typescript/lib",
10-
"[typescript]": {
11-
"editor.defaultFormatter": "biomejs.biome"
8+
"source.fixAll": "explicit",
9+
"source.organizeImports.biome": "explicit"
1210
},
13-
"[javascript]": {
14-
"editor.defaultFormatter": "biomejs.biome"
15-
}
11+
"typescript.tsdk": "node_modules/typescript/lib"
1612
}

README.md

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<img src="logo.png" width="400px" />
44
<br /><br />
55

6-
![Vulnerabilities][vulns-badge-url]
76
[![Version][v-badge-url]][npm-url] [![Coverage][cov-img]][cov-url] [![Github actions][gh-actions-img]][github-actions] [![Downloads][dl-badge-url]][npm-url]
87

98
</div>
@@ -15,8 +14,7 @@ Check out [deno-libs/parsec](https://github.com/deno-libs/parsec) for Deno port.
1514

1615
## Features
1716

18-
- ⏩ built with `async` / `await`
19-
- 🛠 JSON / raw / urlencoded data support
17+
- 🛠 JSON / raw / urlencoded / multipart support
2018
- 📦 tiny package size (8KB dist size)
2119
- 🔥 no dependencies
2220
-[tinyhttp](https://github.com/tinyhttp/tinyhttp) and Express support
@@ -43,75 +41,18 @@ import { createServer } from 'node:http'
4341
import { json } from 'milliparsec'
4442

4543
const server = createServer(async (req: ReqWithBody, res) => {
46-
await json()(req, res, (err) => void err && console.log(err))
44+
await json()(req, res, (err) => void err && res.end(err))
4745

4846
res.setHeader('Content-Type', 'application/json')
4947

5048
res.end(JSON.stringify(req.body))
5149
})
5250
```
5351

54-
### Web frameworks integration
55-
56-
#### tinyhttp
57-
58-
```ts
59-
import { App } from '@tinyhttp/app'
60-
import { urlencoded } from 'milliparsec'
61-
62-
new App()
63-
.use(urlencoded())
64-
.post('/', (req, res) => void res.send(req.body))
65-
.listen(3000, () => console.log(`Started on http://localhost:3000`))
66-
```
67-
68-
## API
69-
70-
### `raw(req, res, cb)`
71-
72-
Minimal body parsing without any formatting.
73-
74-
### `text(req, res, cb)`
75-
76-
Converts request body to string.
77-
78-
### `urlencoded(req, res, cb)`
79-
80-
Parses request body using `new URLSearchParams`.
81-
82-
### `json(req, res, cb)`
83-
84-
Parses request body using `JSON.parse`.
85-
86-
### `multipart(req, res, cb)`
87-
88-
Parses request body using `multipart/form-data` content type and boundary. Supports files as well.
89-
90-
```js
91-
// curl -F "textfield=textfield" -F "someother=textfield with text" localhost:3000
92-
await multipart()(req, res, (err) => void err && console.log(err))
93-
res.end(req.body) // { textfield: "textfield", someother: "textfield with text" }
94-
```
95-
96-
### `custom(fn)(req, res, cb)`
97-
98-
Custom function for `parsec`.
99-
100-
```js
101-
// curl -d "this text must be uppercased" localhost:3000
102-
await custom(
103-
req,
104-
(d) => d.toUpperCase(),
105-
(err) => {}
106-
)
107-
res.end(req.body) // "THIS TEXT MUST BE UPPERCASED"
108-
```
109-
11052
### What is "parsec"?
11153

11254
The parsec is a unit of length used to measure large distances to astronomical objects outside the Solar System.
11355

114-
[vulns-badge-url]: https://img.shields.io/snyk/vulnerabilities/npm/milliparsec.svg?style=for-the-badge&color=25608B&label=vulns
11556
[v-badge-url]: https://img.shields.io/npm/v/milliparsec.svg?style=for-the-badge&color=25608B&logo=npm&label=
11657
[npm-url]: https://www.npmjs.com/package/milliparsec
11758
[dl-badge-url]: https://img.shields.io/npm/dt/milliparsec?style=for-the-badge&color=25608B

bench/file.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
this is a file that is being sent in a multipart request

bench/formidable.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createReadStream } from 'node:fs'
2+
// @ts-check
3+
import { createServer } from 'node:http'
4+
import formidable from 'formidable'
5+
6+
const form = formidable({})
7+
8+
const server = createServer((req, res) => {
9+
form.parse(req, (_, fields, files) => {
10+
// @ts-expect-error this is JS
11+
const file = createReadStream(files.file[0].filepath)
12+
file.pipe(res)
13+
})
14+
})
15+
16+
server.listen(3005)

bench/index.md

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
## Benchmark
1+
# Benchmark
22

3-
Below is a comparison of body-parser and milliparsec in terms of parsing a request with JSON payload.
3+
Below are benchmarks of body-parser vs milliparsec and formidable vs milliparsec. Please take into account that these benchmarks are not entirely accurate, since they are taken on a regular desktop computer in usual conditions.
44

5-
### Environment
5+
## Environment
66

77
- Node.js 22.3.0
8-
- System: Linux 6.9.7
8+
- System: Linux 6.10.10
9+
- CPU: Intel Core i9-13900H
910
- Machine: Asus ROG Zephyrus G16
1011

12+
## JSON parsing
13+
1114
### Benchmark command:
1215

1316
```sh
@@ -22,20 +25,20 @@ body-parser result:
2225
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
2326
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
2427
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
25-
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.79 ms │ 251 ms │
28+
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.81 ms │ 258 ms │
2629
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
2730
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐
2831
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
2932
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
30-
│ Req/Sec │ 31,23131,23142,81543,93541,823.28 │ 3,470.8831,224
33+
│ Req/Sec │ 33,05533,05544,09545,05542,820.37 │ 3,265.0133,046
3134
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
32-
│ Bytes/Sec │ 4.03 MB │ 4.03 MB │ 5.52 MB │ 5.67 MB │ 5.39 MB │ 448 kB │ 4.03 MB │
35+
│ Bytes/Sec │ 4.26 MB │ 4.26 MB │ 5.69 MB │ 5.81 MB │ 5.52 MB │ 421 kB │ 4.26 MB │
3336
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘
3437
3538
Req/Bytes counts sampled once per second.
3639
# of samples: 11
3740
38-
460k requests in 11.02s, 59.3 MB read
41+
471k requests in 11.03s, 60.8 MB read
3942
```
4043

4144
milliparsec result:
@@ -44,22 +47,80 @@ milliparsec result:
4447
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
4548
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
4649
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
47-
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.65 ms │ 254 ms │
50+
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.64 ms │ 252 ms │
4851
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
49-
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────┐
50-
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
51-
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
52-
│ Req/Sec │ 52,51152,511 │ 63,007 │ 67,455 │ 63,397.82 │ 4,255.4252,480
53-
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
54-
│ Bytes/Sec │ 6.41 MB │ 6.41 MB │ 7.69 MB │ 8.23 MB │ 7.74 MB │ 519 kB │ 6.4 MB │
55-
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────┘
52+
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────
53+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min
54+
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────
55+
│ Req/Sec │ 50,75150,751 │ 63,423 │ 67,071 │ 63,610.19 │ 4,416.7250,739
56+
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────
57+
│ Bytes/Sec │ 6.19 MB │ 6.19 MB │ 7.74 MB │ 8.18 MB │ 7.76 MB │ 538 kB │ 6.19 MB │
58+
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────
5659
5760
Req/Bytes counts sampled once per second.
5861
# of samples: 11
5962
60-
697k requests in 11.02s, 85.1 MB rea
63+
700k requests in 11.02s, 85.4 MB read
64+
```
65+
66+
### Verdict
67+
68+
milliparsec, on average, is ~30-40% faster.
69+
70+
## Multipart with files
71+
72+
### Benchmark command:
73+
74+
```sh
75+
autocannon -m POST --form '{ "file": { "type": "file", "path": "./file.txt" } }' localhost:3004
76+
```
77+
78+
### Results
79+
80+
formidable result:
81+
82+
```
83+
┌─────────┬──────┬──────┬───────┬───────┬─────────┬─────────┬────────┐
84+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
85+
├─────────┼──────┼──────┼───────┼───────┼─────────┼─────────┼────────┤
86+
│ Latency │ 1 ms │ 8 ms │ 26 ms │ 32 ms │ 9.73 ms │ 8.81 ms │ 256 ms │
87+
└─────────┴──────┴──────┴───────┴───────┴─────────┴─────────┴────────┘
88+
┌───────────┬─────────┬─────────┬────────┬────────┬────────┬────────┬─────────┐
89+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
90+
├───────────┼─────────┼─────────┼────────┼────────┼────────┼────────┼─────────┤
91+
│ Req/Sec │ 420 │ 420 │ 690 │ 2,517 │ 974.7 │ 627.32 │ 420 │
92+
├───────────┼─────────┼─────────┼────────┼────────┼────────┼────────┼─────────┤
93+
│ Bytes/Sec │ 83.2 kB │ 83.2 kB │ 137 kB │ 498 kB │ 193 kB │ 124 kB │ 83.2 kB │
94+
└───────────┴─────────┴─────────┴────────┴────────┴────────┴────────┴─────────┘
95+
96+
Req/Bytes counts sampled once per second.
97+
# of samples: 10
98+
99+
10k requests in 10.03s, 1.93 MB read
100+
```
101+
102+
milliparsec result:
103+
104+
```
105+
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
106+
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
107+
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
108+
│ Latency │ 0 ms │ 0 ms │ 1 ms │ 1 ms │ 0.21 ms │ 2.15 ms │ 375 ms │
109+
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
110+
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬──────────┬────────┐
111+
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
112+
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
113+
│ Req/Sec │ 6,543 │ 6,543 │ 14,607 │ 15,455 │ 13,841 │ 2,516.57 │ 6,542 │
114+
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
115+
│ Bytes/Sec │ 1.3 MB │ 1.3 MB │ 2.89 MB │ 3.06 MB │ 2.74 MB │ 498 kB │ 1.3 MB │
116+
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴──────────┴────────┘
117+
118+
Req/Bytes counts sampled once per second.
119+
# of samples: 10
120+
121+
138k requests in 10.03s, 27.4 MB read
61122
```
62123

63-
## Verdict
124+
### Verdict
64125

65-
milliparsec, on average, is ~34% faster.
126+
milliparsec, on average, is ~1000-1200% faster.

bench/milliparsec-multipart.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @ts-check
2+
3+
import { createServer } from 'node:http'
4+
import * as bodyParser from '../dist/index.js'
5+
const mw = bodyParser.multipart()
6+
7+
const server = createServer((req, res) => {
8+
mw(req, res, () => {
9+
/**
10+
* @type {File}
11+
*/
12+
// @ts-ignore
13+
const file = req.body.file[0]
14+
const stream = file.stream()
15+
16+
// Pipe the stream to the response
17+
stream.pipeTo(
18+
new WritableStream({
19+
write(chunk) {
20+
res.write(chunk)
21+
},
22+
close() {
23+
res.end()
24+
},
25+
abort(err) {
26+
console.error('Stream error:', err)
27+
res.writeHead(500)
28+
res.end('Error streaming file')
29+
}
30+
})
31+
)
32+
})
33+
})
34+
35+
server.listen(3004)

bench/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
"description": "",
66
"main": "index.js",
77
"scripts": {
8-
"bench": "autocannon -b '{\"a\":1}' -H \"Content-Type=application/json\""
8+
"bench": "autocannon -b '{\"a\":1}' -H \"Content-Type=application/json\"",
9+
"bench:multipart": "autocannon -m POST --form '{ \"file\": { \"type\": \"file\", \"path\": \"./file.txt\" } }'"
910
},
1011
"keywords": [],
1112
"author": "",
1213
"license": "ISC",
1314
"devDependencies": {
1415
"@types/body-parser": "^1.19.5",
16+
"@types/formidable": "^3.4.5",
1517
"autocannon": "^7.15.0",
1618
"body-parser": "^1.20.2"
19+
},
20+
"dependencies": {
21+
"formidable": "^3.5.1"
1722
}
1823
}

0 commit comments

Comments
 (0)