http_static: support multiple listeners 80/42680/9
authorFlorin Coras <[email protected]>
Fri, 4 Apr 2025 21:56:18 +0000 (17:56 -0400)
committerDave Barach <[email protected]>
Mon, 7 Apr 2025 19:03:27 +0000 (19:03 +0000)
Add support for multiple listeners that can be added and removed via
"http static listener" cli.

Type: feature

Change-Id: I024d1bf8b02b2046f1757656944b2d48662a1254
Signed-off-by: Florin Coras <[email protected]>
src/plugins/http_static/http_cache.c
src/plugins/http_static/http_cache.h
src/plugins/http_static/http_static.c
src/plugins/http_static/http_static.h
src/plugins/http_static/static_server.c
test/asf/test_http_static.py

index 2e63e33..61f1f50 100644 (file)
@@ -400,6 +400,14 @@ hss_cache_init (hss_cache_t *hc, uword cache_size, u8 debug_level)
   hc->first_index = hc->last_index = ~0;
 }
 
+void
+hss_cache_free (hss_cache_t *hc)
+{
+  hss_cache_clear (hc);
+  BV (clib_bihash_free) (&hc->name_to_data);
+  clib_spinlock_free (&hc->cache_lock);
+}
+
 /** \brief format a file cache entry
  */
 static u8 *
index 21f71a9..c1e3634 100644 (file)
@@ -67,6 +67,7 @@ u32 hss_cache_add_and_attach (hss_cache_t *hc, u8 *path, u8 **data,
 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);
+void hss_cache_free (hss_cache_t *hc);
 
 u8 *format_hss_cache (u8 *s, va_list *args);
 
index 7a12f37..2574f65 100644 (file)
@@ -73,16 +73,18 @@ hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
   int rv;
 
   hsm->fifo_size = fifo_size;
-  hsm->cache_size = cache_limit;
   hsm->prealloc_fifos = 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;
-  hsm->max_body_size = max_body_size;
-  hsm->keepalive_timeout = keepalive_timeout;
-
-  if (vec_len (hsm->www_root) < 2)
+  if (uri && parse_uri ((char *) uri, &hsm->default_listener.sep))
+    return VNET_API_ERROR_INVALID_VALUE;
+  hsm->default_listener.www_root = format (0, "%s%c", www_root, 0);
+  hsm->default_listener.cache_size = cache_limit;
+  hsm->default_listener.max_age = max_age;
+  hsm->default_listener.max_body_size = max_body_size;
+  hsm->default_listener.keepalive_timeout = keepalive_timeout;
+  hsm->have_default_listener = 1;
+
+  if (vec_len (hsm->default_listener.www_root) < 2)
     return VNET_API_ERROR_INVALID_VALUE;
 
   if (hsm->app_index != ~0)
@@ -99,8 +101,7 @@ hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
     case 0:
       break;
     default:
-      vec_free (hsm->www_root);
-      vec_free (hsm->uri);
+      vec_free (hsm->default_listener.www_root);
       return VNET_API_ERROR_INIT_FAILED;
     }
   return 0;
index 2b6de03..25ee195 100644 (file)
@@ -42,6 +42,8 @@ typedef struct hss_session_
   /** vpp session index, handle */
   u32 vpp_session_index;
   session_handle_t vpp_session_handle;
+  /** Index of listener for which connection was accepted */
+  u32 listener_index;
   u8 *target_path;
   u8 *target_query;
   http_req_method_t rt;
@@ -63,6 +65,8 @@ typedef struct hss_session_
   u8 *headers_buf;
   /** POST body left to receive */
   u64 left_recv;
+  /** threshold for switching to pointers */
+  u64 use_ptr_thresh;
   int (*read_body_handler) (struct hss_session_ *hs, session_t *ts);
 } hss_session_t;
 
@@ -119,6 +123,34 @@ typedef hss_url_handler_rc_t (*hss_url_handler_fn) (hss_url_handler_args_t *);
 typedef void (*hss_register_url_fn) (hss_url_handler_fn, char *, int);
 typedef void (*hss_session_send_fn) (hss_url_handler_args_t *args);
 
