http_static: make max-age configurable 16/41416/10
authorAdrian Villin <[email protected]>
Thu, 8 Aug 2024 06:56:34 +0000 (08:56 +0200)
committerFlorin Coras <[email protected]>
Tue, 20 Aug 2024 18:19:12 +0000 (18:19 +0000)
Type: improvement

Change-Id: I629add6e3f4219d56610c3785013f69dbe847844
Signed-off-by: Adrian Villin <[email protected]>
extras/hs-test/http_test.go
src/plugins/http_static/http_static.api
src/plugins/http_static/http_static.c
src/plugins/http_static/http_static.h
src/plugins/http_static/http_static_test.c
src/plugins/http_static/static_server.c
test/asf/test_http_static.py

index 5b2b65f..f8b0fb7 100644 (file)
@@ -28,7 +28,7 @@ func init() {
                HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
                HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
                HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
-               HttpHeadersTest, HttpStaticFileHandlerTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
+               HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
                HttpClientPostFileTest, HttpClientPostFilePtrTest, AuthorityFormTargetTest)
        RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest, PromConcurrentConnectionsTest,
                PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest)
@@ -535,7 +535,23 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) {
 
 }
 
+func HttpStaticFileHandlerDefaultMaxAgeTest(s *NoTopoSuite) {
+       HttpStaticFileHandlerTestFunction(s, "default")
+}
+
 func HttpStaticFileHandlerTest(s *NoTopoSuite) {
+       HttpStaticFileHandlerTestFunction(s, "123")
+}
+
+func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) {
+       var max_age_formatted string
+       if max_age == "default" {
+               max_age_formatted = ""
+               max_age = "600"
+       } else {
+               max_age_formatted = "max-age " + max_age
+       }
+
        content := "<html><body><p>Hello</p></body></html>"
        content2 := "<html><body><p>Page</p></body></html>"
        vpp := s.GetContainerByName("vpp").VppInstance
@@ -545,7 +561,7 @@ func HttpStaticFileHandlerTest(s *NoTopoSuite) {
        err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
        s.AssertNil(err, fmt.Sprint(err))
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
-       s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m"))
+       s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m " + max_age_formatted))
 
        client := NewHttpClient()
        req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil)
@@ -556,7 +572,7 @@ func HttpStaticFileHandlerTest(s *NoTopoSuite) {
        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=")
+       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))
@@ -572,7 +588,7 @@ func HttpStaticFileHandlerTest(s *NoTopoSuite) {
        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=")
+       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))
@@ -586,7 +602,7 @@ func HttpStaticFileHandlerTest(s *NoTopoSuite) {
        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=")
+       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))
index 4d6d8bf..dd4f513 100644 (file)
@@ -2,7 +2,8 @@
 /** \file
     This file defines static http server control-plane API messages
 */
-option version = "2.1.0";
+
+option version = "2.2.0";
 
 /** \brief Configure and enable the static http server
     @param client_index - opaque cookie to identify the sender
@@ -16,6 +17,39 @@ option version = "2.1.0";
 */
 
 autoreply define http_static_enable {
+    option deprecated;
+
+    /* Client identifier, set from api_main.my_client_index */
+    u32 client_index;
+
+    /* Arbitrary context, so client can match reply to request */
+    u32 context;
+    /* Typical options */
+    u32 fifo_size;
+    u32 cache_size_limit;
+    /* Unusual options */
+    u32 prealloc_fifos;
+    u32 private_segment_size;
+
+    /* Root of the html path */
+    string www_root[256];
+    /* The bind URI */
+    string uri[256];
+};
+
+/** \brief Configure and enable the static http server
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param fifo_size - size (in bytes) of the session FIFOs
+    @param cache_size_limit - size (in bytes) of the in-memory file data cache
+    @param max_age - how long a response is considered fresh (in seconds)
+    @param prealloc_fifos - number of preallocated fifos (usually 0)
+    @param private_segment_size - fifo segment size (usually 0)
+    @param www_root - html root path
+    @param uri - bind URI, defaults to "tcp://0.0.0.0/80"
+*/
+
+autoreply define http_static_enable_v2 {
     /* Client identifier, set from api_main.my_client_index */
     u32 client_index;
 
@@ -24,6 +58,7 @@ autoreply define http_static_enable {
     /* Typical options */
     u32 fifo_size;
     u32 cache_size_limit;
+    u32 max_age [default=600];
     /* Unusual options */
     u32 prealloc_fifos;
     u32 private_segment_size;
index 8f8fe37..9a98763 100644 (file)
@@ -66,7 +66,7 @@ hss_register_url_handler (hss_url_handler_fn fp, const char *url,
  */
 static int
 hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
-               u32 private_segment_size, u8 *www_root, u8 *uri)
+               u32 private_segment_size, u8 *www_root, u8 *uri, u32 max_age)
 {
   hss_main_t *hsm = &hss_main;
   int rv;
@@ -77,6 +77,7 @@ hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
   hsm->private_segment_size = private_segment_size;
   hsm->www_root = format (0, "%s%c", www_root, 0);
   hsm->uri = format (0, "%s%c", uri, 0);
+  hsm->max_age = max_age;
 
   if (vec_len (hsm->www_root) < 2)
     return VNET_API_ERROR_INVALID_VALUE;
@@ -110,14 +111,33 @@ static void vl_api_http_static_enable_t_handler
   mp->uri[ARRAY_LEN (mp->uri) - 1] = 0;
   mp->www_root[ARRAY_LEN (mp->www_root) - 1] = 0;
 
-  rv =
-    hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
-                   ntohl (mp->prealloc_fifos),
-                   ntohl (mp->private_segment_size), mp->www_root, mp->uri);
+  rv = hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
+                      ntohl (mp->prealloc_fifos),
+                      ntohl (mp->private_segment_size), mp->www_root, mp->uri,
+                      HSS_DEFAULT_MAX_AGE);
 
   REPLY_MACRO (VL_API_HTTP_STATIC_ENABLE_REPLY);
 }
 
