如何使用Docker使用imgproxy提供下一代图像
作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。
介绍
Web 开发人员最常用的 图像格式 是 JPEG 和 PNG。 .jpg
文件最适合用于照片,而 .png
文件最适合用于具有类似颜色的大块的图像,例如徽标或图表。 然而,这些格式分别于 1992 年和 1996 年首次开发。 近年来,视频压缩技术的进步导致了两种新的主要网络图像格式的发展:webp 和 avif。 这些格式被称为“下一代”(或“下一代”)图像格式,可以免费使用,导致图像显着缩小(缩小 20% 到 50%),并得到 Web 浏览器的广泛支持。 由于 webp
和 avif
会生成更小的图像,因此以这些新格式提供图像更高效、更快。
但是,使用“下一代”格式存在一些挑战。 首先,如果您希望在旧浏览器上为用户提供最大的兼容性,您可能需要根据需要使用 png
和 jpg
。 其次,如果您有大量上传的图片库,则转换它们会很慢(而且成本很高)。
这是 图像代理 可以提供帮助的地方。 图像代理是一个应用程序,它将根据 Web 请求即时生成或检索自定义图像。 它们可用于裁剪或调整图像大小、添加水印或图像效果以及更改格式。 使用图像代理,您可以向最终用户提供看起来相同但效率更高的图像。
一种可用的代理称为 imgproxy
。 它由 Evil Martians 提供支持,并且可以作为带有商业“Pro”扩展的自托管免费使用版本,如果您需要它们。 imgproxy
是用 Go 编写的,并使用 libvips 为其图像转换提供动力。 libvips
是可用的最快和最有效的图像处理库之一,非常适合根据用户的要求生成图像。
在本教程中,您将设置 imgproxy
并生成代理 URL 以修改图像。 您还将配置 imgproxy
以使用签名 URL 安全地提供图像,并在支持的情况下自动切换到“下一代”图像格式。
先决条件
要遵循本教程,您将需要:
- 安装了 Docker 的服务器或开发机器。 对于 Ubuntu 20.04,您可以按照指南 如何在 Ubuntu 20.04 上安装和使用 Docker(或选择其他版本或发行版)。 对于 macOS 或 Windows,请参阅 Docker Desktop 的产品文档。
- 用于运行示例脚本的脚本语言。 在本教程中,我们将使用 Ruby,您可以按照指南在 Ubuntu 上安装它,如何在 Ubuntu 20.04 上使用 rbenv 安装 Ruby on Rails。 在 macOS 上,您可以使用内置版本。
- 熟悉浏览器开发工具会很有用。 对于 Chrome,请查看 官方文档 或教程 如何使用 Chrome 工具查找性能瓶颈。
第 1 步 — 使用 Docker 安装 imgproxy
在此步骤中,您将安装 imgproxy
并检查它是否正常运行。 安装 imgproxy
有几种不同的方法,但在本教程中,您将使用 Docker,因为它是最便携的方法,并且在迁移到生产环境时意味着最小的变化.
在安装了 Docker 的服务器或开发机器上,运行以下命令来检查 Docker 是否正在运行:
docker info
输出的开头将类似于以下内容(如果您的容器或图像数量略有不同,请不要担心):
OutputClient: Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc., v0.7.1) compose: Docker Compose (Docker Inc., v2.2.1) scan: Docker Scan (Docker Inc., v0.14.0) Server: Containers: 15 Running: 0 Paused: 0 Stopped: 15 Images: 47 Server Version: 20.10.11 ...
确认 Docker 正在运行后,您可以使用 pull
子命令从 Dockerhub 下载最新版本的 imgproxy
映像:
docker pull darthsim/imgproxy:latest
在下载图像时,您将看到一组进度条。
接下来,您将使用 run
子命令启动容器实例:
docker run -p 8080:8080 -it darthsim/imgproxy
-p
选项告诉 Docker 将容器的端口 8080
映射到您机器上的端口 8080
。 --it
选项告诉 Docker 你想要一个交互式终端(在这种情况下,你会想要查看 imgproxy
输出的日志)。 最后,darthsim/imgproxy
是你之前下载并想运行的镜像名称。
输出将类似于以下内容:
WARNING [2021-12-24T03:21:18Z] No keys defined, so signature checking is disabled WARNING [2021-12-24T03:21:18Z] No salts defined, so signature checking is disabled INFO [2021-12-24T03:21:18Z] Starting server at :8080
不要担心警告; 您将在稍后的步骤中修复这些问题。
在此步骤中,您安装了 impgproxy
并确认它正在运行。 在下一步中,您将使用 imgproxy
URL 请求修改后的图像。
第 2 步 — 创建 imgproxy
URL 以修改图像
在此步骤中,您将创建可用于请求修改图像的 imgproxy
URL。
使用 imgproxy
时,网站的最终用户向 imgproxy
发出 HTTP 请求。 然后,imgproxy
请求获取 source 图像。 源图像可以是我们的代理可以访问的 Internet 上的任何位置。 (您将在后面的步骤中确保这一点。)一旦获得源图像,imgproxy
根据您的要求修改图像,然后将该图像发送给最终用户。
您可以通过在 URL 参数中指定它们来告诉 imgproxy
您想要的修改类型。 imgproxy
URL 使用以下格式:
http://your-imgproxy-host/%signature/%processing_options/%encoded_source_url.%extension
此 URL 中的 %signature
是可选的,您将在后面的步骤中配置它。 %processing_options
是您告诉 imgproxy
对图像进行裁剪或加水印之类的操作 - 或者如果您不想修改图像,可以完全忽略它。 %encoded_source_url
指向 source 图像,即您要求 imgproxy
修改的图像。 您需要对其进行编码,以便在您将其嵌入另一个 URL 时它仍然有效。 最后,可选的 %extension
参数允许您指定 imgproxy
使用通用文件扩展名转换为的图像文件类型,例如 .jpg
。
生成代理 URL 后,您可以将 img
标记的 src
属性替换为新的代理 URL。 例如,这是一个典型的图片 URL:
<img src="https://yourserver.com/assets/image.png" />
使用 imgproxy
,您可以使用以下内容替换该 URL:
<img src="https://imgproxy.yourserver.com/your imgproxy url here />
当需要修改您的网站时,这将很有效。 但是,在学习使用 imgproxy
时,查看结果的最快方法是在您喜欢的 Web 浏览器的地址栏中输入 URL,它将立即呈现结果。 接下来,您将运行 imgproxy
URL 的不同部分并使用浏览器访问它。
因为您在 Docker 中启动了 imgproxy
,所以您的服务器在 localhost
上运行并侦听端口 8080
。 这使您的 imgproxy
主机:
http://localhost:8080
注意: 如果您使用远程服务器而不是本地计算机来运行本教程,请将以下示例中的“localhost”替换为您的服务器的 IP 地址。 如果您的 URL 正确,您将在访问 http://your-server-ip:8080
时看到 嘿,我是 imgproxy 消息。
对于本教程,您可以使用在以下链接中找到的小狗图片:https://i.imgur.com/KSLD4VV.jpeg
。 (此图像最初来自 Unsplash。)或者,您可以使用互联网上可访问的任何图像。 对于本教程,请确保使用照片(jpg
图像)而不是插图或徽标。
创建代理 URL 的第一步是对源 URL 进行编码。 这意味着您将 URL 中所有有意义的特殊字符(例如,://
、/
、.
)替换为没有意义的安全字符。 这样,当您将源图像 URL 嵌入代理 URL 时,它仍然有效,并且客户端可以将其与代理 URL 分开。
imgproxy
使用 URL 安全的 Base64 编码。 您可能有一个最喜欢的编程语言可以用于此,或者在线 Base64 编码工具 可以解决问题(请务必选中标有“执行 URL 安全编码(使用 Base64URL 格式)”的框)。
注意: 您可以使用纯 URL 代替带有 /plain
选项的 Base64 编码,但这意味着您需要对任何查询参数进行编码。 一般来说,Base64 编码不易出错。
使用 URL-Safe Base64,样本小狗图片编码为 aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
。 您现在可以使用它来构建您的 imgproxy
URL,并将其输入到您的网络浏览器中,如下所示:
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
注意: 如果您使用不同的图像进行测试,请将此 URL 的最后一部分替换为您的 Base64 编码的 URL。
加载后,您将看到未经修改的小狗图像。
注意: 如果看不到图像,则会显示错误消息,或者您可以检查步骤 1 中 docker run
命令的输出。 它将显示 imgproxy
日志,包括导致错误的任何请求。
接下来,您将使用 URL 的 .extension
部分更改图像格式。 在浏览器中打开以下内容:
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.png
这将需要更长的时间来加载,如果您查看浏览器开发者工具中的 Network 选项卡,您会发现此图像比原始图像大得多 - 在示例小狗图像的情况下, png
版本为 15.84MB,而原始 jpg
版本仅为 1.3MB。 当客户端请求图像时,imgproxy
自动将其从 jpg
修改为 png
。
您还可以将此图像转换为更有效的“下一代”格式,webp
或 avif
,方法是添加其中一种格式作为说明符,如下所示:
http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.webp
测试图像的 webp
版本约为 600KB,与原始的 .jpg
相比增加了大约 53% d。 如您所见,使用这种图像格式可以加快对图像的请求。
您还可以更改图像大小或添加不同的效果。 为此,您可以将不同的修改指定为 %processing_options
参数。 在这种情况下,您将要求 imgproxy
使用 fill
的 resizing_type
使用 size
将图像大小调整为 200 像素的宽度和 500 像素的高度(保持图像 纵横比 并在其之外裁剪部分图像),然后使用 5 的掩码添加 高斯模糊 。
通过在浏览器中访问以下 URL 来尝试一下:
http://localhost:8080/sig/size:200:500/resizing_type:fill/blur:5/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
如果您在浏览器中加载该 URL,您将看到测试图像的模糊和调整大小版本。
除了调整大小和模糊之外,您还可以使用许多不同的转换。 有关更多选项,请查看 imgproxy
文档的 生成 URL 部分 。
在此步骤中,您生成了 imgproxy
URL 以提供和修改源图像。 接下来,您将保护 imgproxy
以便它只提供来自您使用 %signature
URL 参数特别允许的 URL 的源图像。
第 3 步 — 使用签名保护 imgproxy
到目前为止,您一直在 imgproxy
URL 的“签名”部分使用占位符。 但是,对您正在使用的 URL 进行 签名 很重要。 这意味着 imgproxy
将处理的唯一 URL 是您使用密钥/盐对创建的 URL。 这可以防止任何恶意使用您的服务器资源。 在此步骤中,您将配置 imgproxy
以要求签名并创建一个有效的签名以在您的 URL 中使用。
imgproxy
是一个 12-factor 应用程序,这意味着您可以使用环境变量对其进行配置,而无需修改任何文件。 这在 Docker 中运行时效果很好,因为 Docker 提供了 --env
标志来设置环境变量。
要创建签名,您首先需要一个密钥/盐对,它可以是任何随机的十六进制字符串。 您可以使用计算机上的随机数据或在线随机生成器工具生成它们。 确保指定 64 位数字,并生成至少两个字符串——一个用于键,一个用于盐。
生成两个 64 位的十六进制字符串,如下所示。
Example Random Hex49d5e2cd30d80fccc2e30877e4e58b2f0854a8dca6fb2e980b129171910080ed7ffa5dfbfde006e0c1a8ff52e7b5c614f0d3e9ec6e6ed754399fb0e2eb473c59
(请注意,您的字符串将与上述不同。)记录生成的字符串以在下一个命令中使用。
接下来,使用您在步骤 1 中使用的相同命令重新启动 Docker,但这次添加两个 --env
选项以将 IMGPROXY_KEY
和 IMGPROXY_SALT
环境变量设置为随机生成的值正在运行的容器。 用您之前生成的字符串替换突出显示的部分。
docker run --env IMGPROXY_KEY=first-hex-result --env IMGPROXY_SALT=second-hex-result -p 8080:8080 -it darthsim/imgproxy
现在,如果您在 Web 浏览器中打开 http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc
,您将收到“403 Forbidden”响应。 您还将在 Docker 日志中看到如下所示的条目:
Example Docker LogWARNING [2021-12-28T03:12:34Z] Completed in 134.6µs /sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc request_id=WQGTfRgeBXPvaQYHkNjab method=GET status=403 client_ip=172.17.0.1 error="Invalid signature"
正如您在该日志行的末尾所看到的那样,imgproxy
返回了 403 错误,因为您没有指定与 imgproxy
预期匹配的签名。 这意味着您已正确配置 imgproxy
以要求签名。 这个签名匹配是共享秘密认证的一个例子。 imgproxy
在内部使用请求的 URL 的 SHA256 哈希加上密钥和盐来计算签名。 为了生成相同的签名,必须提供相同的密钥和盐。 这意味着只有拥有您的 IMGPROXY_KEY
和 IMGPROXY_SALT
值的人或程序才能生成您的 imgproxy
实例将响应的 URL。 没有这些值的用户无法使用您的资源。
注意:您不要直接在 URL 中指定键或盐值,因为这将被公开,无论是在网页源中还是在被其他浏览器或代理缓存时。 这就是为什么使用像 SHA256 这样的“单向”哈希很重要的原因——有人无法获取您的签名并使用它来重新创建您的秘密。
最后,为了发出有效的请求,您需要以与 imgproxy
相同的方式生成有效签名。 imgproxy
Github 存储库中有许多 签名算法 的 示例实现 ,但对于本教程,您将使用 Ruby 脚本。
使用 nano
或您喜欢的文本编辑器,创建一个名为 signature.rb
的文件:
nano signature.rb
将以下内容复制到您的文件中。 用您之前生成的 IMGPROXY_KEY
和 IMGPROXY_SALT
值替换键盐对。 如果您使用的是远程服务器而不是本地计算机,您还应该将最后一行中的 localhost
输出替换为您的服务器 IP。
签名.rb
# adapted from https://github.com/imgproxy/imgproxy/blob/master/examples/signature.rb require "openssl" require "base64" key = ["your IMGPROXY_KEY value"].pack("H*") salt = ["your IMGPROXY_SALT value"].pack("H*") url = "https://i.imgur.com/KSLD4VV.jpeg" encoded_url = Base64.urlsafe_encode64(url).tr("=", "").scan(/.{1,16}/).join("/") path = "/#{encoded_url}" digest = OpenSSL::Digest.new("sha256") hmac = Base64.urlsafe_encode64(OpenSSL::HMAC.digest(digest, key, "#{salt}#{path}")).tr("=", "") signed_path = "/#{hmac}#{path}" puts "Open http://localhost:8080#{signed_path} in a web browser"
首先,此代码块需要生成您的签名所需的库。 然后它使用 .pack("H*")
将密钥和盐字符串转换为十六进制字节,因为这是 OpenSSL 库所期望的(您可以 在此处 阅读有关打包和解包的更多信息)。
然后脚本 URL-safe Base64 对源 URL 进行编码,类似于您在步骤 1 中所做的。 虽然在这种情况下,它也会删除任何在 Base64 中用作填充的 =
字符,并用 /
分割每 16 个字符。 这些更改会生成一个好看的 URL。
一旦脚本具有编码的源 URL,它就会使用 SHA256 算法、密钥和盐生成签名。 然后签名本身会经过 Base64 编码,以便可以安全地包含在您的结果 URL 中。
最后,该脚本会打印出您可以在浏览器中打开的签名 URL。
运行此脚本以获取您的签名 URL。 您可以在终端中打开不同的选项卡,或按 Ctrl+C
停止 imgproxy
,运行脚本,然后使用 docker
命令重新启动它。
ruby signature.rb
输出将类似于以下内容:
OutputOpen http://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc in a web browser
URL 中突出显示的部分将有所不同。 这是您的签名 URL,只有您可以生成,因为只有您知道密钥/盐对。 如果您在浏览器中打开签名 URL,您将看到预期的图像而不是 403。 请务必保存您签名的 URL 以供后续步骤使用。
您现在已将 imgproxy
配置为需要签名 URL,并且您生成了有效的签名 URL。 现在只有有权访问您的密钥/盐对的人或程序才能与您的 imgproxy
对话。
接下来,您将配置 imgproxy
以自动提供最现代的图像格式。
第 4 步 — 配置 imgproxy
以自动提供“下一代”图像
到目前为止,您一直明确告诉 imgproxy
返回图像时使用哪种格式。 另一种选择是告诉 imgproxy
尽可能使用最有效的图像格式。 这意味着只有在用户浏览器支持的情况下,您才能提供“下一代”格式(webp
或 avif
)。 否则,imgproxy
将自动回退到 png
或 jpg
。
imgproxy
通过检查 Accept 标头来执行此操作,该标头在 Web 浏览器请求检查支持的图像格式时自动发送。 您可以在 MDN 上查看图像的默认 Accept 标头的 列表。 例如,Chrome 发送:
image/avif,image/webp,image/apng,image/*,*/*;q=0.8
因为同时存在 image/avif
和 image/webp
(相应图像格式的 MIME 类型 ),我们知道 Chrome 支持这两种图像格式。
您可以使用 AVIF/WebP Support Detection 指示 imgproxy
使用最现代的支持图像格式。 要启用此功能,请将 IMGPROXY_ENABLE_WEBP_DETECTION
环境变量设置为 true
,您可以通过在调用 docker run
时指定它来执行此操作。
首先,使用 Ctrl+C
停止正在运行的 Docker 映像。 然后,使用以下命令重新启动它,确保用您的键和盐值替换突出显示的部分:
docker run --env IMGPROXY_ENABLE_WEBP_DETECTION=true --env IMGPROXY_KEY=your-key-from-Step-3 --env IMGPROXY_SALT=your-Salt-from-Step 3 -p 8080:8080 -it darthsim/imgproxy
这与您在步骤 3 中使用的 docker
命令相同,带有一个额外的 --env
标志以启用 webp
检测。
要检查配置,您将在浏览器中打开图像。 访问您在第 3 步中生成的签名 URL,如下所示:
Example Image URLhttp://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc
将为您提供 webp
图像。 您可以通过在浏览器开发人员工具中检查请求大小和类型或使用 curl
来验证这一点。 要使用 curl
进行检查,请在终端中打开一个新选项卡,以便 imgproxy
保持运行并使用以下命令,将突出显示的 URL 替换为您自己的:
curl -s -v -H "Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8" http://localhost:8080/M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc
-s
标志意味着使输出静音,因为当以纯文本输出时图像没有多大意义。 -v
表示详细,以便您可以看到请求和响应标头。 最后,-H
选项将 Accept
标头设置为与 Chrome 的相同。
输出将类似于以下内容:
Output* Trying ::1:8080... * Connected to localhost (::1) port 8080 (#0) > GET /M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.77.0 > Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8 > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Cache-Control: max-age=3600, public < Content-Disposition: inline; filename="KSLD4VV.webp" < Content-Length: 601246 < Content-Type: image/webp < Expires: Tue, 28 Dec 2021 09:08:32 GMT < Server: imgproxy < Vary: Accept < X-Request-Id: I3sp-dXATs2AClfpHcSg- < Date: Tue, 28 Dec 2021 08:08:32 GMT < * Failure writing output to destination * Closing connection 0
您可以在 Content-Type
标头中看到 imgproxy
已返回 webp 图像。
如果您愿意,可以对 IMGPROXY_ENABLE_AVIF_DETECTION
环境变量执行相同的操作。 这同样适用,但适用于 avif
格式而不是 webp
。
在此步骤中,您将 imgproxy
配置为自动使用下一代图像格式,当且仅当它们受用户浏览器支持时。
结论
在本教程中,您了解了新的图像格式以及如何通过 Docker 运行、配置和保护 imgproxy
。 您还将 imgproxy
配置为使用可以处理它们的浏览器自动将 webp
图像提供给任何网站访问者。 您现在已准备好将 imgproxy
部署到将自动运行 Docker 容器以动态转换图像并提高 Web Vitals 分数的任何地方。
下一步,您可以探索使用 imgproxy
处理图像的其他选项。 您可以在产品文档中查看 处理选项 的完整列表。 您还可以探索其他 配置选项 。 最后,您可以 在 Digital Ocean App Platform 上部署您的 imgproxy 容器。