├── caddy.conf ├── apache.conf ├── varnish.vcl ├── README.md ├── iis.config ├── nginx.conf └── pagespeed.cc /caddy.conf: -------------------------------------------------------------------------------- 1 | rewrite { 2 | if {>Accept} has image/webp 3 | r ^(.+)\.(jpg|jpeg|png)$ 4 | to {1}.webp {path} {path}/ 5 | } 6 | header / Vary Accept 7 | -------------------------------------------------------------------------------- /apache.conf: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteCond %{HTTP_ACCEPT} image/webp 4 | RewriteCond %{DOCUMENT_ROOT}/$1.webp -f 5 | RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1] 6 | 7 | 8 | 9 | Header append Vary Accept env=REDIRECT_accept 10 | 11 | 12 | AddType image/webp .webp 13 | -------------------------------------------------------------------------------- /varnish.vcl: -------------------------------------------------------------------------------- 1 | backend default { 2 | .host = "127.0.0.1"; 3 | .port = "8080"; 4 | } 5 | 6 | sub webp_detect { 7 | unset req.http.WebP; 8 | 9 | if (req.http.Accept ~ "image\/webp") { 10 | set req.http.WebP = "true"; 11 | } 12 | } 13 | 14 | 15 | sub vcl_recv { 16 | set req.http.host = "yoursite.com"; 17 | set req.backend = default; 18 | 19 | // detect WebP support 20 | call webp_detect; 21 | 22 | if (req.request == "GET") { 23 | return(lookup); 24 | } 25 | 26 | if (req.request != "GET" && req.request != "HEAD" && 27 | req.request != "PUT" && req.request != "POST" && 28 | req.request != "TRACE" && req.request != "OPTIONS" && 29 | req.request != "DELETE") { 30 | 31 | # Non-RFC2616 or CONNECT which is weird. 32 | return(pass); 33 | } 34 | 35 | return(lookup); 36 | } 37 | 38 | sub vcl_fetch { 39 | // no caching, configure your own strategy here 40 | return(hit_for_pass); 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebP with Accept negotiation 2 | 3 | A collection of configuration scripts for serving WebP assets: 4 | 5 | - Check if the client advertises "image/webp" in Accept header 6 | - If WebP is supported, check if the local WebP file is on disk, and serve it 7 | - If server is configured as proxy, append a "WebP: true" header and forward to backend 8 | - Append "Vary: Accept" to the client response 9 | 10 | Above sequence of steps allows [transparent Accept negotiation of WebP assets](http://www.igvita.com/2013/05/01/deploying-webp-via-accept-content-negotiation/) - no need to modify your existing applications. Either pregenerate the WebP files, or serve WebP files dynamically to approriate clients. 11 | 12 | ## Getting started 13 | 14 | Download or copy the configuration file and run your server. For example: 15 | 16 | ``` 17 | $> nginx -c /path-to/webp-detect/nginx.conf 18 | $> varnishd -a :8081 -T localhost:6082 -F -f varnish.vcl 19 | ``` 20 | 21 | With the above in place, access the page and look at the request header appended by the server - you will see a new `WebP` header sent to your application server if the browser supports WebP. 22 | 23 | ### What about server X? 24 | 25 | * See list of included server configurations above. 26 | * Connect middleware (node.js): https://github.com/msemenistyi/connect-image-optimus 27 | * ... please send a pull request! 28 | -------------------------------------------------------------------------------- /iis.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | include mime.types; 9 | default_type application/octet-stream; 10 | 11 | # 12 | # < regular Nginx configuration here > 13 | # 14 | 15 | # For a hands-on explanation of using Accept negotiation, see: 16 | # http://www.igvita.com/2013/05/01/deploying-webp-via-accept-content-negotiation/ 17 | 18 | # For an explanation of how to use maps for that, see: 19 | # http://www.lazutkin.com/blog/2014/02/23/serve-files-with-nginx-conditionally/ 20 | 21 | map $http_accept $webp_suffix { 22 | "~*webp" ".webp"; 23 | } 24 | map $msie $cache_control { 25 | "1" "private"; 26 | } 27 | map $msie $vary_header { 28 | default "Accept"; 29 | "1" ""; 30 | } 31 | 32 | # if proxying to another backend and using nginx as cache 33 | proxy_cache_path /tmp/cache levels=1:2 keys_zone=my-cache:8m max_size=1000m inactive=600m; 34 | proxy_temp_path /tmp/cache/tmp; 35 | 36 | server { 37 | listen 8081; 38 | server_name localhost; 39 | 40 | location ~ \.(png|jpe?g)$ { 41 | # set response headers specially treating MSIE 42 | add_header Vary $vary_header; 43 | add_header Cache-Control $cache_control; 44 | # now serve our images 45 | try_files $uri$webp_suffix $uri =404; 46 | } 47 | 48 | # if proxying to another backend and using nginx as cache 49 | if ($http_accept ~* "webp") { set $webp_accept "true"; } 50 | proxy_cache_key $scheme$proxy_host$request_uri$webp_local$webp_accept; 51 | 52 | location ~ ^/proxy.*\.(png|jpe?g)$ { 53 | # Pass WebP support header to backend 54 | proxy_set_header WebP $webp_accept; 55 | proxy_pass http://127.0.0.1:8080; 56 | proxy_cache my-cache; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pagespeed.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2010 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // WebP User-Agent detection rules from: 16 | // https://code.google.com/p/modpagespeed/source/browse/trunk/src/net/instaweb/http/user_agent_matcher.cc#86 17 | 18 | // For webp rewriting, we whitelist Android, Chrome and Opera, but blacklist 19 | // older versions of the browsers that are not webp capable. As other browsers 20 | // roll out webp support we will need to update this list to include them. 21 | 22 | const char* kWebpWhitelist[] = { 23 | "*Android *", 24 | "*Chrome/*", 25 | "*Opera/9.80*Version/??.*", 26 | "*Opera???.*" 27 | }; 28 | 29 | const char* kWebpBlacklist[] = { 30 | "*Android 0.*", 31 | "*Android 1.*", 32 | "*Android 2.*", 33 | "*Android 3.*", 34 | "*Chrome/0.*", 35 | "*Chrome/1.*", 36 | "*Chrome/2.*", 37 | "*Chrome/3.*", 38 | "*Chrome/4.*", 39 | "*Chrome/5.*", 40 | "*Chrome/6.*", 41 | "*Chrome/7.*", 42 | "*Chrome/8.*", 43 | "*Chrome/9.0.*", 44 | // Chrome 14, 15 and 16 had webp rendering bug. 45 | "*Chrome/14.*", 46 | "*Chrome/15.*", 47 | "*Chrome/16.*", 48 | // Clank v<21 had webp endianness bug. 49 | "*Android *Chrome/1?.*", 50 | "*Android *Chrome/20.*", 51 | "*Opera/9.80*Version/10.*", 52 | "*Opera?10.*", 53 | "*Opera/9.80*Version/11.0*", 54 | "*Opera?11.0*", 55 | }; 56 | 57 | const char* kWebpLosslessAlphaWhitelist[] = { 58 | "*Chrome/??.*", 59 | "*Chrome/???.*" 60 | }; 61 | 62 | const char* kWebpLosslessAlphaBlacklist[] = { 63 | "*Chrome/?.*", 64 | "*Chrome/1?.*", 65 | "*Chrome/20.*", 66 | "*Chrome/21.*", 67 | "*Chrome/22.*", 68 | }; 69 | --------------------------------------------------------------------------------