实现逻辑
用户访问一张图片,nginx收到请求,通过读取 Request Headers
中的 Accept
字段值来判断浏览器是否支持WebP,如果支持则返回WebP,不支持则返回原图。
环境安装
安装libjpeg, libpng
yum install libjpeg-turbo-devel libjpeg-turbo libpng-devel gcc-c++ -y
安装LibTIFF
cd /usr/local/src
wget http://download.osgeo.org/libtiff/tiff-4.0.8.tar.gz
tar -zxvf tiff-4.0.8.tar.gz
cd tiff-4.0.8
./configure
make
make install
安装giflib
cd /usr/local/src
wget https://sourceforge.net/projects/giflib/files/giflib-5.1.4.tar.gz
tar zxvf giflib-5.1.4.tar.gz
cd giflib-5.1.4
./configure
make
make install
安装libwebp
cd /usr/local/src
wget http://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.2.0.tar.gz
tar -zxvf libwebp-1.2.0.tar.gz
cd libwebp-1.2.0
./configure --enable-libwebpmux \
--enable-libwebpdemux \
--enable-libwebpdecoder \
--enable-libwebpextras \
--enable-swap-16bit-csp \
--disable-static
make
make install
- 编译完要检查输出是否支持 JPG、PNG、GIF、WEBP
WebP Configuration Summary -------------------------- Shared libraries: yes Static libraries: no Threading support: yes libwebp: yes libwebpdecoder: yes libwebpdemux: yes libwebpmux: yes libwebpextras: yes Tools: cwebp : yes Input format support ==================== JPEG : yes PNG : yes TIFF : yes WIC : no dwebp : yes Output format support ===================== PNG : yes WIC : no GIF support : yes anim_diff : yes gif2webp : yes img2webp : yes webpmux : yes vwebp : no webpinfo : yes SDL support : no vwebp_sdl : no
加载环境
ldconfig
测试转换
Google提供了一组工具集合,叫 libwebp ,其中包括各种 webp 相关转换的命令:
- cwebp – 将其它图片转为webp格式图片 (不包括GIF)
- dwebp – 将webp格式图片转为其它格式图片
- vwebp – webp图片浏览器
- webpmux – WebP muxing tool
- gif2webp – 将GIF转换为webp图片
- 不太建议使用,测试发现 75 质量的 webp 文件会出现比原文件还大的情况。
# png -> webp
cwebp -q 75 01.png -o 01.webp
# gif -> webp
gif2webp -q 80 02.gif -o 02.webp
# webp -> png
dwebp image.webp -o image.png
此处省略 lua-nginx 的相关编译: https://nestealin.com/ce9634bf/
方案一: 在原资源目录下生成 webp 文件
- 此方案用于 webp 文件经常变更的情况下,每次随项目部署,路径自动清空临时 webp 文件,会影响初次加载。
引入 lua 脚本
打开 /usr/local/nginx/conf/nginx.conf 文件,在http段添加
http { lua_package_path "/usr/local/luanginx/lualib/?.lua;;"; }
webp 转换脚本
vim /usr/local/nginx/conf/lua/webp.lua
-- 检测文件是否存在 function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end -- 接收 location 中定义的 $webp_filepath 变量 local newFile = ngx.var.webp_filepath; --ngx.log(ngx.ERR, " newFile:", newFile) local originalFile = newFile:sub(1, #newFile - 5); -- 去掉 .webp 的后缀 --ngx.log(ngx.ERR, " originalFile:", originalFile) if not file_exists(originalFile) then -- 原文件不存在 ngx.log(ngx.ERR, "The originalFile is NOT FOUND!") ngx.exit(404); return; end -- 转换原图片到 webp 格式,这里的质量是 75 ,你也可以改成别的 os.execute("cwebp -q 75 " .. originalFile .. " -o " .. newFile); --ngx.log(ngx.ERR, "converting to webp...") if file_exists(newFile) then -- 如果新文件存在(转换成功) -- 转换 webp 成功,并返回 .webp 结尾的 uri 地址 ngx.exec(ngx.var.webp_filename) -- Internal Redirect else ngx.exit(404) end
域名配置
# 根据浏览器 accpet 请求头辨别是否支持webp
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
listen 80;
server_name abc.nestealin.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.nestealin.com ;
charset utf-8;
access_log logs/$http_host.access.main.log main;
error_log logs/abc.nestealin.com.error.log error;
ssl_certificate /data/keys/server.cer;
ssl_certificate_key /data/keys/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256:P-384;
ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:EECDH+CHACHA20:EECDH+AES128;
# 开启 TLS 1.3 0-RTT
ssl_early_data on;
# 添加 Early-Data 头告知后端, 防止重放攻击
proxy_set_header Early-Data $ssl_early_data;
#include firewall.conf;
set $origin_root /data/blogs/;
location @webp_redirect {
content_by_lua_file "/usr/local/nginx/conf/lua/webp.lua";
}
location ~ .*\.(jpg|jpeg|png)$ {
set $webp_filename $uri$webp_suffix;
set $webp_filepath $origin_root$webp_filename;
# 先去检测是否存在webp文件,如果没有,则内部跳转执行lua转换
try_files $webp_filename @webp_redirect;
root $origin_root;
}
location / {
root $origin_root ;
index index.html;
if (!-e $request_filename) {
rewrite ^/(.*)$ / redirect;
}
}
}
方案二: 将 webp 生成到临时目录
- 此方案用于 webp 文件不经常更新的情况下,无需繁复重新生成,影响初次加载。
- 与方案一区别在于多了一层临时目录。
创建临时目录
mkdir /data/webp_data
# 给予 nginx/lua 脚本运行用户可写权限
chown -R www.www /data/webp_data
引入 lua 脚本
打开 /usr/local/nginx/conf/nginx.conf 文件,在http段添加
http { lua_package_path "/usr/local/luanginx/lualib/?.lua;;"; }
创建执行脚本
vim /usr/local/nginx/conf/lua/webp_tmp_path.lua
--检测路径是否为目录
local function is_dir(sPath)
if type(sPath) ~= "string" then
return false
end
local response = os.execute("cd " .. sPath)
if response == 0 then
return true
end
return false
end
function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
local newFile = ngx.var.webp_filepath;
--ngx.log(ngx.ERR, " newFile:", newFile)
local originalFile = ngx.var.origin_filepath;
--ngx.log(ngx.ERR, " originalFile:", originalFile)
if not file_exists(originalFile) then -- 原文件不存在
ngx.log(ngx.ERR, "The originalFile is NOT FOUND!")
ngx.exit(404);
return;
end
--获取文件路径,不存在则新增
function getFileDir(filename)
return string.match(filename, "(.+)/[^/]*%.%w+$")
end
if not is_dir(getFileDir(newFile)) then
ngx.log(ngx.ERR, "creating webp tmp document...")
os.execute("mkdir -p " .. getFileDir(newFile))
end
-- 转换原图片到 webp 格式,这里的质量是 75 ,你也可以改成别的
os.execute("cwebp -q 75 " .. originalFile .. " -o " .. newFile);
ngx.log(ngx.ERR, "converting to webp...")
if file_exists(newFile) then -- 如果新文件存在(转换成功)
-- 转换 webp 成功,并返回 .webp 结尾的 uri 地址
ngx.exec(ngx.var.webp_filename) -- Internal Redirect
else
ngx.exit(404)
end
域名配置
# 根据浏览器 accpet 请求头辨别是否支持webp
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
listen 80;
server_name abc.nestealin.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.nestealin.com ;
charset utf-8;
access_log logs/$http_host.access.main.log main;
error_log logs/abc.nestealin.com.error.log error;
ssl_certificate /data/keys/server.cer;
ssl_certificate_key /data/keys/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256:P-384;
ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:EECDH+CHACHA20:EECDH+AES128;
# 开启 TLS 1.3 0-RTT
ssl_early_data on;
# 添加 Early-Data 头告知后端, 防止重放攻击
proxy_set_header Early-Data $ssl_early_data;
#include firewall.conf;
set $origin_root /data/blogs/;
location @webp_redirect {
content_by_lua_file "/usr/local/nginx/conf/lua/webp_tmp_path.lua";
}
location ~ .*\.(jpg|jpeg|png)$ {
set $webp_tmp_root /data/webp_data/;
set $webp_filename $uri$webp_suffix;
set $webp_filepath $webp_tmp_root$webp_filename;
set $origin_filepath $origin_root$uri;
# 兼容不支持 webp 的浏览器,直接返回原格式
if ($webp_filename !~ .*\.webp$) {
root $origin_root;
}
# 先去检测是否存在webp文件,如果没有,则内部跳转执行lua转换
try_files $webp_filename @webp_redirect;
root $webp_tmp_root;
}
location / {
root $origin_root ;
index index.html;
if (!-e $request_filename) {
rewrite ^/(.*)$ / redirect;
}
}
}
效果
- 在 URL 不变的基础上,实现图片压缩,转换为 webp 格式返回,大大缩小原图带宽开销,缩短客户端加载时间。
- 压缩前,客户端不支持 webp ,获得原图2.3MB
- 压缩后,客户端支持 webp ,获得压缩后图片137KB
相关链接
- 通过 Nginx-Lua 自动转换图片为 WebP 格式
- 前端图片webp
- 支持webp的图片智适应下发