实现逻辑
用户访问一张图片,nginx收到请求,通过读取 Request Headers
中的 Accept
字段值来判断浏览器是否支持WebP,如果支持则返回WebP,不支持则返回原图
环境安装
安装libjpeg, libpng
yum install libjpeg-turbo-devel libjpeg-turbo libpng-devel gcc-c++ -y
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
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
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
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
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
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
# 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
方案一
在原资源目录下生成 webp 文件,此方案用于 webp 文件经常变更的情况下,每次随项目部署,路径自动清空临时 webp 文件,会影响初次加载
引入 lua 脚本
- http
http {
lua_package_path "/data/apps/luajit2/lib/?.lua;;";
}
http {
lua_package_path "/data/apps/luajit2/lib/?.lua;;";
}
- webp 转换脚本
vim 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
-- 检测文件是否存在
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.xxx.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.xxx.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;
set $origin_root /data/blogs/;
location @webp_redirect {
content_by_lua_file "/data/apps/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;
}
}
}
# 根据浏览器 accpet 请求头辨别是否支持webp
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
listen 80;
server_name abc.xxx.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.xxx.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;
set $origin_root /data/blogs/;
location @webp_redirect {
content_by_lua_file "/data/apps/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 nginx.nginx /data/webp_data
mkdir /data/webp_data
# 给予 nginx/lua 脚本运行用户可写权限
chown -R nginx.nginx /data/webp_data
引入 lua 脚本
http {
lua_package_path "/data/apps/luajit2/lib/?.lua;;";
}
http {
lua_package_path "/data/apps/luajit2/lib/?.lua;;";
}
- 创建执行脚本
vim /data/apps/nginx/conf/lua/webp_tmp_path.lua
vim /data/apps/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
--检测路径是否为目录
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.xxx.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.xxx.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;
}
}
}
# 根据浏览器 accpet 请求头辨别是否支持webp
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
server {
listen 80;
server_name abc.xxx.com ;
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl http2;
server_name abc.xxx.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;
}
}
}