Skip to content

Commit

Permalink
Merge pull request #1 from fastify/init
Browse files Browse the repository at this point in the history
Initial release
  • Loading branch information
delvedor authored Nov 17, 2017
2 parents b2a4cb7 + 898647a commit c80cdfb
Show file tree
Hide file tree
Showing 6 changed files with 767 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,11 @@ typings/
# dotenv environment variables file
.env

# mac files
.DS_Store

# vim swap files
*.swp

# lock files
package-lock.json
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: node_js

node_js:
- "9"
- "8"
- "6"
- "4"

notifications:
email:
on_success: never
on_failure: always
80 changes: 79 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,79 @@
# fastify-compress
# fastify-compress

[![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/)

Adds compression utils to the Fastify `reply` object.
Support `gzip`, `deflate` and `brotli`.

## Install
```
npm i fastify-compress --save
```

## Usage
This plugins adds two functionalities to Fastify, a compress utility and a global compression hook.

Currently the following headers are supported:
- `'deflate'`
- `'gzip'`
- `'br'`

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.

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`.

### Global hook
The global compression hook is enabled by default if you want to disable it, pass the option `{ global: false }`.
```javascript
fastify.register(
require('fastify-compress'),
{ global: false }
)
```
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.

### `reply.compress`
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.

```javascript
const fs = require('fs')
const fastify = require('fastify')

fastify.register(require('fastify-compress'), { global: false })

fastify.get('/', (req, reply) => {
reply
.type('text/plain')
.compress(fs.createReadStream('./package.json'))
})

fastify.listen(3000, function (err) {
if (err) throw err
console.log(`server listening on ${fastify.server.address().port}`)
})
```
## Options
### Threshold
You can set a custom threshold below which it will not be made compression, default to `1024`.
```javascript
fastify.register(
require('fastify-compress'),
{ threshold: 2048 }
)
```
### Brotli
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.
```javascript
fastify.register(
require('fastify-compress'),
{ brotli: require('iltorb') }
)
```

## Acknowledgements
This project is kindly sponsored by:
- [LetzDoIt](http://www.letzdoitapp.com/)

## License

Licensed under [MIT](./LICENSE).
169 changes: 169 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'use strict'

const fp = require('fastify-plugin')
const zlib = require('zlib')
const pump = require('pump')
const sts = require('string-to-stream')
const mimedb = require('mime-db')
const supportedEncodings = ['deflate', 'gzip', 'br', 'identity']
const compressibleTypes = /^text\/|\+json$|\+text$|\+xml$/

function compressPlugin (fastify, opts, next) {
fastify.decorateReply('compress', compress)

if (opts.global !== false) {
fastify.addHook('onSend', onSend)
}

const threshold = typeof opts.threshold === 'number' ? opts.threshold : 1024
const compressStream = {
gzip: zlib.createGzip,
deflate: zlib.createDeflate
}

if (opts.brotli) {
compressStream.br = opts.brotli.compressStream
}

next()

function compress (payload) {
if (!payload) {
this.res.log.warn('compress: missing payload')
this.send(new Error('Internal server error'))
return
}

if (this.request.headers['x-no-compression'] !== undefined) {
return this.send(payload)
}

var type = this.res.getHeader('Content-Type') || 'application/json'
if (shouldCompress(type) === false) {
return this.send(payload)
}

var encoding = getEncodingHeader(this.request)

if (encoding === undefined) {
closeStream(payload)
this.code(400).send(new Error('Missing `accept encoding` header'))
return
}

if (encoding === 'identity') {
return this.send(payload)
}

if (encoding === null) {
closeStream(payload)
this.code(406).send(new Error('Unsupported encoding'))
return
}

if (payload._readableState === undefined) {
if (typeof payload !== 'string') {
payload = this.serialize(payload)
}
if (Buffer.byteLength(payload) < threshold) {
return this.send(payload)
}
payload = sts(payload)
}

this.header('Content-Encoding', encoding)
this.send(pump(
payload,
compressStream[encoding](),
onEnd.bind(this))
)
}

function onSend (req, reply, payload, next) {
if (!payload) {
reply.res.log.warn('compress: missing payload')
return next()
}

if (req.headers['x-no-compression'] !== undefined) {
return next()
}

var type = reply.res.getHeader('Content-Type') || 'application/json'
if (shouldCompress(type) === false) {
return next()
}

var encoding = getEncodingHeader(req)

if (encoding === undefined) {
closeStream(payload)
reply.code(400)
next(new Error('Missing `accept encoding` header'))
return
}

if (encoding === null) {
closeStream(payload)
reply.code(406)
next(new Error('Unsupported encoding'))
return
}

if (encoding === 'identity') {
return next()
}

if (payload._readableState === undefined) {
if (typeof payload !== 'string') {
payload = reply.serialize(payload)
}
if (Buffer.byteLength(payload) < threshold) {
return next()
}
payload = sts(payload)
}

reply.header('Content-Encoding', encoding)
next(null, pump(
payload,
compressStream[encoding](),
onEnd.bind(reply))
)
}
}

function onEnd (err) {
if (err) this.res.log.error(err)
}

function closeStream (payload) {
if (typeof payload.close === 'function') {
payload.close()
} else if (typeof payload.destroy === 'function') {
payload.destroy()
} else if (typeof payload.abort === 'function') {
payload.abort()
}
}

function getEncodingHeader (request) {
var header = request.headers['accept-encoding']
if (!header) return undefined
var acceptEncodings = header.split(',')
for (var i = 0; i < acceptEncodings.length; i++) {
if (supportedEncodings.indexOf(acceptEncodings[i]) > -1) {
return acceptEncodings[i]
}
}
return null
}

function shouldCompress (type) {
if (compressibleTypes.test(type)) return true
var data = mimedb[type.split(';', 1)[0].trim().toLowerCase()]
if (data === undefined) return false
return data.compressible
}

module.exports = fp(compressPlugin, '>=0.20.0')
38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "fastify-compress",
"version": "0.1.0",
"description": "Fastify compression utils",
"main": "index.js",
"dependencies": {
"fastify-plugin": "^0.1.1",
"mime-db": "^1.31.0",
"pump": "^1.0.3",
"string-to-stream": "^1.1.0"
},
"devDependencies": {
"fastify": "^0.35.0",
"iltorb": "^2.0.2",
"standard": "^10.0.3",
"tap": "^10.7.3"
},
"scripts": {
"test": "standard && tap test.js"
},
"keywords": [
"fastify",
"compression",
"deflate",
"gzip",
"brodli"
],
"author": "Tomas Della Vedova - @delvedor (http://delved.org)",
"license": "MIT",
"bugs": {
"url": "https://github.com/fastify/fastify-compress/issues"
},
"homepage": "https://github.com/fastify/fastify-compress#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/fastify/fastify-compress.git"
}
}
Loading

0 comments on commit c80cdfb

Please sign in to comment.