NGINX和NGINX Plus轻松交付响应式图像

NGINX | 2016年9月29日

照片:Phil Roeder

使用Image‑Filter模块和srcset标签动态调整图像大小

响应式网页设计已成为现代网站和Web应用的常见做法,既能在各种设备上提供一致的体验,也能针对每种设备优化显示效果。然而,现代设备不仅屏幕尺寸各异,像素密度也不尽相同。HTML5 img 标签提供了多项功能。当服务器提供同一资源的多个变体时,浏览器可以从中选择最合适的资源。如果网站部署了同一图像的多种尺寸,Web浏览器可以选择最适合当前环境的尺寸。

因此,响应式图像可让Web浏览器呈现更贴近设计者意图的效果。这虽然改善了用户体验,但也增加了开发和运维团队的负担,因为他们除了默认图像外,还需要创建和部署大量图像资源变体。

本篇博文将介绍如何使用适用于NGINX和NGINX Plus的Image‑Filter模块来交付响应式图像,无需费力创建和管理大量图像资源变体。只需为每张图像部署一个“源”版本,NGINX或NGINX Plus即可动态调整其大小。本文信息同时适用于NGINX Open Source和NGINX Plus(安装Image‑Filter模块中的单独说明除外)。为简洁起见,本文统一使用NGINX Plus这一名称。

The srcset 属性

提供响应式图像的主要工具是HTML5 srcset 属性,该属性属于img标签。可以使用该属性为不同的像素密度和视口尺寸指定多个图像资源变体。视口是指Web浏览器可用显示空间的通用术语,无论是桌面设备上的窗口,还是移动设备上的全屏应用。

在以下示例中,src属性定义了默认图像,供不支持srcset属性的浏览器使用;srcset属性则指定了两个变体:一个用于标准像素密度的显示设备(1x),另一个用于双倍像素密度的显示设备,例如Apple Retina™显示屏和部分4K显示器(2x)。

<img src="/images/mylogo-default.png"  srcset="/images/mylogo-density1.png 1x, /images/mylogo-density2.png 2x">

以下更复杂的示例定义了多个图像资源变体,并根据视口宽度进行显示。sizes属性指定:如果视口宽度大于10em,浏览器将以视口宽度的一半渲染图像;否则使用整个视口宽度。浏览器会确定图像的可用空间,并选择最适合该空间的图像资源变体,通常会向上取整到下一个宽度(w后缀),然后在内部调整图像大小,使其恰好填满可用空间。

<img src="/images/racecar-default.jpg"  sizes="(min-width: 10em) 50vw, 100vw"
  srcset="/images/racecar-100px.jpg 100w, /images/racecar-225px.jpg 225w, /images/racecar-450px.jpg 450w, /images/racecar-675px.jpg 675w">

这种通过指定一系列图像资源变体来提供响应式图像的方法易于编码,而且非常有效。不过,创建和管理这些图像变体本身并不容易。您需要在制作前完成大量图像尺寸调整工作,并在服务器上部署更多文件。要精细调整各变体的适当数量和尺寸,可能需要耗费大量时间,进而难以测试每个图像资源变体是否均可访问。

有关srcset属性及其他响应式图像技术的更多信息,请参阅这篇精彩的博客文章。

安装Image‑Filter模块

NGINX Plus和NGINX获取该Image‑Filter模块的步骤有所不同。

为NGINX Plus安装Image‑Filter模块

Image‑Filter模块作为免费动态模块提供给NGINX Plus订阅用户。

  1. 从NGINX Plus存储库安装该模块。对于Ubuntu和Debian系统:
$ sudo apt-get install nginx-plus-module-image-filter
  1. 对于RedHat、CentOS和Oracle Linux系统:
$ sudo yum install nginx-plus-module-image-filter
  1. 要启用该模块,请在load_module配置文件的顶层(“main”)上下文中添加相应的nginx.conf指令(即不要添加到http或stream上下文中)。
load_module modules/ngx_http_image_filter_module.so;
  1. 重新加载NGINX Plus,将Image‑Filter模块加载到正在运行的实例中。
$ sudo nginx -s reload>

为NGINX Open Source安装图像过滤模块

安装Image‑Filter模块最简便的方法是从NGINX官方存储库获取该模块。按照这些说明配置系统以访问NGINX官方存储库,然后使用操作系统的软件包管理器进行安装。

对于Ubuntu和Debian系统:

$ sudo apt-get install nginx-module-image-filter

对于Red Hat、CentOS和Oracle Linux系统:

$ sudo yum install nginx-module-image-filter

安装完成后,请按照为NGINX Plus安装图像过滤器模块中的步骤2和步骤3配置并重新加载NGINX。

也可以从源代码编译Image‑Filter模块,并将其作为静态编译模块或动态模块加载。有关详细信息,请参阅《NGINX Plus管理指南》。

使图像尺寸与像素密度相匹配

借助Image‑Filter模块,只需为每张图片创建并部署一个“源”版本,NGINX Plus即可动态调整其大小,提供浏览器请求的任意尺寸变体。我们可以完全在HTML源代码中微调响应式网页和图片,无需手动调整图片大小并将其部署到Web服务器。

