[experimental-ims] 4fe672c Add section on device detection

Lasse Karstensen lasse at varnish-software.com
Thu Dec 18 10:27:40 CET 2014


commit 4fe672ca2b418043a965ab28049e1256a5011dbe
Author: Lasse Karstensen <lasse at varnish-software.com>
Date:   Wed Mar 7 14:35:41 2012 +0100

    Add section on device detection

diff --git a/doc/sphinx/tutorial/devicedetection.rst b/doc/sphinx/tutorial/devicedetection.rst
new file mode 100644
index 0000000..a083e6e
--- /dev/null
+++ b/doc/sphinx/tutorial/devicedetection.rst
@@ -0,0 +1,260 @@
+.. _tutorial-devicedetect:
+
+Device detection
+~~~~~~~~~~~~~~~~
+
+Device detection is figuring out what kind of content to serve to a
+client based on the User-Agent string supplied in a request.
+
+Use cases for this are for example to send size reduced files to mobile
+clients with small screens and on high latency networks, or to 
+provide a streaming video codec that the client understands.
+
+There are a couple of strategies on what to do with such clients:
+1) Redirect them to another URL.
+2) Use a different backend for the special clients.
+3) Change the backend requests so the usual backend sends tailored content.
+
+To make the examples easier to understand, it is assumed in this text 
+that all the req.http.X-UA-Device header is present and unique per client class
+that content is to be served to. 
+
+Setting this header can be as simple as::
+
+   sub vcl_recv { 
+       if (req.http.User-Agent ~ "(?i)iphone" {
+           set req.http.X-UA-Device = "mobile-iphone";
+       }
+   }
+
+There are different commercial and free offerings in doing grouping and identifiying clients
+in further detail than this.
+
+
+Serve the different content on the same URL
+-------------------------------------------
+
+The tricks involved are: 
+1. Detect the client (pretty simple, just include devicedetect.vcl and call
+it)
+2. Figure out how to signal the backend what client class this is. This
+includes for example setting a header, changing a header or even changing the
+backend request URL.
+3. Modify any response from the backend to add missing Vary headers, so
+Varnish' internal handling of this kicks in.
+4. Modify output sent to the client so any caches outside our control don't
+serve the wrong content.
+
+All this while still making sure that we only get 1 cache object per URL per
+device class.
+
+
+Example 1: Send HTTP header to backend
+''''''''''''''''''''''''''''''''''''''
+
+The basic case is that Varnish add the X-UA-Device HTTP header on the backend
+requests, and the backend mentions in the response Vary header that the content
+is dependant on this header. 
+
+Everything works out of the box from Varnish' perspective.
+
+.. 071-example1-start
+VCL::
+
+    sub vcl_recv { 
+        # call some detection engine that set req.http.X-UA-Device
+    }
+
+    sub append_ua_device {
+        if (req.http.X-UA-Device) { 
+            set bereq.http.X-UA-Device = req.http.X-UA-Device; }
+    }
+
+    # This must be done in vcl_miss and vcl_pass, before any backend request is
+    # actually sent. vcl_fetch runs after the request to the backend has
+    # completed.
+    sub vcl_miss { call append_ua_device; }
+    sub vcl_pass { call append_ua_device; }
+
+    # so, this is a bit conterintuitive. The backend creates content based on
+    # the normalized User-Agent, but we use Vary on X-UA-Device so Varnish will
+    # use the same cached object for all U-As that map to the same X-UA-Device.
+    #
+    # If the backend does not mention in Vary that it has crafted special
+    # content based on the User-Agent (==X-UA-Device), add it. 
+    # If your backend does set Vary: User-Agent, you may have to remove that here.
+    sub vcl_fetch {
+        if (req.http.X-UA-Device) {
+            if (!beresp.http.Vary) { # no Vary at all
+                set beresp.http.Vary = "X-UA-Device"; 
+            } elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
+                set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device"; 
+            } 
+        }
+        # comment this out if you don't want the client to know your
+        # classification
+        set beresp.http.X-UA-Device = req.http.X-UA-Device;
+    }
+
+    # to keep any caches in the wild from serving wrong content to client #2
+    # behind them, we need to transform the Vary on the way out.
+    sub vcl_deliver {
+        if ((req.http.X-UA-Device) && (resp.http.Vary)) {
+            set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
+        }
+    }
+.. 071-example1-end
+
+Example 2: Normalize the User-Agent string
+''''''''''''''''''''''''''''''''''''''''''
+
+Another way of signaling the device type is to override or normalize the
+User-Agent header sent to the backend.
+
+For example
+
+    User-Agent: Mozilla/5.0 (Linux; U; Android 2.2; nb-no; HTC Desire Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
+
+becomes:
+
+    User-Agent: mobile-android
+
+when seen by the backend.
+
+This works if you don't need the original header for anything on the backend.
+A possible use for this is for CGI scripts where only a small set of predefined
+headers are (by default) available for the script.
+
+.. 072-example2-start
+VCL::
+
+    sub vcl_recv { 
+        # call some detection engine that set req.http.X-UA-Device
+    }
+
+    # override the header before it is sent to the backend
+    sub vcl_miss { if (req.http.X-UA-Device) { set bereq.http.User-Agent = req.http.X-UA-Device; } }
+    sub vcl_pass { if (req.http.X-UA-Device) { set bereq.http.User-Agent = req.http.X-UA-Device; } }
+
+    # standard Vary handling code from previous examples.
+    sub vcl_fetch {
+        if (req.http.X-UA-Device) {
+            if (!beresp.http.Vary) { # no Vary at all
+                set beresp.http.Vary = "X-UA-Device";
+            } elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
+                set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
+            }
+        }
+        set beresp.http.X-UA-Device = req.http.X-UA-Device;
+    }
+    sub vcl_deliver {
+        if ((req.http.X-UA-Device) && (resp.http.Vary)) {
+            set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
+        }
+    }
+
+.. 072-example2-end
+
+Example 3: Add the device class as a GET query parameter
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+If everything else fails, you can add the device type as a GET argument.
+
+    http://example.com/article/1234.html --> http://example.com/article/1234.html?devicetype=mobile-iphone
+
+The client itself does not see this classification, only the backend request
+is changed.
+
+.. 073-example3-start
+VCL::
+
+    sub vcl_recv { 
+        # call some detection engine that set req.http.X-UA-Device
+
+        if ((req.http.X-UA-Device) && (req.request == "GET")) {
+            # if there are existing GET arguments;
+            if (req.url ~ "\?") {
+                set req.http.X-get-devicetype = "&devicetype=" + req.http.X-UA-Device;
+            } else { 
+                set req.http.X-get-devicetype = "?devicetype=" + req.http.X-UA-Device;
+            }
+            set req.url = req.url + req.http.X-get-devicetype;
+            unset req.http.X-get-devicetype;
+        }
+    }
+
+    # Handle redirects, otherwise standard Vary handling code from previous
+    # examples.
+    sub vcl_fetch {
+        if (req.http.X-UA-Device) {
+            if (!beresp.http.Vary) { # no Vary at all
+                set beresp.http.Vary = "X-UA-Device";
+            } elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
+                set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
+            }
+
+            # if the backend returns a redirect (think missing trailing slash),
+            # we will potentially show the extra address to the client. we
+            # don't want that.  if the backend reorders the get parameters, you
+            # may need to be smarter here. (? and & ordering)
+
+            if (beresp.status == 301 || beresp.status == 302 || beresp.status == 303) {
+                set beresp.http.location = regsub(beresp.http.location, "[?&]devicetype=.*$", "");
+            }
+        }
+        set beresp.http.X-UA-Device = req.http.X-UA-Device;
+    }
+    sub vcl_deliver {
+        if ((req.http.X-UA-Device) && (resp.http.Vary)) {
+            set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
+        }
+    }
+
+.. 073-example3-end
+
+Different backend for mobile clients
+------------------------------------
+
+If you have a different backend that serves pages for mobile clients, or any
+special needs in VCL, you can use the X-UA-Device header like this::
+
+    backend mobile {
+        .host = "10.0.0.1";
+        .port = "80";
+    }
+
+    sub vcl_recv {
+        # call some detection engine
+
+        if (req.http.X-UA-Device ~ "^mobile" || req.http.X-UA-device ~ "^tablet") {
+            set req.backend = mobile;
+        }
+    }
+
+Redirecting mobile clients
+--------------------------
+
+If you want to redirect mobile clients you can use the following snippet.
+
+.. 065-redir-mobile-start
+VCL::
+
+    sub vcl_recv {
+        # call some detection engine
+
+        if (req.http.X-UA-Device ~ "^mobile" || req.http.X-UA-device ~ "^tablet") {
+            error 750 "Moved Temporarily";
+        }
+    }
+     
+    sub vcl_error {
+        if (obj.status == 750) {
+            set obj.http.Location = "http://m.example.com" + req.url;
+            set obj.status = 302;
+            return(deliver);
+        }
+    }
+
+.. 065-redir-mobile-end
+
+
diff --git a/doc/sphinx/tutorial/index.rst b/doc/sphinx/tutorial/index.rst
index 4949aa0..91fdb17 100644
--- a/doc/sphinx/tutorial/index.rst
+++ b/doc/sphinx/tutorial/index.rst
@@ -30,6 +30,7 @@ separate topic. Good luck.
 	esi
 	virtualized
 	websockets
+	devicedetection
 	advanced_backend_servers
         handling_misbehaving_servers
         advanced_topics



More information about the varnish-commit mailing list