+/* API message handler */
+static void
+vl_api_http_static_enable_v2_t_handler (vl_api_http_static_enable_v2_t *mp)
+{
+  vl_api_http_static_enable_v2_reply_t *rmp;
+  hss_main_t *hsm = &hss_main;
+  int rv;
+
+  mp->uri[ARRAY_LEN (mp->uri) - 1] = 0;
+  mp->www_root[ARRAY_LEN (mp->www_root) - 1] = 0;
+
+  rv = hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
+                      ntohl (mp->prealloc_fifos),
+                      ntohl (mp->private_segment_size), mp->www_root, mp->uri,
+                      ntohl (mp->max_age));
+
+  REPLY_MACRO (VL_API_HTTP_STATIC_ENABLE_V2_REPLY);
+}
+
 #include <http_static/http_static.api.c>
 static clib_error_t *
 hss_api_init (vlib_main_t *vm)
index 4c22e1d..f936480 100644 (file)
@@ -23,6 +23,8 @@
 #include <vppinfra/error.h>
 #include <http_static/http_cache.h>
 
+#define HSS_DEFAULT_MAX_AGE 600
+
 /** @file http_static.h
  * Static http server definitions
  */
@@ -156,6 +158,10 @@ typedef struct
   u8 enable_url_handlers;
   /** Max cache size before LRU occurs */
   u64 cache_size;
+  /** How long a response is considered fresh (in seconds) */
+  u32 max_age;
+  /** Formatted max_age: "max-age=xyz" */
+  u8 *max_age_formatted;
 
   /** hash table of file extensions to mime types string indices */
   uword *mime_type_indices_by_file_extensions;
index 3503a1b..f701c8b 100644 (file)
@@ -18,6 +18,7 @@
 #include <vlibapi/api.h>
 #include <vlibmemory/api.h>
 #include <vppinfra/error.h>
+#include <http_static/http_static.h>
 
 uword unformat_sw_if_index (unformat_input_t * input, va_list * args);
 
@@ -126,6 +127,96 @@ api_http_static_enable (vat_main_t * vam)
   return ret;
 }
 