+typedef struct hss_listener_
+{
+  /** Path to file hash table */
+  hss_cache_t cache;
+  /** The bind session endpoint e.g., tcp://0.0.0.0:80 */
+  session_endpoint_cfg_t sep;
+  /** root path to be served */
+  u8 *www_root;
+  /** Threshold for switching to ptr data in http msgs */
+  u64 use_ptr_thresh;
+  /** Max cache size before LRU occurs */
+  u64 cache_size;
+  /** Maximum size of a request body (in bytes) **/
+  u64 max_body_size;
+  /** Timeout during which client connection will stay open */
+  u32 keepalive_timeout;
+  /** How long a response is considered fresh (in seconds) */
+  u32 max_age;
+  /** Formatted max_age: "max-age=xyz" */
+  u8 *max_age_formatted;
+  /** Enable the use of builtinurls */
+  u8 enable_url_handlers;
+  /** Index in listener pool */
+  u32 l_index;
+  /** Listener session handle */
+  session_handle_t session_handle;
+} hss_listener_t;
+
 /** \brief Main data structure
  */
 typedef struct
@@ -126,15 +158,13 @@ typedef struct
   /** Per thread vector of session pools */
   hss_session_t **sessions;
 
+  /** Listeners pool */
+  hss_listener_t *listeners;
+
   /** Hash tables for built-in GET and POST handlers */
   uword *get_url_handlers;
   uword *post_url_handlers;
 
-  hss_cache_t cache;
-
-  /** root path to be served */
-  u8 *www_root;
-
   /** Application index */
   u32 app_index;
 
@@ -150,6 +180,11 @@ typedef struct
    * Config
    */
 
+  /** Listener configured with server, if any */
+  hss_listener_t default_listener;
+  u8 have_default_listener;
+  u8 is_init;
+
   /** Enable debug messages */
   int debug_level;
   /** Number of preallocated fifos, usually 0 */
@@ -158,22 +193,6 @@ typedef struct
   u64 private_segment_size;
   /** Size of the allocated rx, tx fifos, roughly 8K or so */
   u32 fifo_size;
-  /** The bind URI, defaults to tcp://0.0.0.0/80 */
-  u8 *uri;
-  /** Threshold for switching to ptr data in http msgs */
-  u64 use_ptr_thresh;
-  /** Enable the use of builtinurls */
-  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;
-  /** Maximum size of a request body (in bytes) **/
-  u64 max_body_size;
-  /** Formatted max_age: "max-age=xyz" */
-  u8 *max_age_formatted;
-  /** Timeout during which client connection will stay open */
-  u32 keepalive_timeout;
 
   /** hash table of file extensions to mime types string indices */
   uword *mime_type_indices_by_file_extensions;
@@ -183,6 +202,16 @@ extern hss_main_t hss_main;
 
 int hss_create (vlib_main_t *vm);
 
+static inline hss_listener_t *
+hss_listener_get (u32 l_index)
+{
+  hss_main_t *hsm = &hss_main;
+
+  if (pool_is_free_index (hsm->listeners, l_index))
+    return 0;
+  return pool_elt_at_index (hsm->listeners, l_index);
+}
+
 /**
  * Register a GET or POST URL handler
  */
index 75481f8..c21658c 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include <http_static/http_static.h>
+#include <vnet/session/application.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -134,7 +135,7 @@ start_send_data (hss_session_t *hs, http_status_code_t status)
   msg.data.headers_len = hs->resp_headers.tail_offset;
   msg.data.len = msg.data.body_len + msg.data.headers_len;
 
-  if (msg.data.len > hss_main.use_ptr_thresh)
+  if (msg.data.len > hs->use_ptr_thresh)
     {
       msg.data.type = HTTP_MSG_DATA_PTR;
       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
@@ -294,7 +295,7 @@ try_url_handler (hss_session_t *hs)
 
   target_path = hs->target_path;
 
-  if (!hsm->enable_url_handlers || !target_path)
+  if (!target_path)
     return -1;
 
   /* zero-length? try "index.html" */
@@ -389,8 +390,9 @@ file_path_is_valid (u8 *path)
 }
 
 static u32
