Skip to content

实现逻辑

​ 用户访问一张图片,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;
        }
    }
}