+static int
+api_http_static_enable_v2 (vat_main_t *vam)
+{
+  unformat_input_t *line_input = vam->input;
+  vl_api_http_static_enable_v2_t *mp;
+  u64 tmp;
+  u8 *www_root = 0;
+  u8 *uri = 0;
+  u32 prealloc_fifos = 0;
+  u32 private_segment_size = 0;
+  u32 fifo_size = 8 << 10;
+  u32 cache_size_limit = 1 << 20;
+  u32 max_age = HSS_DEFAULT_MAX_AGE;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "www-root %s", &www_root))
+       ;
+      else if (unformat (line_input, "prealloc-fifos %d", &prealloc_fifos))
+       ;
+      else if (unformat (line_input, "private-segment-size %U",
+                        unformat_memory_size, &tmp))
+       {
+         if (tmp >= 0x100000000ULL)
+           {
+             errmsg ("private segment size %llu, too large", tmp);
+             return -99;
+           }
+         private_segment_size = (u32) tmp;
+       }
+      else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
+                        &tmp))
+       {
+         if (tmp >= 0x100000000ULL)
+           {
+             errmsg ("fifo-size %llu, too large", tmp);
+             return -99;
+           }
+         fifo_size = (u32) tmp;
+       }
+      else if (unformat (line_input, "cache-size %U", unformat_memory_size,
+                        &tmp))
+       {
+         if (tmp < (128ULL << 10))
+           {
+             errmsg ("cache-size must be at least 128kb");
+             return -99;
+           }
+         cache_size_limit = (u32) tmp;
+       }
+      else if (unformat (line_input, "max-age %d", &max_age))
+       ;
+      else if (unformat (line_input, "uri %s", &uri))
+       ;
+      else
+       {
+         errmsg ("unknown input `%U'", format_unformat_error, line_input);
+         return -99;
+       }
+    }
+
+  if (www_root == 0)
+    {
+      errmsg ("Must specify www-root");
+      return -99;
+    }
+
+  if (uri == 0)
+    uri = format (0, "tcp://0.0.0.0/80%c", 0);
+
+  /* Construct the API message */
+  M (HTTP_STATIC_ENABLE_V2, mp);
+  strncpy_s ((char *) mp->www_root, 256, (const char *) www_root, 256);
+  strncpy_s ((char *) mp->uri, 256, (const char *) uri, 256);
+  mp->fifo_size = ntohl (fifo_size);
+  mp->cache_size_limit = ntohl (cache_size_limit);
+  mp->prealloc_fifos = ntohl (prealloc_fifos);
+  mp->private_segment_size = ntohl (private_segment_size);
+  mp->max_age = ntohl (max_age);
+
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+  return ret;
+}
+
 #include <http_static/http_static.api_test.c>
 
 /*
index b47fc9c..40de6cb 100644 (file)
@@ -475,10 +475,9 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   http_add_header (&hs->resp_headers,
                   http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
                   http_content_type_token (type));
-  /* TODO configurable max-age value */
-  http_add_header (&hs->resp_headers,
-                  http_header_name_token (HTTP_HEADER_CACHE_CONTROL),
-                  http_token_lit ("max-age=600"));
+  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));
 
 done:
   vec_free (sanitized_path);
@@ -863,6 +862,8 @@ hss_create (vlib_main_t *vm)
   if (hsm->enable_url_handlers)
     hss_url_handlers_init (hsm);
 
+  hsm->max_age_formatted = format (0, "max-age=%d", hsm->max_age);
+
   return 0;
 }
 
@@ -883,6 +884,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
   hsm->private_segment_size = 0;
   hsm->fifo_size = 0;
   hsm->cache_size = 10 << 20;
+  hsm->max_age = HSS_DEFAULT_MAX_AGE;
 
   /* Get a line of input. */
   if (!unformat_user (input, unformat_line_input, line_input))
@@ -914,6 +916,8 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
        ;
       else if (unformat (line_input, "url-handlers"))
        hsm->enable_url_handlers = 1;
+      else if (unformat (line_input, "max-age %d", &hsm->max_age))
+       ;
       else
        {
          error = clib_error_return (0, "unknown input `%U'",
@@ -971,8 +975,8 @@ VLIB_CLI_COMMAND (hss_create_command, static) = {
   .path = "http static server",
   .short_help =
     "http static server www-root <path> [prealloc-fifos <nn>]\n"
-    "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
-    "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
+    "[private-segment-size <nnMG>] [fifo-size <nbytes>] [max-age <nseconds>]\n"
+    "[uri <uri>] [ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
   .function = hss_create_command_fn,
 };
 
index 18e8ba5..73a95e9 100644 (file)
@@ -63,11 +63,13 @@ class TestHttpStaticVapi(VppAsfTestCase):
                 "exec",
                 "HttpStatic",
                 "curl",
+                "-v",
                 f"10.10.1.2/{self.temp.name[5:]}",
             ],
             capture_output=True,
         )
         self.assertIn(b"Hello world", process.stdout)
+        self.assertIn(b"max-age=600", process.stderr)
 
         self.temp2.seek(0)
         process = subprocess.run(