在此示例HTML文件中,我们为不同像素密度的设备定义了4种图像变体。

<!DOCTYPE html><html>
<head>
<title>Responsive Logo</title>
</head>
<body>
 
<h2>Logo selection based on pixel density</h2>

<img src="/img400/mylogo.png"
  srcset="/img400/mylogo.png 1x, /img800/mylogo.png 2x, /img1200/mylogo.png 3x, /img1600/mylogo.png 4x">
 
</body>
</html>

实际上并不存在/img400、/img800、/img1200和/img1600这些目录。以下NGINX Plus配置会匹配对前缀为/img的资源的请求,并将其转换为调整原始文件名所指图像大小的请求(例如,前述HTML中的mylogo.png)。

server {    listen 80;
    root /var/www/public_html;
 
    location ~ ^/img([0-9]+)(?:/(.*))?$ {
        alias /var/www/source_images/$2;
        image_filter_buffer 10M;
        image_filter resize $1 -;
    }
}

The server块定义了NGINX Plus处理传入HTTP请求的方式。listen指令让NGINX Plus侦听端口80,这是HTTP流量的默认端口。root指令指定此网站在磁盘上的位置。在这个简单示例中,我们使用由NGINX Plus托管的静态网站,但NGINX Plus也常用作动态内容的反向代理,或FastCGI等应用连接器。只需将源图像部署到NGINX Plus服务器,所有这些用例都可以按此处所述使用Image‑Filter模块。

The location块使用正则表达式匹配以下资源请求:资源存储在以/img开头且后跟一个或多个数字的任意目录中。这些数字捕获为变量$1,后面的文件名捕获为变量$2。然后,使用alias指令从磁盘上包含源图像的目录中提供所请求的资源。请注意,此目录不在root路径下,因此客户端无法直接请求源图像。

由于源图像可能非常大,宽度甚至可达数千像素,因此需要确保Image‑Filter模块分配足够的内存来加载图像并调整其大小。在本例中,我们使用image_filter_buffer指令来支持最大10 MB的图像文件。

最后,image_filter指令会让Image‑Filter模块根据从/img目录名称后缀中捕获的宽度调整源图像大小。短横线(‑)表示让NGINX Plus保持源图像的宽高比。

生产环境使用注意事项

在根据像素密度匹配图像尺寸中介绍的配置可根据请求图像时使用的目录名称,提供任意尺寸的图像变体。但在生产环境中,我们不希望等待Web服务器为每个请求调整图像尺寸,因为这会增加整体延迟,还可能显著增加CPU开销。

更有效的解决方案是缓存调整尺寸后的图像变体。这样,后续对各变体的请求可直接由缓存提供响应,无需经过Image‑Filter模块。通过配置NGINX Plus,可以定义一个单独的虚拟服务器来调整图像尺寸,并且仅当缓存中尚无所请求尺寸的图像时,才将请求代理到该服务器。我们将其称为“响应式图像服务器”。

图 1. 响应式图像服务器的生产环境配置,前端 Web 服务器已启用缓存

还必须考虑允许任意请求执行图像大小调整操作所带来的安全影响。如果攻击者快速请求不同的图像资源变体,例如/img1001/mylogo.png、/img1002/mylogo.png、/img1003/mylogo.png等,即使配置了缓存也无济于事。即使请求量相对较低,此类攻击也可能因CPU利用率过高而导致拒绝服务(DoS)。为解决此问题,我们对响应式图像服务器实施速率限制,但不限制包含缓存变体的前端服务器。以下配置在使图像大小与像素密度相匹配中的配置基础上,为Image‑Filter模块应用缓存和速率限制。

proxy_cache_path /var/www/imgcache levels=1 keys_zone=resized:1m max_size=256m;
server {
    listen 80;
    root /var/www/public_html;

    location ~ ^/img([0-9]+)(?:/(.*))?$ {
        proxy_pass        http://127.0.0.1:9001;
        proxy_cache       resized;
        proxy_cache_valid 180m;
    }
}

limit_req_zone "1" zone=2persec:32k rate=2r/s;

server {
    listen 9001;
    allow 127.0.0.1;
    deny all;
    limit_req zone=2persec burst=10;

    location ~ ^/img([0-9]+)(?:/(.*))?$ {
        alias /var/www/source_images/$2;
        image_filter_buffer 10M;
        image_filter resize $1 -;
    }
}

首先,使用proxy_cache_path指令定义缓存图像的存储位置。keys_zone参数为缓存索引定义一个名为resized的共享内存区,并分配1 MB内存,足以容纳约8,000张调整大小后的图像。max_size参数定义缓存大小阈值;达到该阈值后,NGINX Plus会开始从缓存中移除最近请求次数最少的图像,为新的缓存项腾出空间。

前端Web服务器(监听端口80)的location指令使用proxy_pass指令,将前缀为/img的请求发送到内部托管的响应式图像服务器(127.0.0.1:9001)。proxy_cache指令通过指定用于存储响应式图像服务器响应的缓存名称(resized),为此位置启用缓存。proxy_cache_valid指令可确保调整大小后的图像在缓存中至少保留180分钟(除非缓存已超过max_size,且这些图像属于最近请求次数最少的图像),并确保不缓存响应式图像服务器返回的任何错误响应。

