http_static: added last-modified header

Type: improvement

Change-Id: I492df92ef25f9c0cd57fc8980500b58bebaa94c6
Signed-off-by: Adrian Villin <avillin@cisco.com>
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index ffdcf93..872f4c2 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -553,6 +553,8 @@
 
 	content := "<html><body><p>Hello</p></body></html>"
 	content2 := "<html><body><p>Page</p></body></html>"
+	currentDate := time.Now().In(time.FixedZone("GMT", 0)).Format(http.TimeFormat)[:17]
+
 	vpp := s.GetContainerByName("vpp").VppInstance
 	vpp.Container.Exec("mkdir -p " + wwwRootPath)
 	err := vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
@@ -568,11 +570,16 @@
 	resp, err := client.Do(req)
 	s.AssertNil(err, fmt.Sprint(err))
 	defer resp.Body.Close()
+
 	s.Log(DumpHttpResp(resp, true))
 	s.AssertEqual(200, resp.StatusCode)
 	s.AssertContains(resp.Header.Get("Content-Type"), "html")
 	s.AssertContains(resp.Header.Get("Cache-Control"), "max-age="+max_age)
+	// only checking date
+	s.AssertContains(resp.Header.Get("Last-Modified"), currentDate)
+	s.AssertEqual(len(resp.Header.Get("Last-Modified")), 29)
 	s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+
 	body, err := io.ReadAll(resp.Body)
 	s.AssertNil(err, fmt.Sprint(err))
 	s.AssertEqual(string(body), content)
@@ -589,6 +596,7 @@
 	s.AssertContains(resp.Header.Get("Content-Type"), "html")
 	s.AssertContains(resp.Header.Get("Cache-Control"), "max-age="+max_age)
 	s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+
 	body, err = io.ReadAll(resp.Body)
 	s.AssertNil(err, fmt.Sprint(err))
 	s.AssertEqual(string(body), content)
@@ -603,6 +611,7 @@
 	s.AssertContains(resp.Header.Get("Content-Type"), "html")
 	s.AssertContains(resp.Header.Get("Cache-Control"), "max-age="+max_age)
 	s.AssertEqual(int64(len([]rune(content2))), resp.ContentLength)
+
 	body, err = io.ReadAll(resp.Body)
 	s.AssertNil(err, fmt.Sprint(err))
 	s.AssertEqual(string(body), content2)
@@ -828,7 +837,7 @@
 }
 
 func HttpStaticMacTimeTest(s *NoTopoSuite) {
-	currentDate := time.Now().In(time.FixedZone("GMT", 0)).Format(http.TimeFormat)
+	currentDate := time.Now().In(time.FixedZone("GMT", 0)).Format(http.TimeFormat)[:17]
 	vpp := s.GetContainerByName("vpp").VppInstance
 	serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
 	s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
@@ -848,6 +857,8 @@
 	s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Ip4AddressString())
 	s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
 	s.AssertContains(resp.Header.Get("Content-Type"), "json")
+	s.AssertEqual(len(resp.Header.Get("Date")), 29)
+	// only checking date
 	s.AssertContains(resp.Header.Get("Date"), currentDate)
 }
 
diff --git a/src/plugins/http_static/http_cache.c b/src/plugins/http_static/http_cache.c
index 7a069da..2e63e33 100644
--- a/src/plugins/http_static/http_cache.c
+++ b/src/plugins/http_static/http_cache.c
@@ -17,6 +17,8 @@
 #include <vppinfra/bihash_template.c>
 #include <vppinfra/unix.h>
 #include <vlib/vlib.h>
+#include <sys/stat.h>
+#include <vppinfra/time_range.h>
 
 static void
 hss_cache_lock (hss_cache_t *hc)
@@ -153,7 +155,7 @@
 
 static void
 hss_cache_attach_entry (hss_cache_t *hc, u32 ce_index, u8 **data,
-			u64 *data_len)
+			u64 *data_len, u8 **last_modified)
 {
   hss_cache_entry_t *ce;
 
@@ -162,6 +164,7 @@
   ce->inuse++;
   *data = ce->data;
   *data_len = vec_len (ce->data);
+  *last_modified = ce->last_modified;
 
   /* Update the cache entry, mark it in-use */
   lru_update (hc, ce, vlib_time_now (vlib_get_main ()));
@@ -209,16 +212,15 @@
 
 u32
 hss_cache_lookup_and_attach (hss_cache_t *hc, u8 *path, u8 **data,
-			     u64 *data_len)
+			     u64 *data_len, u8 **last_modified)
 {
   u32 ce_index;
-
   /* Make sure nobody removes the entry while we look it up */
   hss_cache_lock (hc);
 
   ce_index = hss_cache_lookup (hc, path);
   if (ce_index != ~0)
-    hss_cache_attach_entry (hc, ce_index, data, data_len);
+    hss_cache_attach_entry (hc, ce_index, data, data_len, last_modified);
 
   hss_cache_unlock (hc);
 
@@ -260,6 +262,7 @@
       hc->cache_evictions++;
       vec_free (ce->filename);
       vec_free (ce->data);
+      vec_free (ce->last_modified);
 
       if (hc->debug_level > 1)
 	clib_warning ("pool put index %d", ce - hc->cache_pool);
@@ -271,13 +274,15 @@
 }
 
 u32