-try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
+try_index_file (hss_listener_t *l, hss_session_t *hs, u8 *path)
 {
+  hss_main_t *hsm = &hss_main;
   u8 *port_str = 0, *redirect;
   transport_endpoint_t endpt;
   transport_proto_t proto;
@@ -418,7 +420,7 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
   /*
    * We found an index.html file, build a redirect
    */
-  vec_delete (path, vec_len (hsm->www_root) - 1, 0);
+  vec_delete (path, vec_len (l->www_root) - 1, 0);
 
   ts = session_get (hs->vpp_session_index, hs->thread_index);
   session_get_endpoint (ts, &endpt, 1 /* is_local */);
@@ -463,10 +465,13 @@ try_file_handler (hss_session_t *hs)
   u32 ce_index, max_dequeue;
   http_content_type_t type;
   u8 *last_modified;
+  hss_listener_t *l;
   session_t *ts;
 
+  l = hss_listener_get (hs->listener_index);
+
   /* Feature not enabled */
-  if (!hsm->www_root)
+  if (!l->www_root)
     return -1;
 
   /* Discard request body */
@@ -492,9 +497,9 @@ try_file_handler (hss_session_t *hs)
    * Construct the file to open
    */
   if (!sanitized_path)
-    path = format (0, "%s%c", hsm->www_root, 0);
+    path = format (0, "%s%c", l->www_root, 0);
   else
-    path = format (0, "%s/%s%c", hsm->www_root, sanitized_path, 0);
+    path = format (0, "%s/%s%c", l->www_root, sanitized_path, 0);
 
   if (hsm->debug_level > 0)
     clib_warning ("%s '%s'", (hs->rt == HTTP_REQ_GET) ? "GET" : "POST", path);
@@ -504,7 +509,7 @@ try_file_handler (hss_session_t *hs)
 
   hs->data_offset = 0;
 
-  ce_index = hss_cache_lookup_and_attach (&hsm->cache, path, &hs->data,
+  ce_index = hss_cache_lookup_and_attach (&l->cache, path, &hs->data,
                                          &hs->data_len, &last_modified);
   if (ce_index == ~0)
     {
@@ -521,10 +526,10 @@ try_file_handler (hss_session_t *hs)
              sc = HTTP_STATUS_NOT_FOUND;
              goto done;
            }
-         sc = try_index_file (hsm, hs, path);
+         sc = try_index_file (l, hs, path);
          goto done;
        }
-      ce_index = hss_cache_add_and_attach (&hsm->cache, path, &hs->data,
+      ce_index = hss_cache_add_and_attach (&l->cache, path, &hs->data,
                                           &hs->data_len, &last_modified);
       if (ce_index == ~0)
        {
@@ -545,8 +550,8 @@ try_file_handler (hss_session_t *hs)
   if (hss_add_header (hs, HTTP_HEADER_CONTENT_TYPE,
                      http_content_type_token (type)) ||
       hss_add_header (hs, HTTP_HEADER_CACHE_CONTROL,
-                     (const char *) hsm->max_age_formatted,
-                     vec_len (hsm->max_age_formatted)) ||
+                     (const char *) l->max_age_formatted,
+                     vec_len (l->max_age_formatted)) ||
       hss_add_header (hs, HTTP_HEADER_LAST_MODIFIED,
                      (const char *) last_modified, vec_len (last_modified)))
     {
@@ -565,7 +570,18 @@ done:
 static void
 handle_request (hss_session_t *hs)
 {
-  if (!try_url_handler (hs))
+  hss_listener_t *l;
+
+  l = hss_listener_get (hs->listener_index);
+
+  if (hs->left_recv > l->max_body_size)
+    {
+      start_send_data (hs, HTTP_STATUS_CONTENT_TOO_LARGE);
+      hss_session_disconnect_transport (hs);
+      return;
+    }
+
+  if (l->enable_url_handlers && !try_url_handler (hs))
     return;
 
   if (!try_file_handler (hs))
@@ -593,7 +609,6 @@ file_handler_discard_body (hss_session_t *hs, session_t *ts)
 static int
 hss_ts_rx_callback (session_t *ts)
 {
-  hss_main_t *hsm = &hss_main;
   hss_session_t *hs;
   http_msg_t msg;
   int rv;
@@ -663,12 +678,6 @@ hss_ts_rx_callback (session_t *ts)
 
   if (msg.data.body_len && msg.method_type == HTTP_REQ_POST)
     {
-      if (msg.data.body_len > hsm->max_body_size)
-       {
-         start_send_data (hs, HTTP_STATUS_CONTENT_TOO_LARGE);
-         goto err_done;
-       }
-
       hs->left_recv = msg.data.body_len;
       /* drop everything up to body */
       svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.body_offset);
@@ -726,6 +735,7 @@ static int
 hss_ts_accept_callback (session_t *ts)
 {
   hss_session_t *hs;
+  session_t *ls;
   u32 thresh;
 
   hs = hss_session_alloc (ts->thread_index);
@@ -733,6 +743,11 @@ hss_ts_accept_callback (session_t *ts)
   hs->vpp_session_index = ts->session_index;
   hs->vpp_session_handle = session_handle (ts);
 
+  /* Link to listener context */
+  ls = listen_session_get_from_handle (ts->listener_handle);
+  hs->listener_index = ls->opaque;
+  hs->use_ptr_thresh = hss_listener_get (hs->listener_index)->use_ptr_thresh;
+
   /* The application sets a threshold for it's fifo to get notified when
    * additional data can be enqueued. We want to keep the TX fifo reasonably
    * full, however avoid entering a state where the
@@ -788,7 +803,6 @@ hss_add_segment_callback (u32 client_index, u64 segment_handle)
 static void
 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
 {
-  hss_main_t *hsm = &hss_main;
   hss_session_t *hs;
 
   if (ntf == SESSION_CLEANUP_TRANSPORT)
@@ -800,7 +814,9 @@ hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
 
   if (hs->cache_pool_index != ~0)
     {
-      hss_cache_detach_entry (&hsm->cache, hs->cache_pool_index);
+      hss_listener_t *l = hss_listener_get (hs->listener_index);
+      if (l)
+       hss_cache_detach_entry (&l->cache, hs->cache_pool_index);
       hs->cache_pool_index = ~0;
     }
 
@@ -885,30 +901,22 @@ hss_transport_needs_crypto (transport_proto_t proto)
 }
 
 static int
-hss_listen (void)
+hss_listen (hss_listener_t *l, session_handle_t *lh)
 {
   hss_main_t *hsm = &hss_main;
-  session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
   vnet_listen_args_t _a, *a = &_a;
-  char *uri = "tcp://0.0.0.0/80";
   u8 need_crypto;
   transport_endpt_ext_cfg_t *ext_cfg;
   int rv;
-  transport_endpt_cfg_http_t http_cfg = { hsm->keepalive_timeout, 0 };
+  transport_endpt_cfg_http_t http_cfg = { l->keepalive_timeout, 0 };
 
   clib_memset (a, 0, sizeof (*a));
   a->app_index = hsm->app_index;
 
-  if (hsm->uri)
-    uri = (char *) hsm->uri;
-
-  if (parse_uri (uri, &sep))
-    return -1;
-
-  need_crypto = hss_transport_needs_crypto (sep.transport_proto);
+  need_crypto = hss_transport_needs_crypto (l->sep.transport_proto);
 
-  sep.transport_proto = TRANSPORT_PROTO_HTTP;
-  clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
+  l->sep.transport_proto = TRANSPORT_PROTO_HTTP;
+  clib_memcpy (&a->sep_ext, &l->sep, sizeof (l->sep));
 
   ext_cfg = session_endpoint_add_ext_cfg (
     &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_HTTP, sizeof (http_cfg));
@@ -922,7 +930,8 @@ hss_listen (void)
       ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
     }
 
-  rv = vnet_listen (a);
+  if (!(rv = vnet_listen (a)))
+    *lh = a->handle;
 
   session_endpoint_free_ext_cfgs (&a->sep_ext);
 
@@ -932,13 +941,75 @@ hss_listen (void)
 static void
 hss_url_handlers_init (hss_main_t *hsm)
 {
-  if (!hsm->get_url_handlers)
+  if (hsm->get_url_handlers)
+    return;
+
+  hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
+  hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
+  hss_builtinurl_json_handlers_init ();
+}
+
+int
+hss_listener_add (hss_listener_t *l_cfg)
+{
+  hss_main_t *hsm = &hss_main;
+  session_handle_t lh;
+  app_listener_t *al;
+  hss_listener_t *l;
+  session_t *ls;
+
+  if (hss_listen (l_cfg, &lh))
     {
-      hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
-      hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
+      clib_warning ("failed to start listening");
+      return -1;
     }
 
-  hss_builtinurl_json_handlers_init ();
+  pool_get (hsm->listeners, l);
+  *l = *l_cfg;
+  l->l_index = l - hsm->listeners;
+  l->session_handle = lh;
+
+  al = app_listener_get_w_handle (lh);
+  ls = app_listener_get_session (al);
+  ls->opaque = l->l_index;
+
+  if (l->www_root)
+    hss_cache_init (&l->cache, l->cache_size, hsm->debug_level);
+  if (l->enable_url_handlers)
+    hss_url_handlers_init (hsm);
+
+  l->max_age_formatted = format (0, "max-age=%d", l->max_age);
+
+  return 0;
+}
+
+int
+hss_listener_del (hss_listener_t *l_cfg)
+{
+  hss_main_t *hsm = &hss_main;
+  hss_listener_t *l;
+  u8 found = 0;
+
+  pool_foreach (l, hsm->listeners)
+    {
+      if (clib_memcmp (&l_cfg->sep, &l->sep, sizeof (l_cfg->sep)) == 0)
+       {
+         found = 1;
+         break;
+       }
+    }
+
+  if (!found)
+    return -1;
+
+  vnet_unlisten_args_t args = { .handle = l->session_handle, hsm->app_index };
+
+  vec_free (l->www_root);
+  vec_free (l->max_age_formatted);
+  hss_cache_free (&l->cache);
+  pool_put (hsm->listeners, l);
+
+  return vnet_unlisten (&args);
 }
 
 int
@@ -951,24 +1022,25 @@ hss_create (vlib_main_t *vm)
   num_threads = 1 /* main thread */  + vtm->n_threads;
   vec_validate (hsm->sessions, num_threads - 1);
 
+  /* Make sure session layer is enabled */
+  session_enable_disable_args_t args = { .is_en = 1,
+                                        .rt_engine_type =
+                                          RT_BACKEND_ENGINE_RULE_TABLE };
+  vnet_session_enable_disable (vm, &args);
+
   if (hss_attach ())
     {
       clib_warning ("failed to attach server");
       return -1;
     }
-  if (hss_listen ())
+
+  if (hsm->have_default_listener && hss_listener_add (&hsm->default_listener))
     {
       clib_warning ("failed to start listening");
       return -1;
     }
 
-  if (hsm->www_root)
-    hss_cache_init (&hsm->cache, hsm->cache_size, hsm->debug_level);
-
-  if (hsm->enable_url_handlers)
-    hss_url_handlers_init (hsm);
-
-  hsm->max_age_formatted = format (0, "max-age=%d", hsm->max_age);
+  hsm->is_init = 1;
 
   return 0;
 }
@@ -979,20 +1051,23 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
 {
   unformat_input_t _line_input, *line_input = &_line_input;
   hss_main_t *hsm = &hss_main;
+  hss_listener_t *l = &hsm->default_listener;
   clib_error_t *error = 0;
+  char *uri = 0;
   u64 seg_size;
   int rv;
 
   if (hsm->app_index != (u32) ~0)
-    return clib_error_return (0, "http server already running...");
+    return clib_error_return (0, "http static server already initialized...");
 
   hsm->prealloc_fifos = 0;
   hsm->private_segment_size = 0;
   hsm->fifo_size = 0;
-  hsm->cache_size = 10 << 20;
-  hsm->max_age = HSS_DEFAULT_MAX_AGE;
-  hsm->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE;
-  hsm->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT;
+
+  l->cache_size = 10 << 20;
+  l->max_age = HSS_DEFAULT_MAX_AGE;
+  l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE;
+  l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT;
 
   /* Get a line of input. */
   if (!unformat_user (input, unformat_line_input, line_input))
@@ -1000,38 +1075,40 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
 
   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
     {
-      if (unformat (line_input, "www-root %s", &hsm->www_root))
-       ;
-      else
-       if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
-       ;
-      else if (unformat (line_input, "private-segment-size %U",
-                        unformat_memory_size, &seg_size))
+      /* Server config */
+      if (unformat (line_input, "private-segment-size %U",
+                   unformat_memory_size, &seg_size))
        hsm->private_segment_size = seg_size;
       else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
                         &hsm->fifo_size))
        ;
-      else if (unformat (line_input, "cache-size %U", unformat_memory_size,
-                        &hsm->cache_size))
-       ;
-      else if (unformat (line_input, "uri %s", &hsm->uri))
+      else if (unformat (line_input, "prealloc-fifos %d",
+                        &hsm->prealloc_fifos))
        ;
       else if (unformat (line_input, "debug %d", &hsm->debug_level))
        ;
-      else if (unformat (line_input, "keepalive-timeout %d",
-                        &hsm->keepalive_timeout))
-       ;
       else if (unformat (line_input, "debug"))
        hsm->debug_level = 1;
-      else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
-                        &hsm->use_ptr_thresh))
+      /* Default listener parameters */
+      else if (unformat (line_input, "uri %s", &uri))
+       ;
+      else if (unformat (line_input, "www-root %s", &l->www_root))
        ;
       else if (unformat (line_input, "url-handlers"))
-       hsm->enable_url_handlers = 1;
-      else if (unformat (line_input, "max-age %d", &hsm->max_age))
+       l->enable_url_handlers = 1;
+      else if (unformat (line_input, "cache-size %U", unformat_memory_size,
+                        &l->cache_size))
+       ;
+      else if (unformat (line_input, "max-age %d", &l->max_age))
        ;
       else if (unformat (line_input, "max-body-size %U", unformat_memory_size,
-                        &hsm->max_body_size))
+                        &l->max_body_size))
+       ;
+      else if (unformat (line_input, "keepalive-timeout %d",
+                        &l->keepalive_timeout))
+       ;
+      else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
+                        &l->use_ptr_thresh))
        ;
       else
        {
@@ -1048,28 +1125,33 @@ no_input:
   if (error)
     goto done;
 
-  if (hsm->www_root == 0 && !hsm->enable_url_handlers)
+  if (l->www_root)
     {
-      error = clib_error_return (0, "Must set www-root or url-handlers");
-      goto done;
+      /* Maintain legacy default uri behavior */
+      if (!uri)
+       uri = "tcp://0.0.0.0:80";
+      if (l->cache_size < (128 << 10))
+       {
+         error = clib_error_return (0, "cache-size must be at least 128kb");
+         vec_free (l->www_root);
+         goto done;
+       }
     }
 
-  if (hsm->cache_size < (128 << 10))
+  if (uri)
     {
-      error = clib_error_return (0, "cache-size must be at least 128kb");
-      vec_free (hsm->www_root);
-      goto done;
+      if (parse_uri (uri, &l->sep))
+       {
+         error = clib_error_return (0, "failed to parse uri %s", uri);
+         goto done;
+       }
+      hsm->have_default_listener = 1;
     }
 
-  session_enable_disable_args_t args = { .is_en = 1,
-                                        .rt_engine_type =
-                                          RT_BACKEND_ENGINE_RULE_TABLE };
-  vnet_session_enable_disable (vm, &args);
-
   if ((rv = hss_create (vm)))
     {
       error = clib_error_return (0, "server_create returned %d", rv);
-      vec_free (hsm->www_root);
+      vec_free (l->www_root);
     }
 
 done:
@@ -1093,13 +1175,114 @@ done:
 VLIB_CLI_COMMAND (hss_create_command, static) = {
   .path = "http static server",
   .short_help =
-    "http static server www-root <path> [prealloc-fifos <nn>]\n"
+    "http static server [www-root <path>] [url-handlers]\n"
     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [max-age <nseconds>]\n"
-    "[uri <uri>] [ptr-thresh <nn>] [url-handlers] [debug [nn]]\n"
+    "[uri <uri>] [ptr-thresh <nn>] [prealloc-fifos <nn>] [debug [nn]]\n"
     "[keepalive-timeout <nn>] [max-body-size <nn>]\n",
   .function = hss_create_command_fn,
 };
 
+static clib_error_t *
+hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input,
+                                vlib_cli_command_t *cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+  hss_main_t *hsm = &hss_main;
+  clib_error_t *error = 0;
+  hss_listener_t _l = {}, *l = &_l;
+  u8 is_add = 1;
+  char *uri = 0;
+
+  if (!hsm->is_init)
+    return clib_error_return (0, "Static server not initialized");
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return clib_error_return (0, "No input provided");
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "add"))
+       is_add = 1;
+      else if (unformat (line_input, "del"))
+       is_add = 0;
+      else if (unformat (line_input, "uri %s", &uri))
+       ;
+      else if (unformat (line_input, "www-root %s", &l->www_root))
+       ;
+      else if (unformat (line_input, "url-handlers"))
+       l->enable_url_handlers = 1;
+      else if (unformat (line_input, "cache-size %U", unformat_memory_size,
+                        &l->cache_size))
+       ;
+      else if (unformat (line_input, "keepalive-timeout %d",
+                        &l->keepalive_timeout))
+       ;
+      else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
+                        &l->use_ptr_thresh))
+       ;
+      else if (unformat (line_input, "max-age %d", &l->max_age))
+       ;
+      else if (unformat (line_input, "max-body-size %U", unformat_memory_size,
+                        &l->max_body_size))
+       ;
+      else
+       {
+         error = clib_error_return (0, "unknown input `%U'",
+                                    format_unformat_error, line_input);
+         break;
+       }
+    }
+  unformat_free (line_input);
+
+  if (!uri)
+    {
+      error = clib_error_return (0, "Must set uri");
+      goto done;
+    }
+
+  if (parse_uri (uri, &l->sep))
+    {
+      error = clib_error_return (0, "failed to parse uri %s", uri);
+      goto done;
+    }
+
+  if (!is_add)
+    {
+      hss_listener_del (l);
+      goto done;
+    }
+
+  if (l->www_root == 0 && !l->enable_url_handlers)
+    {
+      error = clib_error_return (0, "Must set www-root or url-handlers");
+      goto done;
+    }
+
+  if (l->cache_size < (128 << 10))
+    {
+      error = clib_error_return (0, "cache-size must be at least 128kb");
+      goto done;
+    }
+
+  if (hss_listener_add (l))
+    {
+      error = clib_error_return (0, "failed to create listener");
+      goto done;
+    }
+
+done:
+
+  vec_free (uri);
+  return error;
+}
+
+VLIB_CLI_COMMAND (hss_add_del_listener_command, static) = {
+  .path = "http static listener",
+  .short_help = "http static listener [add|del] uri <uri>\n"
+               "[www-root <path>] [url-handlers] \n",
+  .function = hss_add_del_listener_command_fn,
+};
+
 static u8 *
 format_hss_session (u8 *s, va_list *args)
 {
@@ -1112,14 +1295,29 @@ format_hss_session (u8 *s, va_list *args)
   return s;
 }
 