有关缓存的深入说明,请参阅《NGINX 和 NGINX Plus 缓存指南》。

在定义响应式图像服务器本身之前,我们先使用limit_req_zone指令指定速率限制。该指令本身并不强制执行速率限制,而是定义每秒2个请求的速率限制。随后,通过在响应式图像服务器的limit_req块中加入server指令来应用此限制(见下一段)。速率限制通常以请求的某个属性作为键,但在本例中,我们指定静态键值"1",使该限制适用于所有请求方。由于键的基数固定为1,因此我们将共享内存区域的大小设置为可用的最小值3 KB。

响应式图像服务器的server块侦听端口9001。我们添加allow和deny指令,指定只有localhost(前端Web服务器)可以连接到响应式图像服务器。然后添加limit_req指令,以应用之前定义的速率限制;burst参数允许先并发处理10个请求,之后再实施速率限制。速率限制生效后,超出的请求将被延迟,直至可在限制范围内处理。

此location块与根据像素密度匹配图像尺寸中的块相同,但现在仅当请求的图像不在缓存中且未超过速率限制时才会执行。

在此基本配置中,单个NGINX Plus实例同时充当前端Web服务器和响应式图像服务器。图像处理可能需要大量计算资源,可能导致工作负载极高,并使NGINX Plus面临DoS攻击。为避免所有工作进程都忙于处理图像大小调整请求,导致前端Web服务器无法立即接受新请求,建议运行一个专用于图像处理的独立NGINX Plus实例。这样可以将前端Web服务器的工作进程与执行图像处理的工作进程隔离开来。要在同一主机上运行独立的NGINX Plus实例,请在命令行中指定其他配置文件:

$ sudo nginx -c /etc/nginx/resize-server.conf

响应式图像实战

要直观了解响应式图像的实际效果,最有效的方法是观察浏览器如何随着视口大小的变化,决定使用哪个srcset图像变体。以下是一个简单图像库的HTML源代码。请注意,为便于演示,每张图像的尺寸变体略有不同,因此会产生许多可能的“断点”,浏览器可在这些断点选择不同的变体。

<!DOCTYPE html>
<html>
<head>
<title>Responsive Image Gallery</title>
</head>
<body>
 
<h2>Responsive Image Gallery</h2>

<img src="/img100/1-dominos.jpg" sizes="(min-width: 20em) 40vw, 100vw"
  srcset="/img110/1-dominos.jpg 110w, /img210/1-dominos.jpg 210w, /img310/1-dominos.jpg 310w, /img410/1-dominos.jpg 410w, /img510/1-dominos.jpg 510w, /img610/1-dominos.jpg 610w">

<img src="/img100/2-sign.jpg" sizes="(min-width: 20em) 40vw, 100vw"
  srcset="/img120/2-sign.jpg 120w, /img220/2-sign.jpg 220w, /img330/2-sign.jpg 330w, /img420/2-sign.jpg 420w, /img520/2-sign.jpg 520w, /img620/2-sign.jpg 620w">

<img src="/img100/3-thruppence.jpg" sizes="(min-width: 20em) 40vw, 100vw"
  srcset="/img130/3-thruppence.jpg 130w, /img230/3-thruppence.jpg 230w, /img330/3-thruppence.jpg 330w, /img440/3-thruppence.jpg 440w, /img550/3-thruppence.jpg 550w, /img660/3-thruppence.jpg 660w">

<img src="/img100/4-aces.jpg" sizes="(min-width: 20em) 40vw, 100vw"
  srcset="/img140/4-aces.jpg 140w, /img240/4-aces.jpg 240w, /img340/4-aces.jpg 340w, /img440/4-aces.jpg 440w, /img540/4-aces.jpg 540w, /img640/4-aces.jpg 640w">

</body>
</html>

以下屏幕截图显示了此网页在Chrome浏览器中的内容,其中检查器已打开至网络标签页。名称列显示了向服务器请求的每个图像变体的路径,因此无需查看Web服务器日志即可了解所选尺寸。

在图 2 所示的窄视口下,浏览器选择了宽度为 220 至 310 像素的图像(“/img”目录名称在“名称”列中的数字后缀介于这两个值之间)。

图2. 视口较窄时的响应式图片库

在图3中,当我们加宽浏览器窗口时,浏览器会选择宽度为440至540像素的图像(即列出的最后4张图像)。这些图像在“发起程序”列中的值为“其他”。

图 3. 视口较宽时的响应式图片库

结论

借助NGINX Plus和Image‑Filter模块,我们可以根据当前浏览器条件提供适宜的图像尺寸,而无需在投入生产前调整图像大小、进行批处理,也无需管理磁盘上数百种图像资源变体。这是NGINX Plus帮助您实现顺畅应用交付的又一种方式。

亲自体验使用NGINX Plus实现响应式图像,立即开始30天免费试用,或联系我们,讨论您的用例。


分享
标签: F5 NGINX, Tech

关于作者

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。