MINOR: cache: Create res.cache_hit and res.cache_name sample fetches

Res.cache_hit sample fetch returns a boolean which is true when the HTTP
response was built out of a cache. The cache's name is returned by the
res.cache_name sample_fetch.

This resolves GitHub issue #900.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 3e16fac..c243369 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -18195,6 +18195,15 @@
   useful (and usable) in the health-check context. It may be used in tcp-check
   based expect rules.
 
+res.cache_hit : boolean
+  Returns the boolean "true" value if the response has been built out of an
+  HTTP cache entry, otherwise returns boolean "false".
+
+res.cache_name : string
+  Returns a string containing the name of the HTTP cache that was used to
+  build the HTTP response if res.cache_hit is true, otherwise returns an
+  empty string.
+
 res.comp : boolean
   Returns the boolean "true" value if the response has been compressed by
   HAProxy, otherwise returns boolean "false". This may be used to add
diff --git a/reg-tests/cache/sample_fetches.vtc b/reg-tests/cache/sample_fetches.vtc
new file mode 100644
index 0000000..1ba0690
--- /dev/null
+++ b/reg-tests/cache/sample_fetches.vtc
@@ -0,0 +1,130 @@
+
+varnishtest "Basic cache test"
+
+#REQUIRE_VERSION=1.9
+
+feature ignore_unknown_macro
+
+server s1 {
+       rxreq
+       txresp -nolen -hdr "Transfer-Encoding: chunked"
+       chunkedlen 15
+       chunkedlen 15
+       chunkedlen 15
+       chunkedlen 0
+} -start
+
+server s2 {
+       rxreq
+       txresp -nolen -hdr "Transfer-Encoding: chunked"
+       chunkedlen 16
+       chunkedlen 16
+       chunkedlen 16
+       chunkedlen 0
+} -start
+
+server s3 {
+       rxreq
+       txresp -nolen -hdr "Transfer-Encoding: chunked"
+       chunkedlen 17
+       chunkedlen 17
+       chunkedlen 17
+       chunkedlen 0
+
+       rxreq
+       txresp -nolen -hdr "Transfer-Encoding: chunked"
+       chunkedlen 17
+       chunkedlen 17
+       chunkedlen 17
+       chunkedlen 0
+} -start
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        ${no-htx} option http-use-htx
+        timeout connect 1s
+        timeout client  1s
+        timeout server  1s
+
+    frontend fe
+        bind "fd@${fe}"
+        use_backend first_be if { path_beg /first }
+        use_backend nocache_be if { path_beg /nocache }
+        default_backend second_be
+
+    backend first_be
+        http-request cache-use first_cache
+        server www ${s1_addr}:${s1_port}
+        http-response cache-store first_cache
+        http-response set-header X-Cache-Hit %[res.cache_hit]
+        http-response set-header X-Cache-Name %[res.cache_name]
+
+    backend second_be
+        http-request cache-use second_cache
+        server www ${s2_addr}:${s2_port}
+        http-response cache-store second_cache
+        http-response set-header X-Cache-Hit %[res.cache_hit]
+        http-response set-header X-Cache-Name %[res.cache_name]
+
+    backend nocache_be
+        server www ${s3_addr}:${s3_port}
+        http-response set-header X-Cache-Hit %[res.cache_hit]
+        http-response set-header X-Cache-Name %[res.cache_name]
+
+    cache first_cache
+            total-max-size 3
+            max-age 40
+            max-object-size 3000
+
+    cache second_cache
+            total-max-size 3
+            max-age 20
+            max-object-size 3072
+} -start
+
+
+client c1 -connect ${h1_fe_sock} {
+        txreq -url "/first"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 45
+        expect resp.http.X-Cache-Hit == 0
+        expect resp.http.X-Cache-Name == ""
+
+        txreq -url "/second"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 48
+        expect resp.http.X-Cache-Hit == 0
+        expect resp.http.X-Cache-Name == ""
+
+        txreq -url "/nocache"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 51
+        expect resp.http.X-Cache-Hit == 0
+        expect resp.http.X-Cache-Name == ""
+
+        # Response should come form the cache now
+        txreq -url "/nocache"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 51
+        expect resp.http.X-Cache-Hit == 0
+        expect resp.http.X-Cache-Name == ""
+
+        txreq -url "/first"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 45
+        expect resp.http.X-Cache-Hit == 1
+        expect resp.http.X-Cache-Name == "first_cache"
+
+        txreq -url "/second"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 48
+        expect resp.http.X-Cache-Hit == 1
+        expect resp.http.X-Cache-Name == "second_cache"
+} -run
diff --git a/src/cache.c b/src/cache.c
index a98ca66..2c37ca1 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -28,6 +28,7 @@
 #include <haproxy/htx.h>
 #include <haproxy/net_helper.h>
 #include <haproxy/proxy.h>
+#include <haproxy/sample.h>
 #include <haproxy/shctx.h>
 #include <haproxy/stream.h>
 #include <haproxy/stream_interface.h>
@@ -1713,6 +1714,53 @@
 
 }
 
+
+/*
+ * boolean, returns true if response was built out of a cache entry.
+ */
+static int
+smp_fetch_res_cache_hit(const struct arg *args, struct sample *smp,
+                        const char *kw, void *private)
+{
+	smp->data.type = SMP_T_BOOL;
+	smp->data.u.sint = (smp->strm ? (smp->strm->target == &http_cache_applet.obj_type) : 0);
+
+	return 1;
+}
+
+/*
+ * string, returns cache name (if response came from a cache).
+ */
+static int
+smp_fetch_res_cache_name(const struct arg *args, struct sample *smp,
+                         const char *kw, void *private)
+{
+	struct appctx *appctx = NULL;
+
+	struct cache_flt_conf *cconf = NULL;
+	struct cache *cache = NULL;
+
+	if (!smp->strm || smp->strm->target != &http_cache_applet.obj_type)
+		return 0;
+
+	/* Get appctx from the stream_interface. */
+	appctx = si_appctx(&smp->strm->si[1]);
+	if (appctx && appctx->rule) {
+		cconf = appctx->rule->arg.act.p[0];
+		if (cconf) {
+			cache = cconf->c.cache;
+
+			smp->data.type = SMP_T_STR;
+			smp->flags = SMP_F_CONST;
+			smp->data.u.str.area = cache->id;
+			smp->data.u.str.data = strlen(cache->id);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
 /* Declare the filter parser for "cache" keyword */
 static struct flt_kw_list filter_kws = { "CACHE", { }, {
 		{ "cache", parse_cache_flt, NULL },
@@ -1757,3 +1805,14 @@
 /* config parsers for this section */
 REGISTER_CONFIG_SECTION("cache", cfg_parse_cache, cfg_post_parse_section_cache);
 REGISTER_POST_CHECK(post_check_cache);
+
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+		{ "res.cache_hit",  smp_fetch_res_cache_hit,  0, NULL, SMP_T_BOOL, SMP_USE_HRSHP, SMP_VAL_RESPONSE },
+		{ "res.cache_name", smp_fetch_res_cache_name, 0, NULL, SMP_T_STR,  SMP_USE_HRSHP, SMP_VAL_RESPONSE },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);