+static u8 *
+format_hss_listener (u8 *s, va_list *args)
+{
+  hss_listener_t *l = va_arg (*args, hss_listener_t *);
+  int __clib_unused verbose = va_arg (*args, int);
+
+  s = format (
+    s, "listener %d, uri %U:%u, www-root %s, cache-size %U url-handlers %d",
+    l->l_index, format_ip46_address, &l->sep.ip, l->sep.is_ip4,
+    clib_net_to_host_u16 (l->sep.port), l->www_root, format_memory_size,
+    l->cache_size, l->enable_url_handlers);
+  return s;
+}
+
 static clib_error_t *
 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
                     vlib_cli_command_t *cmd)
 {
-  int verbose = 0, show_cache = 0, show_sessions = 0;
+  int verbose = 0, show_cache = 0, show_sessions = 0, show_listeners = 0;
+  u32 l_index = 0;
   hss_main_t *hsm = &hss_main;
 
-  if (hsm->www_root == 0)
+  if (!hsm->is_init)
     return clib_error_return (0, "Static server disabled");
 
   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
@@ -1130,17 +1328,26 @@ hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
        verbose = 1;
       else if (unformat (input, "cache"))
        show_cache = 1;
+      else if (unformat (input, "cache %u", &l_index))
+       show_cache = 1;
       else if (unformat (input, "sessions"))
        show_sessions = 1;
+      else if (unformat (input, "listeners"))
+       show_listeners = 1;
       else
        break;
     }
 
