├── 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 |
--------------------------------------------------------------------------------