Skip to content

Commit 11218b1

Browse files
author
Dmitry Dutikov
committed
Middleware implemented
0 parents  commit 11218b1

18 files changed

+4997
-0
lines changed

.github/workflows/npm-publish.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3+
4+
name: Node.js Package
5+
6+
on:
7+
workflow_dispatch:
8+
release:
9+
types: [created]
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v2
16+
- uses: actions/setup-node@v2
17+
with:
18+
node-version: 16
19+
- run: npm ci
20+
- run: npm test
21+
22+
publish-npm:
23+
needs: build
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v2
27+
- uses: actions/setup-node@v2
28+
with:
29+
node-version: 16
30+
registry-url: https://registry.npmjs.org/
31+
- run: npm ci
32+
- run: npm publish
33+
env:
34+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.idea

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
test
3+
.github

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Dmitry Dutikov
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# JSON22-Express
2+
Expressjs middleware providing support to [JSON22](https://github.com/dancecoder/json22#readme) data format in your applications.
3+
4+
## Features
5+
* Ready to use [Express](https://expressjs.com/) middleware
6+
* Parse [JSON22](https://github.com/dancecoder/json22#readme) body content
7+
* Parse JSON body content
8+
* Support for `deflate`, `gzip`, `br` content encodings
9+
* Define Request.json22 method to send [JSON22](https://github.com/dancecoder/json22#readme) encoded data
10+
* Zero-dependency npm-package
11+
* Both CJS/ESM modules support
12+
13+
## Installation
14+
```
15+
npm install json22-express
16+
```
17+
In your application initialization file add as a middleware
18+
```javascript
19+
import express from 'express';
20+
import { json22express } from 'json22-express'
21+
22+
const app = express();
23+
app.use(json22express());
24+
```
25+
26+
For old-fashioned applications
27+
```javascript
28+
const express = require('express');
29+
const { json22express } = require('json22-express');
30+
31+
const app = express();
32+
app.use(json22express());
33+
```
34+
35+
## Configuration
36+
JSON22-Express middleware support set of configuration options.
37+
38+
### Pass options
39+
```javascript
40+
import express from 'express';
41+
import { json22express } from 'json22-express'
42+
43+
const app = express();
44+
app.use(json22express({
45+
overrideResponseJsonMethod: true,
46+
maxContentLength: 1024 * 1024, // 1 meg
47+
}));
48+
```
49+
50+
### Options
51+
| Option | Type | Default | Description |
52+
|:-----------|:----------|---------|:----------------------------------|
53+
| handleJson | boolean | false | Parse JSON content as well as JSON22 |
54+
| maxContentLength | number | no limit | Prevent from parsing of too large payloads |
55+
| keepRawAs | string | do not keep raw body | Define `Request` field name to save payload Buffer to |
56+
| overrideResponseJsonMethod | boolean | false | Override response json method as well |
57+
| json22ParseOptions | Json22ParseOptions | empty | Options to be passed to `JSON22.parse()` method |
58+
| json22StringifyOptions | Json22StringifyOptions | empty | Options to be passed to `JSON22.stringify()` method |
59+
60+
## Usage
61+
62+
```javascript
63+
import express from 'express';
64+
import { json22express } from 'json22-express'
65+
66+
const app = express();
67+
68+
app.use(json22express({
69+
overrideResponseJsonMethod: true,
70+
maxContentLength: 1024 * 1024, // 1 meg
71+
}));
72+
73+
app.get('/date', (req, res, next) => {
74+
// Use .json22() method from Response object
75+
res.status(200).json22({ date: new Date() });
76+
next();
77+
});
78+
79+
app.get('/json', (req, res, next) => {
80+
// in case overrideResponseJsonMethod is set to true you may use
81+
// .json() method to send JSON22 as well
82+
res.status(200).json({ date: new Date() });
83+
next();
84+
});
85+
86+
app.get('/deprecated', (req, res, next) => {
87+
// WARNING: don't do this. It is deprecated method interface and in case
88+
// you set overrideResponseJsonMethod to true this method will throw an exception
89+
res.json(200, { date: new Date() });
90+
next();
91+
});
92+
93+
94+
```
95+

index.cjs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
MIT License
3+
4+
Copyright (c) 2022 Dmitry Dutikov
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
const zlib = require('zlib');
26+
const { JSON22 } = require('json22');
27+
const { Buffer } = require('buffer');
28+
const { pipeline } = require('stream');
29+
const { MediaType } = require('./media-type');
30+
const { Json22Parser } = require('./json22-parser');
31+
32+
const JSON22_MEDIA_TYPE = MediaType.parse(JSON22.mimeType);
33+
const JSON_MEDIA_TYPE = MediaType.parse('application/json');
34+
const DEFAULT_CHARSET = 'utf-8';
35+
const SUPPORTED_CHARSETS = ['utf-8', 'utf8', 'utf-16', 'utf16', 'utf-32', 'utf32'];
36+
const SUPPORTED_ENCODINGS = ['deflate', 'gzip', 'br', 'identity'];
37+
38+
const HTTP_PAYLOAD_TOO_LARGE = 413;
39+
const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
40+
41+
const JSON22_WAS_HERE = Symbol();
42+
43+
/**
44+
* @param {Json22ExpressOptions} [options = {}]
45+
* */
46+
function json22express(options = {}) {
47+
48+
const SUPPORTED_MEDIA_TYPE = [JSON22_MEDIA_TYPE];
49+
if (options.handleJson) {
50+
SUPPORTED_MEDIA_TYPE.push(JSON_MEDIA_TYPE);
51+
}
52+
53+
/**
54+
* @param {IncomingMessage} req
55+
* @param {ServerResponse} res
56+
* @param {Function} next
57+
* */
58+
async function json22Middleware (req, res, next) {
59+
60+
if (req[JSON22_WAS_HERE] === true) {
61+
next(new Error('Duplicated json22 middleware on the pipe'));
62+
return;
63+
}
64+
65+
req[JSON22_WAS_HERE] = true;
66+
67+
res.json22 = function (obj) {
68+
const serialized = JSON22.stringify(obj, options.json22StringifyOptions);
69+
res.set('Content-Type', JSON22.mimeType);
70+
return res.send(serialized);
71+
}
72+
73+
if (options.overrideResponseJsonMethod) {
74+
res.json = function (obj) {
75+
if (arguments.length > 1) {
76+
throw new Error('Too much arguments');
77+
}
78+
return res.json22(obj);
79+
}
80+
}
81+
82+
if (req.headers['transfer-encoding'] == null && (req.headers['content-length'] == null || req.headers['content-length'] === '0')) {
83+
next();
84+
return;
85+
}
86+
87+
const contentLength = Number(req.headers['content-length']);
88+
if (options.maxContentLength != null && contentLength > options.maxContentLength) {
89+
res.status(HTTP_PAYLOAD_TOO_LARGE).send('Payload too large');
90+
return;
91+
}
92+
93+
const ct = MediaType.parse(req.headers['content-type'] ?? '');
94+
if (SUPPORTED_MEDIA_TYPE.some(mt => ct.match(mt))) {
95+
const charset = ct.parameters.charset?.toLowerCase() ?? DEFAULT_CHARSET;
96+
if (Buffer.isEncoding(charset) && SUPPORTED_CHARSETS.some(enc => enc === charset)) {
97+
const encoding = req.headers['content-encoding']?.toLowerCase() ?? 'identity';
98+
if (SUPPORTED_ENCODINGS.some(enc => enc === encoding)) {
99+
const streams = [req];
100+
switch (encoding) {
101+
case 'deflate': streams.push(zlib.createInflate()); break;
102+
case 'gzip': streams.push(zlib.createGunzip()); break;
103+
case 'br': streams.push(zlib.createBrotliDecompress()); break;
104+
case 'identity':
105+
default:
106+
break;
107+
}
108+
streams.push(new Json22Parser(req, charset, options.maxContentLength, options.keepRawAs, options.json22ParseOptions));
109+
await pipeline(...streams, err => {
110+
if (err == null) {
111+
next();
112+
} else {
113+
res.status(HTTP_UNSUPPORTED_MEDIA_TYPE).send(`Wrong content encoding: ${err.code ?? err.message}`);
114+
}
115+
});
116+
} else {
117+
res.status(HTTP_UNSUPPORTED_MEDIA_TYPE).send(`Unsupported content encoding "${encoding}"`);
118+
}
119+
} else {
120+
res.status(HTTP_UNSUPPORTED_MEDIA_TYPE).send(`Unsupported charset "${ct.parameters.charset}"`);
121+
}
122+
} else {
123+
next();
124+
}
125+
126+
}
127+
128+
return json22Middleware;
129+
}
130+
131+
module.exports = { json22express };

index.d.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
MIT License
3+
4+
Copyright (c) 2022 Dmitry Dutikov
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
import { Json22ParseOptions, Json22StringifyOptions } from 'json22';
26+
27+
export interface Json22ExpressOptions {
28+
handleJson?: boolean;
29+
maxContentLength?: number;
30+
keepRawAs?: string;
31+
overrideResponseJsonMethod?: boolean;
32+
json22ParseOptions?: Json22ParseOptions;
33+
json22StringifyOptions?: Json22StringifyOptions;
34+
}
35+
36+
export declare function json22express(options: Json22ExpressOptions): Function;

0 commit comments

Comments
 (0)