-  if ((show_cache + show_sessions) == 0)
+  if ((show_cache + show_sessions + show_listeners) == 0)
     return clib_error_return (0, "specify one or more of cache, sessions");
 
   if (show_cache)
-    vlib_cli_output (vm, "%U", format_hss_cache, &hsm->cache, verbose);
+    {
+      hss_listener_t *l = hss_listener_get (l_index);
+      if (l == 0)
+       return clib_error_return (0, "listener %d not found", l_index);
+      vlib_cli_output (vm, "%U", format_hss_cache, &l->cache, verbose);
+    }
 
   if (show_sessions)
     {
@@ -1165,6 +1372,15 @@ hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
        }
       vec_free (session_indices);
     }
+
+  if (show_listeners)
+    {
+      hss_listener_t *l;
+      pool_foreach (l, hsm->listeners)
+       {
+         vlib_cli_output (vm, "%U", format_hss_listener, l, verbose);
+       }
+    }
   return 0;
 }
 
@@ -1180,7 +1396,8 @@ hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
 ?*/
 VLIB_CLI_COMMAND (hss_show_command, static) = {
   .path = "show http static server",
-  .short_help = "show http static server sessions cache [verbose [<nn>]]",
+  .short_help = "show http static server [sessions] [cache] [listeners] "
+               "[verbose [<nn>]]",
   .function = hss_show_command_fn,
 };
 
