This repository has been archived by the owner on Sep 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
auto-webp.ts
105 lines (91 loc) · 2.74 KB
/
auto-webp.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
/** @module Middleware */
import { Image } from "@fly/v8env/lib/fly/image";
import responseCache from "@fly/v8env/lib/fly/cache/response";
import { FetchFunction } from "../fetch";
/**
* Automatically encodes images as webp for supported clients.
* This would apply to requests with 'image/webp' in the accept header
* and 'image/jpeg' or 'image/png' in the response type from origin.
*
* Example:
*
* ```typescript
* import { origin } from "./src/backends";
* import { autoWebp } from "./src/middleware/auto-webp";
*
* const backend = origin({
* origin: "https://fly.io/",
* headers: {host: "fly.io"}
* })
*
* const images = autoWebp(backend);
*
* fly.http.respondWith(images);
* ```
*/
export function autoWebp(fetch: FetchFunction): FetchFunction {
return async function imageConversions(req: RequestInfo, init?: RequestInit) {
if (typeof req === "string"){
req = new Request(req, init);
init = undefined;
}
// pass through if client doesn't support webp
if(!webpAllowed(req)){
return fetch(req, init);
}
// generate a cache key
const key = `webp:${req.url}`;
// get response from responseCache and serve it (if available)
let resp: Response = await responseCache.get(key)
if(resp){
resp.headers.set("Fly-Image-Cache", "HIT")
return resp
}
resp = await fetch(req, init);
// check if the req is a png or jpeg image
if(!isImage(resp)) {
return resp
}
if (req.method === "GET"){
let img = await loadImage(resp)
img = img.webp({ force: true, quality: 60 });
resp.headers.set("content-type", "image/webp");
const body = await img.toBuffer()
resp = new Response(body.data, resp)
resp.headers.set("content-length", body.data.byteLength.toString())
let etag = resp.headers.get("etag")
if(etag){
etag = etag.replace(/^("?)([^"]+)("?)$/, "$1$2-webp$3")
resp.headers.set("etag", etag)
}
// put webp in responseCache for an hour
await responseCache.set(key, resp, { tags: [req.url], ttl: 3600 })
resp.headers.set("Fly-Image-Cache", "MISS")
}
return resp
}
}
async function loadImage(resp: Response): Promise<Image> {
if (!isImage(resp)) {
throw new Error("Response wasn't an image")
}
const raw = await resp.arrayBuffer()
const img = new Image(raw)
return img
}
function isImage(resp: Response): boolean{
const contentType = resp.headers.get("Content-Type") || ""
if (contentType.includes("image/png") || contentType.includes("image/jpeg")) {
return true
}
return false
}
export function webpAllowed(req: Request){
const accept = req.headers.get("accept") || ""
if(
accept.includes("image/webp")
){
return true
}
return false
}