Skip to content

Commit 1b1d60f

Browse files
author
v1rtl
committed
write the library
1 parent d12945b commit 1b1d60f

File tree

3 files changed

+155
-2
lines changed

3 files changed

+155
-2
lines changed

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,27 @@
1-
# compress
2-
🗜️ Deno compression middleware
1+
# compression
2+
3+
Deno HTTP compression middleware.
4+
5+
## Features
6+
7+
- Detects supported encodings with `Accept-Encoding` header
8+
- Supports chaining algorithms (e.g. `gzip` -> `br`)
9+
- Creates a `Content-Encoding` header with applied compression
10+
- Send `409 Not Acceptable` if encoding is not supported
11+
12+
## Example
13+
14+
```ts
15+
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
16+
import { compression } from 'https://deno.land/x/compression/mod.ts'
17+
18+
const s = serve({ port: 3000 })
19+
20+
for await (const req of s) {
21+
await compression({
22+
path: 'README.md',
23+
// Apply all algos in a queue
24+
compression: ['gzip', 'deflate']
25+
})(req)
26+
}
27+
```

example.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
2+
import { compression } from './mod.ts'
3+
4+
const s = serve({ port: 3000 })
5+
6+
for await (const req of s) {
7+
await compression({
8+
path: 'README.md',
9+
compression: ['gzip', 'deflate']
10+
})(req)
11+
}

mod.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// import { compress as brotli } from 'https://deno.land/x/[email protected]/mod.ts'
2+
import { gzip, deflate } from 'https://deno.land/x/[email protected]/mod.ts'
3+
import { ServerRequest } from 'https://deno.land/[email protected]/http/server.ts'
4+
import { Accepts } from 'https://deno.land/x/[email protected]/mod.ts'
5+
6+
const funcs = {
7+
// br: brotli,
8+
gzip: (body: Uint8Array) => gzip(body, undefined),
9+
deflate: (body: Uint8Array) => deflate(body, undefined)
10+
}
11+
12+
/**
13+
* Supported compression algorithms
14+
*/
15+
type Compression = 'gzip' | /* 'br' | */ 'deflate'
16+
17+
export type CompressionOptions = {
18+
/**
19+
* Path to file
20+
*/
21+
path: string
22+
/**
23+
* Compression algorithms (gzip, brotli, deflate). The first is used if all are accepted by the client
24+
*/
25+
compression: [Compression] | [Compression, Compression] | [Compression, Compression, Compression]
26+
}
27+
28+
/**
29+
* HTTP Compression middleware.
30+
* @param {CompressionOptions} opts
31+
*
32+
* @example
33+
* ```ts
34+
*import { serve } from 'https://deno.land/[email protected]/http/server.ts'
35+
*import { compression } from 'https://deno.land/x/compression/brotli.ts'
36+
*
37+
*const s = serve({ port: 3000 })
38+
*
39+
*for await (const req of s) {
40+
* await compression({
41+
* path: 'README.md',
42+
* compression: ['gzip', 'deflate']
43+
* })(req)
44+
*}
45+
* ```
46+
*/
47+
export const compression = (opts: CompressionOptions) => async (req: ServerRequest) => {
48+
const acceptHeader = req.headers.get('Accept-Encoding')
49+
50+
const accepts = new Accepts(req.headers)
51+
52+
const encodings = accepts.encodings()
53+
54+
const buf = await Deno.readAll(await Deno.open(opts.path))
55+
56+
if (
57+
!acceptHeader ||
58+
encodings === 'identity' ||
59+
(Array.isArray(encodings) && encodings.length === 1 && encodings[0] === 'identity')
60+
) {
61+
await req.respond({
62+
body: buf,
63+
status: 200,
64+
headers: new Headers({
65+
'Content-Encoding': 'identity'
66+
})
67+
})
68+
} else if (acceptHeader === '*') {
69+
const preferredAlgo = opts.compression[0]
70+
71+
const compressed = funcs[preferredAlgo](buf)
72+
73+
await req.respond({
74+
body: compressed,
75+
headers: new Headers({
76+
'Content-Encoding': preferredAlgo
77+
}),
78+
status: 200
79+
})
80+
} else {
81+
if (Array.isArray(encodings)) {
82+
let compressed: Uint8Array = buf
83+
let encs: string[] = []
84+
for (const enc of encodings.filter((x) => x !== 'identity')) {
85+
if (Object.keys(funcs).includes(enc as string)) {
86+
compressed = funcs[enc as Compression](compressed)
87+
encs.push(enc)
88+
} else {
89+
return await req.respond({
90+
status: 406,
91+
body: 'Not Acceptable'
92+
})
93+
}
94+
}
95+
return await req.respond({
96+
body: compressed,
97+
headers: new Headers({
98+
'Content-Encoding': encs.join(', ')
99+
})
100+
})
101+
} else {
102+
return await req.respond(
103+
Object.keys(funcs).includes(encodings as string)
104+
? {
105+
body: funcs[encodings as Compression](buf),
106+
headers: new Headers({
107+
'Content-Encoding': encodings as string
108+
})
109+
}
110+
: {
111+
status: 406,
112+
body: 'Not Acceptable'
113+
}
114+
)
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)