@@ -1189,12 +1406,28 @@ hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
                            vlib_cli_command_t *cmd)
 {
   hss_main_t *hsm = &hss_main;
-  u32 busy_items = 0;
+  u32 busy_items = 0, l_index = 0;
+  hss_listener_t *l;
 
-  if (hsm->www_root == 0)
+  if (!hsm->is_init)
     return clib_error_return (0, "Static server disabled");
 
-  busy_items = hss_cache_clear (&hsm->cache);
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "index %u", &l_index))
+       ;
+      else
+       {
+         return clib_error_return (0, "unknown input `%U'",
+                                   format_unformat_error, input);
+       }
+    }
+
+  l = hss_listener_get (l_index);
+  if (l == 0)
+    return clib_error_return (0, "listener %d not found", l_index);
+
+  busy_items = hss_cache_clear (&l->cache);
 
   if (busy_items > 0)
     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
@@ -1216,7 +1449,7 @@ hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
 ?*/
 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
   .path = "clear http static cache",
-  .short_help = "clear http static cache",
+  .short_help = "clear http static cache [index <index>]",
   .function = hss_clear_cache_command_fn,
 };
 
index 368826f..8d488cb 100644 (file)
@@ -77,11 +77,20 @@ class TestHttpStaticVapi(VppAsfTestCase):
                 "exec",
                 self.ns_name,
                 "curl",
