Skip to content

Commit

Permalink
Merge pull request #22 from tinyhttp/limits
Browse files Browse the repository at this point in the history
feat: Request limits
  • Loading branch information
talentlessguy authored Oct 3, 2024
2 parents 037ae81 + aba7e16 commit f17aa6b
Show file tree
Hide file tree
Showing 13 changed files with 1,164 additions and 795 deletions.
16 changes: 6 additions & 10 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
{
"npm.packageManager": "pnpm",
"editor.formatOnSave": true,
"biome.enabled": true,
"editor.defaultFormatter": "biomejs.biome",
"prettier.enable": false,
"eslint.enable": false,
"prettier.enable": false,
"editor.codeActionsOnSave": {
"source.fixAll": "always"
},
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
"source.fixAll": "explicit",
"source.organizeImports.biome": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
}
"typescript.tsdk": "node_modules/typescript/lib"
}
63 changes: 2 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<img src="logo.png" width="400px" />
<br /><br />

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

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

## Features

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

const server = createServer(async (req: ReqWithBody, res) => {
await json()(req, res, (err) => void err && console.log(err))
await json()(req, res, (err) => void err && res.end(err))

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

res.end(JSON.stringify(req.body))
})
```

### Web frameworks integration

#### tinyhttp

```ts
import { App } from '@tinyhttp/app'
import { urlencoded } from 'milliparsec'

new App()
.use(urlencoded())
.post('/', (req, res) => void res.send(req.body))
.listen(3000, () => console.log(`Started on http://localhost:3000`))
```

## API

### `raw(req, res, cb)`

Minimal body parsing without any formatting.

### `text(req, res, cb)`

Converts request body to string.

### `urlencoded(req, res, cb)`

Parses request body using `new URLSearchParams`.

### `json(req, res, cb)`

Parses request body using `JSON.parse`.

### `multipart(req, res, cb)`

Parses request body using `multipart/form-data` content type and boundary. Supports files as well.

```js
// curl -F "textfield=textfield" -F "someother=textfield with text" localhost:3000
await multipart()(req, res, (err) => void err && console.log(err))
res.end(req.body) // { textfield: "textfield", someother: "textfield with text" }
```

### `custom(fn)(req, res, cb)`

Custom function for `parsec`.

```js
// curl -d "this text must be uppercased" localhost:3000
await custom(
req,
(d) => d.toUpperCase(),
(err) => {}
)
res.end(req.body) // "THIS TEXT MUST BE UPPERCASED"
```

### What is "parsec"?

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

[vulns-badge-url]: https://img.shields.io/snyk/vulnerabilities/npm/milliparsec.svg?style=for-the-badge&color=25608B&label=vulns
[v-badge-url]: https://img.shields.io/npm/v/milliparsec.svg?style=for-the-badge&color=25608B&logo=npm&label=
[npm-url]: https://www.npmjs.com/package/milliparsec
[dl-badge-url]: https://img.shields.io/npm/dt/milliparsec?style=for-the-badge&color=25608B
Expand Down
1 change: 1 addition & 0 deletions bench/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this is a file that is being sent in a multipart request
16 changes: 16 additions & 0 deletions bench/formidable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createReadStream } from 'node:fs'
// @ts-check
import { createServer } from 'node:http'
import formidable from 'formidable'

const form = formidable({})

const server = createServer((req, res) => {
form.parse(req, (_, fields, files) => {
// @ts-expect-error this is JS
const file = createReadStream(files.file[0].filepath)
file.pipe(res)
})
})

server.listen(3005)
99 changes: 80 additions & 19 deletions bench/index.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
## Benchmark
# Benchmark

Below is a comparison of body-parser and milliparsec in terms of parsing a request with JSON payload.
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.

### Environment
## Environment

- Node.js 22.3.0
- System: Linux 6.9.7
- System: Linux 6.10.10
- CPU: Intel Core i9-13900H
- Machine: Asus ROG Zephyrus G16

## JSON parsing

### Benchmark command:

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

milliparsec result:
Expand All @@ -44,22 +47,80 @@ milliparsec result:
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.65 ms │ 254 ms │
│ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.64 ms │ 252 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Req/Sec │ 52,51152,511 │ 63,007 │ 67,455 │ 63,397.82 │ 4,255.4252,480
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Bytes/Sec │ 6.41 MB │ 6.41 MB │ 7.69 MB │ 8.23 MB │ 7.74 MB │ 519 kB │ 6.4 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────
│ Req/Sec │ 50,75150,751 │ 63,423 │ 67,071 │ 63,610.19 │ 4,416.7250,739
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────
│ Bytes/Sec │ 6.19 MB │ 6.19 MB │ 7.74 MB │ 8.18 MB │ 7.76 MB │ 538 kB │ 6.19 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────
Req/Bytes counts sampled once per second.
# of samples: 11
697k requests in 11.02s, 85.1 MB rea
700k requests in 11.02s, 85.4 MB read
```

