-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
mod.ts
123 lines (107 loc) · 3.28 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { readAll } from 'https://deno.land/std@0.181.0/streams/read_all.ts'
import { compress as brotli } from 'https://deno.land/x/brotli@0.1.7/mod.ts'
import { Foras } from 'https://deno.land/x/foras@2.0.8/src/deno/mod.ts'
import { Accepts } from 'https://deno.land/x/accepts@2.1.1/mod.ts'
await Foras.initBundledOnce()
const funcs = {
br: brotli,
gzip: (body: Uint8Array) => Foras.gzip(body, undefined),
deflate: (body: Uint8Array) => Foras.deflate(body, undefined),
}
/**
* Supported compression algorithms
*/
type Compression = 'gzip' | 'br' | 'deflate'
export type CompressionOptions = Partial<{
/**
* Path to file
*/
path: string
/**
* Body as a byte array (as returned from Deno.readFile methods)
*/
bodyBinary: Uint8Array
/**
* Body as a string (as returned from Deno.readTextFile)
*/
bodyText: string
}>
/**
* HTTP Compression middleware.
* @param {CompressionOptions} opts
*
* @example
* ```ts
import { compression } from 'https://deno.land/x/http_compression/mod.ts'
import { Server } from 'https://deno.land/std@0.181.0/http/server.ts'
new Server({
handler: async (req) => {
return await compression({ path, compression: ['br', 'gzip', 'deflate'] })(req)
}, port: 3000
}).listenAndServe()
* ```
*/
export const compression =
(opts: CompressionOptions) => async (req: Request): Promise<Response> => {
const acceptHeader = req.headers.get('Accept-Encoding')
const accepts = new Accepts(req.headers)
const encodings = accepts.encodings()
let buf: Uint8Array
if (opts.bodyBinary) {
buf = opts.bodyBinary
} else if (opts.bodyText) {
const encoder = new TextEncoder()
buf = encoder.encode(opts.bodyText)
} else if (opts.path) {
const file = await Deno.open(opts.path)
buf = await readAll(file)
file.close()
} else {
throw Error('Must specify either bodyBinary, bodyText, or path.')
}
if (
!acceptHeader || acceptHeader === 'identity' ||
(Array.isArray(encodings) && encodings[0] === 'identity')
) {
return new Response(buf, {
status: 200,
headers: new Headers({
'Content-Encoding': 'identity',
}),
})
} else if (acceptHeader === '*') {
const compressed = funcs.gzip(buf)
return new Response(compressed, {
headers: new Headers({
'Content-Encoding': 'gzip',
}),
})
} else {
if (Array.isArray(encodings)) {
let compressed: Uint8Array = buf
const encs: string[] = []
for (let enc of encodings.filter((x) => x !== 'identity')) {
if (enc === 'brotli') enc = 'br'
if (Object.keys(funcs).includes(enc as string)) {
compressed = funcs[enc as Compression](compressed)
encs.push(enc)
}
}
return new Response(compressed, {
headers: new Headers({
'Content-Encoding': encs.join(', '),
}),
})
} else {
return Object.keys(funcs).includes(encodings as string)
? new Response(funcs[encodings as Compression](buf), {
headers: new Headers({
'Content-Encoding': encodings as string,
}),
})
: new Response('Not Acceptable', {
status: 406,
})
}
}
}