+                "--noproxy",
+                "10.10.1.2",
                 "-v",
                 f"10.10.1.2/{self.temp.name[5:]}",
             ],
             capture_output=True,
         )
+        if process.returncode != 0:
+            self.logger.error(
+                f"Subprocess failed with return code {process.returncode}"
+            )
+            self.logger.error(f"stderr: {process.stderr.decode()}")
+            raise RuntimeError("Subprocess execution failed")
+        self.logger.info(self.vapi.cli("sh session verbose"))
         self.assertIn(b"Hello world", process.stdout)
         self.assertIn(b"max-age=600", process.stderr)
 
@@ -93,6 +102,8 @@ class TestHttpStaticVapi(VppAsfTestCase):
                 "exec",
                 self.ns_name,
                 "curl",
+                "--noproxy",
+                "10.10.1.2",
                 f"10.10.1.2/{self.temp2.name[5:]}",
             ],
             capture_output=True,
@@ -183,9 +194,9 @@ class TestHttpStaticCli(VppAsfTestCase):
         )
         self.assertIn(b"Hello world2", process.stdout)
 
-        self.vapi.cli("show http static server cache")
-        self.vapi.cli("clear http static cache")
-        self.vapi.cli("show http static server sessions")
+        self.logger.info(self.vapi.cli("show http static server cache"))
+        self.logger.info(self.vapi.cli("clear http static cache"))
+        self.logger.info(self.vapi.cli("show http static server sessions"))
 
 
 if __name__ == "__main__":