Skip to content

Instantly share code, notes, and snippets.

@olragon
Last active August 17, 2023 01:32
Show Gist options
  • Save olragon/011b3a45f63924e267aa50f287b394a4 to your computer and use it in GitHub Desktop.
Save olragon/011b3a45f63924e267aa50f287b394a4 to your computer and use it in GitHub Desktop.
Elysia's plugin for response compression, support gzip & deflare
// fork of elysia-compression
// changes:
// 1/ use gzip, deflate from node:zlib,
// 2/ use async version,
// 3/ check accept-encoding header,
// 4/ prevent unexpected download because of application/octet-stream
import {Context, Elysia} from "elysia";
import {gzip, deflate, ZlibOptions} from "zlib";
export interface CompressionOptions {
type: 'gzip' | 'deflate' // default gzip
force?: boolean
options?: ZlibOptions
}
export const compression =
(options: CompressionOptions = { type: 'gzip' }) =>
(app: Elysia) => {
return app.onAfterHandle(async (context, response) => {
return handleCompression(options)(context, response);
});
}
export async function toBuffer(res: unknown) {
if (res instanceof Buffer) {
return res;
}
if (res instanceof Blob || res instanceof Response) {
return new Buffer(await res.arrayBuffer());
}
return Buffer.from(
(typeof res === 'object'
? JSON.stringify(res)
: res?.toString() ?? String(res))
);
}
export function getCompressors() {
return {
async gzip(buffer: Buffer, options?: ZlibOptions): Promise<Buffer> {
return new Promise((resolve, reject) => {
gzip(buffer, {...options}, (error, result) => {
if (error) return reject(error);
resolve(result);
});
});
},
async deflate(buffer: Buffer, options?: ZlibOptions): Promise<Buffer> {
return new Promise((resolve, reject) => {
deflate(buffer, {...options}, (error, result) => {
if (error) return reject(error);
resolve(result);
});
});
}
};
}
export function handleCompression({ type = 'gzip', force = false, options }: CompressionOptions) {
const compressors = getCompressors();
if (!compressors[type]) {
throw new Error(`Unknown compression type: ${type}`);
}
return async function onAfterHandle<C extends Context, R extends unknown>({ set, request }: C, response: R) {
// do not compress if response is empty, eg: redirect
if (!response) {
return response;
}
// do not compress if client does not support
if (!force && !request.headers.get('accept-encoding')?.includes(type)) {
return response;
}
// keep original content-type otherwise it will be application/octet-stream
const contentType = (response instanceof Response
? response.headers.get('Content-Type')
: set.headers['Content-Type']) || 'text/plain';
const buffer = await toBuffer(response);
const compressed = await compressors[type](buffer, options);
set.headers['Content-Encoding'] = type;
const resp = new Response(compressed, set);
// set content-type to original value
resp.headers.set('Content-Type', contentType);
return resp;
}
}
import { Elysia } from "elysia";
import { compression } from "./elysia-compression"
const app = new Elysia()
.use(compression())
.get('/', async () => "Compressed!")
.listen(process.env.PORT || process.env.APP_PORT || 3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment