Skip to content

Commit c80cdfb

Browse files
authored
Merge pull request #1 from fastify/init
Initial release
2 parents b2a4cb7 + 898647a commit c80cdfb

File tree

6 files changed

+767
-1
lines changed

6 files changed

+767
-1
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,11 @@ typings/
5757
# dotenv environment variables file
5858
.env
5959

60+
# mac files
61+
.DS_Store
62+
63+
# vim swap files
64+
*.swp
65+
66+
# lock files
67+
package-lock.json

.travis.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
language: node_js
2+
3+
node_js:
4+
- "9"
5+
- "8"
6+
- "6"
7+
- "4"
8+
9+
notifications:
10+
email:
11+
on_success: never
12+
on_failure: always

README.md

+79-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,79 @@
1-
# fastify-compress
1+
# fastify-compress
2+
3+
[![Build Status](https://travis-ci.org/fastify/fastify-compress.svg?branch=master)](https://travis-ci.org/fastify/fastify-compress) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
4+
5+
Adds compression utils to the Fastify `reply` object.
6+
Support `gzip`, `deflate` and `brotli`.
7+
8+
## Install
9+
```
10+
npm i fastify-compress --save
11+
```
12+
13+
## Usage
14+
This plugins adds two functionalities to Fastify, a compress utility and a global compression hook.
15+
16+
Currently the following headers are supported:
17+
- `'deflate'`
18+
- `'gzip'`
19+
- `'br'`
20+
21+
If an unsupported encoding is received, it will automatically return a `406` error, if the `'accept-encoding'` header is missing, it will return a `400` error.
22+
23+
It automatically defines if a payload should be compressed or not based on its `Content-Type`, if no content type is present, it will assume is `application/json`.
24+
25+
### Global hook
26+
The global compression hook is enabled by default if you want to disable it, pass the option `{ global: false }`.
27+
```javascript
28+
fastify.register(
29+
require('fastify-compress'),
30+
{ global: false }
31+
)
32+
```
33+
Remember that thanks to the Fastify encapsulation model, you can set a global compression, but running it only in a subset of routes is you wrap them inside a plugin.
34+
35+
### `reply.compress`
36+
This plugin add a `compress` function to `reply` that accepts a stream or a string and compress it based on the `'accept-encoding'` header. If a js object is passed in, will be stringified as json.
37+
38+
```javascript
39+
const fs = require('fs')
40+
const fastify = require('fastify')
41+
42+
fastify.register(require('fastify-compress'), { global: false })
43+
44+
fastify.get('/', (req, reply) => {
45+
reply
46+
.type('text/plain')
47+
.compress(fs.createReadStream('./package.json'))
48+
})
49+
50+
fastify.listen(3000, function (err) {
51+
if (err) throw err
52+
console.log(`server listening on ${fastify.server.address().port}`)
53+
})
54+
```
55+
## Options
56+
### Threshold
57+
You can set a custom threshold below which it will not be made compression, default to `1024`.
58+
```javascript
59+
fastify.register(
60+
require('fastify-compress'),
61+
{ threshold: 2048 }
62+
)
63+
```
64+
### Brotli
65+
Brotli compression is not enabled by default, if you need it we recommend to install [`iltorb`](https://www.npmjs.com/package/iltorb) and pass it as option.
66+
```javascript
67+
fastify.register(
68+
require('fastify-compress'),
69+
{ brotli: require('iltorb') }
70+
)
71+
```
72+
73+
## Acknowledgements
74+
This project is kindly sponsored by:
75+
- [LetzDoIt](http://www.letzdoitapp.com/)
76+
77+
## License
78+
79+
Licensed under [MIT](./LICENSE).

index.js

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
'use strict'
2+
3+
const fp = require('fastify-plugin')
4+
const zlib = require('zlib')
5+
const pump = require('pump')
6+
const sts = require('string-to-stream')
7+
const mimedb = require('mime-db')
8+
const supportedEncodings = ['deflate', 'gzip', 'br', 'identity']
9+
const compressibleTypes = /^text\/|\+json$|\+text$|\+xml$/
10+
11+
function compressPlugin (fastify, opts, next) {
12+
fastify.decorateReply('compress', compress)
13+
14+
if (opts.global !== false) {
15+
fastify.addHook('onSend', onSend)
16+
}
17+
18+
const threshold = typeof opts.threshold === 'number' ? opts.threshold : 1024
19+
const compressStream = {
20+
gzip: zlib.createGzip,
21+
deflate: zlib.createDeflate
22+
}
23+
24+
if (opts.brotli) {
25+
compressStream.br = opts.brotli.compressStream
26+
}
27+
28+
next()
29+
30+
function compress (payload) {
31+
if (!payload) {
32+
this.res.log.warn('compress: missing payload')
33+
this.send(new Error('Internal server error'))
34+
return
35+
}
36+
37+
if (this.request.headers['x-no-compression'] !== undefined) {
38+
return this.send(payload)
39+
}
40+
41+
var type = this.res.getHeader('Content-Type') || 'application/json'
42+
if (shouldCompress(type) === false) {
43+
return this.send(payload)
44+
}
45+
46+
var encoding = getEncodingHeader(this.request)
47+
48+
if (encoding === undefined) {
49+
closeStream(payload)
50+
this.code(400).send(new Error('Missing `accept encoding` header'))
51+
return
52+
}
53+
54+
if (encoding === 'identity') {
55+
return this.send(payload)
56+
}
57+
58+
if (encoding === null) {
59+
closeStream(payload)
60+
this.code(406).send(new Error('Unsupported encoding'))
61+
return
62+
}
63+
64+
if (payload._readableState === undefined) {
65+
if (typeof payload !== 'string') {
66+
payload = this.serialize(payload)
67+
}
68+
if (Buffer.byteLength(payload) < threshold) {
69+
return this.send(payload)
70+
}
71+
payload = sts(payload)
72+
}
73+
74+
this.header('Content-Encoding', encoding)
75+
this.send(pump(
76+
payload,
77+
compressStream[encoding](),
78+
onEnd.bind(this))
79+
)
80+
}
81+
82+
function onSend (req, reply, payload, next) {
83+
if (!payload) {
84+
reply.res.log.warn('compress: missing payload')
85+
return next()
86+
}
87+
88+
if (req.headers['x-no-compression'] !== undefined) {
89+
return next()
90+
}
91+
92+
var type = reply.res.getHeader('Content-Type') || 'application/json'
93+
if (shouldCompress(type) === false) {
94+
return next()
95+
}
96+
97+
var encoding = getEncodingHeader(req)
98+
99+
if (encoding === undefined) {
100+
closeStream(payload)
101+
reply.code(400)
102+
next(new Error('Missing `accept encoding` header'))
103+
return
104+
}
105+
106+
if (encoding === null) {
107+
closeStream(payload)
108+
reply.code(406)
109+
next(new Error('Unsupported encoding'))
110+
return
111+
}
112+
113+
if (encoding === 'identity') {
114+
return next()
115+
}
116+
117+
if (payload._readableState === undefined) {
118+
if (typeof payload !== 'string') {
119+
payload = reply.serialize(payload)
120+
}
121+
if (Buffer.byteLength(payload) < threshold) {
122+
return next()
123+
}
124+
payload = sts(payload)
125+
}
126+
127+
reply.header('Content-Encoding', encoding)
128+
next(null, pump(
129+
payload,
130+
compressStream[encoding](),
131+
onEnd.bind(reply))
132+
)
133+
}
134+
}
135+
136+
function onEnd (err) {
137+
if (err) this.res.log.error(err)
138+
}
139+
140+
function closeStream (payload) {
141+
if (typeof payload.close === 'function') {
142+
payload.close()
143+
} else if (typeof payload.destroy === 'function') {
144+
payload.destroy()
145+
} else if (typeof payload.abort === 'function') {
146+
payload.abort()
147+
}
148+
}
149+
150+
function getEncodingHeader (request) {
151+
var header = request.headers['accept-encoding']
152+
if (!header) return undefined
153+
var acceptEncodings = header.split(',')
154+
for (var i = 0; i < acceptEncodings.length; i++) {
155+
if (supportedEncodings.indexOf(acceptEncodings[i]) > -1) {
156+
return acceptEncodings[i]
157+
}
158+
}
159+
return null
160+
}
161+
162+
function shouldCompress (type) {
163+
if (compressibleTypes.test(type)) return true
164+
var data = mimedb[type.split(';', 1)[0].trim().toLowerCase()]
165+
if (data === undefined) return false
166+
return data.compressible
167+
}
168+
169+
module.exports = fp(compressPlugin, '>=0.20.0')

package.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "fastify-compress",
3+
"version": "0.1.0",
4+
"description": "Fastify compression utils",
5+
"main": "index.js",
6+
"dependencies": {
7+
"fastify-plugin": "^0.1.1",
8+
"mime-db": "^1.31.0",
9+
"pump": "^1.0.3",
10+
"string-to-stream": "^1.1.0"
11+
},
12+
"devDependencies": {
13+
"fastify": "^0.35.0",
14+
"iltorb": "^2.0.2",
15+
"standard": "^10.0.3",
16+
"tap": "^10.7.3"
17+
},
18+
"scripts": {
19+
"test": "standard && tap test.js"
20+
},
21+
"keywords": [
22+
"fastify",
23+
"compression",
24+
"deflate",
25+
"gzip",
26+
"brodli"
27+
],
28+
"author": "Tomas Della Vedova - @delvedor (http://delved.org)",
29+
"license": "MIT",
30+
"bugs": {
31+
"url": "https://github.com/fastify/fastify-compress/issues"
32+
},
33+
"homepage": "https://github.com/fastify/fastify-compress#readme",
34+
"repository": {
35+
"type": "git",
36+
"url": "git+https://github.com/fastify/fastify-compress.git"
37+
}
38+
}

0 commit comments

Comments
 (0)