-hss_cache_add_and_attach (hss_cache_t *hc, u8 *path, u8 **data, u64 *data_len)
+hss_cache_add_and_attach (hss_cache_t *hc, u8 *path, u8 **data, u64 *data_len,
+			  u8 **last_modified)
 {
   BVT (clib_bihash_kv) kv;
   hss_cache_entry_t *ce;
   clib_error_t *error;
   u8 *file_data;
   u32 ce_index;
+  struct stat dm;
 
   hss_cache_lock (hc);
 
@@ -298,11 +303,17 @@
   pool_get_zero (hc->cache_pool, ce);
   ce->filename = vec_dup (path);
   ce->data = file_data;
+  if (stat ((char *) path, &dm) == 0)
+    {
+      ce->last_modified =
+	format (0, "%U GMT", format_clib_timebase_time, (f64) dm.st_mtime);
+    }
 
   /* Attach cache entry without additional lock */
   ce->inuse++;
   *data = file_data;
   *data_len = vec_len (file_data);
+  *last_modified = ce->last_modified;
   lru_add (hc, ce, vlib_time_now (vlib_get_main ()));
 
   hc->cache_size += vec_len (ce->data);
@@ -364,6 +375,7 @@
       hc->cache_evictions++;
       vec_free (ce->filename);
       vec_free (ce->data);
+      vec_free (ce->last_modified);
       if (hc->debug_level > 1)
 	clib_warning ("pool put index %d", ce - hc->cache_pool);
       pool_put (hc->cache_pool, ce);
diff --git a/src/plugins/http_static/http_cache.h b/src/plugins/http_static/http_cache.h
index a89ed5e..21f71a9 100644
--- a/src/plugins/http_static/http_cache.h
+++ b/src/plugins/http_static/http_cache.h
@@ -22,6 +22,9 @@
 {
   /** Name of the file */
   u8 *filename;
+  /** Last modified date, format:
+   *  <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT  */
+  u8 *last_modified;
   /** Contents of the file, as a u8 * vector */
   u8 *data;
   /** Last time the cache entry was used */
@@ -58,9 +61,9 @@
 } hss_cache_t;
 
 u32 hss_cache_lookup_and_attach (hss_cache_t *hc, u8 *path, u8 **data,
-				 u64 *data_len);
+				 u64 *data_len, u8 **last_modified);
 u32 hss_cache_add_and_attach (hss_cache_t *hc, u8 *path, u8 **data,
-			      u64 *data_len);
+			      u64 *data_len, u8 **last_modified);
 void hss_cache_detach_entry (hss_cache_t *hc, u32 ce_index);
 u32 hss_cache_clear (hss_cache_t *hc);
 void hss_cache_init (hss_cache_t *hc, uword cache_size, u8 debug_level);
diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c
index 40de6cb..674ce8a 100644
--- a/src/plugins/http_static/static_server.c
+++ b/src/plugins/http_static/static_server.c
@@ -411,6 +411,7 @@
   u8 *path, *sanitized_path;
   u32 ce_index;
   http_content_type_t type;
+  u8 *last_modified;
 
   /* Feature not enabled */
   if (!hsm->www_root)
@@ -435,8 +436,8 @@
 
   hs->data_offset = 0;
 
-  ce_index =
-    hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
+  ce_index = hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data,
+					  &hs->data_len, &last_modified);
   if (ce_index == ~0)
     {
       if (!file_path_is_valid (path))
@@ -455,8 +456,8 @@
 	  sc = try_index_file (hsm, hs, path);
 	  goto done;
 	}
-      ce_index =
-	hss_cache_add_and_attach (&hsm->cache, path, &hs->data, &hs->data_len);
+      ce_index = hss_cache_add_and_attach (&hsm->cache, path, &hs->data,
+					   &hs->data_len, &last_modified);
       if (ce_index == ~0)
 	{
 	  sc = HTTP_STATUS_INTERNAL_ERROR;
@@ -478,6 +479,9 @@
   http_add_header (
     &hs->resp_headers, http_header_name_token (HTTP_HEADER_CACHE_CONTROL),
     (const char *) hsm->max_age_formatted, vec_len (hsm->max_age_formatted));
+  http_add_header (&hs->resp_headers,
+		   http_header_name_token (HTTP_HEADER_LAST_MODIFIED),
+		   (const char *) last_modified, vec_len (last_modified));
 
 done:
   vec_free (sanitized_path);