### Verdict

milliparsec, on average, is ~30-40% faster.

## Multipart with files

### Benchmark command:

```sh
autocannon -m POST --form '{ "file": { "type": "file", "path": "./file.txt" } }' localhost:3004
```

### Results

formidable result:

```
┌─────────┬──────┬──────┬───────┬───────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼───────┼─────────┼─────────┼────────┤
│ Latency │ 1 ms │ 8 ms │ 26 ms │ 32 ms │ 9.73 ms │ 8.81 ms │ 256 ms │
└─────────┴──────┴──────┴───────┴───────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬────────┬────────┬────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼────────┼────────┼────────┼────────┼─────────┤
│ Req/Sec │ 420 │ 420 │ 690 │ 2,517 │ 974.7 │ 627.32 │ 420 │
├───────────┼─────────┼─────────┼────────┼────────┼────────┼────────┼─────────┤
│ Bytes/Sec │ 83.2 kB │ 83.2 kB │ 137 kB │ 498 kB │ 193 kB │ 124 kB │ 83.2 kB │
└───────────┴─────────┴─────────┴────────┴────────┴────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 10
10k requests in 10.03s, 1.93 MB read
```

milliparsec result:

```
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms │ 1 ms │ 0.21 ms │ 2.15 ms │ 375 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬──────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Req/Sec │ 6,543 │ 6,543 │ 14,607 │ 15,455 │ 13,841 │ 2,516.57 │ 6,542 │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Bytes/Sec │ 1.3 MB │ 1.3 MB │ 2.89 MB │ 3.06 MB │ 2.74 MB │ 498 kB │ 1.3 MB │
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴──────────┴────────┘
Req/Bytes counts sampled once per second.
# of samples: 10
138k requests in 10.03s, 27.4 MB read
```

## Verdict
### Verdict

milliparsec, on average, is ~34% faster.
milliparsec, on average, is ~1000-1200% faster.
35 changes: 35 additions & 0 deletions bench/milliparsec-multipart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @ts-check

import { createServer } from 'node:http'
import * as bodyParser from '../dist/index.js'
const mw = bodyParser.multipart()

const server = createServer((req, res) => {
mw(req, res, () => {
/**
* @type {File}
*/
// @ts-ignore
const file = req.body.file[0]
const stream = file.stream()

// Pipe the stream to the response
stream.pipeTo(
new WritableStream({
write(chunk) {
res.write(chunk)
},
close() {
res.end()
},
abort(err) {
console.error('Stream error:', err)
res.writeHead(500)
res.end('Error streaming file')
}
})
)
})
})

server.listen(3004)
7 changes: 6 additions & 1 deletion bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
"description": "",
"main": "index.js",
"scripts": {
"bench": "autocannon -b '{\"a\":1}' -H \"Content-Type=application/json\""
"bench": "autocannon -b '{\"a\":1}' -H \"Content-Type=application/json\"",
"bench:multipart": "autocannon -m POST --form '{ \"file\": { \"type\": \"file\", \"path\": \"./file.txt\" } }'"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/body-parser": "^1.19.5",
"@types/formidable": "^3.4.5",
"autocannon": "^7.15.0",
"body-parser": "^1.20.2"
},
"dependencies": {
"formidable": "^3.5.1"
}
}
Loading

0 comments on commit f17aa6b

Please sign in to comment.