hsa: added GET method to client

Type: improvement

Change-Id: I46f7e1e2b509a463ff4b2492bf6412b67a1afdc4
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 7d0063d..0e5d7e6 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -10,6 +10,7 @@
 	"net/http/httptrace"
 	"os"
 	"strconv"
+	"strings"
 	"sync"
 	"time"
 
@@ -30,9 +31,10 @@
 		HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
 		HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
 		HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
-		HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
-		HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest,
-		HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest)
+		HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest,
+		HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest,
+		HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest,
+		HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest)
 	RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
 		PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
 		PromConsecutiveConnectionsTest)
@@ -168,7 +170,6 @@
 	s.Log(o2)
 	s.AssertContains(o2, "ESTABLISHED")
 	s.AssertEqual(o1, o2)
-
 }
 
 func HttpPipeliningTest(s *NoTopoSuite) {
@@ -196,7 +197,7 @@
 	s.AssertNil(err, fmt.Sprint(err))
 	s.AssertEqual(n, len([]rune(req2)))
 	reply := make([]byte, 1024)
-	n, err = conn.Read(reply)
+	_, err = conn.Read(reply)
 	s.AssertNil(err, fmt.Sprint(err))
 	s.Log(string(reply))
 	s.AssertContains(string(reply), "delayed data", "first request response not received")
@@ -297,6 +298,7 @@
 			s.LogHttpReq(true),
 			ghttp.VerifyRequest("POST", "/test"),
 			ghttp.VerifyContentType("application/x-www-form-urlencoded"),
+			ghttp.VerifyHeaderKV("Hello", "World"),
 			ghttp.VerifyBody([]byte(body)),
 			ghttp.RespondWith(http.StatusOK, nil),
 		))
@@ -305,10 +307,92 @@
 
 	uri := "http://" + serverAddress + "/80"
 	vpp := s.GetContainerByName("vpp").VppInstance
-	o := vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+	o := vpp.Vppctl("http client post verbose header Hello:World uri " + uri + " target /test data " + body)
 
 	s.Log(o)
-	s.AssertNotContains(o, "error")
+	s.AssertContains(o, "200 OK")
+}
+
+func HttpClientGetResponseBodyTest(s *NoTopoSuite) {
+	response := "<body>hello world</body>"
+	size := len(response)
+	httpClientGet(s, response, size)
+}
+
+func HttpClientGet128kbResponseTest(s *NoTopoSuite) {
+	response := strings.Repeat("a", 128*1024)
+	size := len(response)
+	httpClientGet(s, response, size)
+}
+
+func HttpClientGetNoResponseBodyTest(s *NoTopoSuite) {
+	response := ""
+	httpClientGet(s, response, 0)
+}
+
+func httpClientGet(s *NoTopoSuite, response string, size int) {
+	serverAddress := s.HostAddr()
+	vpp := s.GetContainerByName("vpp").VppInstance
+
+	server := ghttp.NewUnstartedServer()
+	l, err := net.Listen("tcp", serverAddress+":80")
+	s.AssertNil(err, fmt.Sprint(err))
+	server.HTTPTestServer.Listener = l
+	server.AppendHandlers(
+		ghttp.CombineHandlers(
+			s.LogHttpReq(false),
+			ghttp.VerifyRequest("GET", "/test"),
+			ghttp.VerifyHeaderKV("Hello", "World"),
+			ghttp.VerifyHeaderKV("Test-H2", "Test-K2"),
+			ghttp.RespondWith(http.StatusOK, string(response), http.Header{"Content-Length": {strconv.Itoa(size)}}),
+		))
+	server.Start()
+	defer server.Close()
+
+	uri := "http://" + serverAddress + "/80"
+	cmd := "http client use-ptr verbose header Hello:World header Test-H2:Test-K2 save-to response.txt uri " + uri + " target /test"
+
+	o := vpp.Vppctl(cmd)
+	outputLen := len(o)
+	if outputLen > 500 {
+		s.Log(o[:500])
+		s.Log("* HST Framework: output limited to 500 chars to avoid flooding the console. Output length: " + fmt.Sprint(outputLen))
+	} else {
+		s.Log(o)
+	}
+	s.AssertContains(o, "200 OK")
+	s.AssertContains(o, response)
+	s.AssertContains(o, "Content-Length: "+strconv.Itoa(size))
+
+	file_contents := vpp.Container.Exec(false, "cat /tmp/response.txt")
+	s.AssertContains(file_contents, response)
+}
+
+func HttpClientGetTimeout(s *NoTopoSuite) {
+	serverAddress := s.HostAddr()
+	vpp := s.GetContainerByName("vpp").VppInstance
+
+	server := ghttp.NewUnstartedServer()
+	l, err := net.Listen("tcp", serverAddress+":"+s.GetPortFromPpid())
+	s.AssertNil(err, fmt.Sprint(err))
+	server.HTTPTestServer.Listener = l
+	server.AppendHandlers(
+		ghttp.CombineHandlers(
+			s.LogHttpReq(false),
+			ghttp.VerifyRequest("GET", "/timeout"),
+			http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				time.Sleep(5 * time.Second)
+			}),
+			ghttp.RespondWith(http.StatusOK, nil),
+		))
+	server.Start()
+	defer server.Close()
+	uri := "http://" + serverAddress + "/" + s.GetPortFromPpid()
+	cmd := "http client verbose timeout 1 uri " + uri + " target /timeout"
+
+	o := vpp.Vppctl(cmd)
+	s.Log(o)
+	s.AssertContains(o, "error: timeout")
 }
 
 func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) {
@@ -334,14 +418,14 @@
 	defer server.Close()
 
 	uri := "http://" + serverAddress + "/80"
-	cmd := "http post uri " + uri + " target /test file " + fileName
+	cmd := "http client post verbose uri " + uri + " target /test file " + fileName
 	if usePtr {
 		cmd += " use-ptr"
 	}
 	o := vpp.Vppctl(cmd)
 
 	s.Log(o)
-	s.AssertNotContains(o, "error")
+	s.AssertContains(o, "200 OK")
 }
 
 func HttpClientPostFileTest(s *NoTopoSuite) {
diff --git a/src/plugins/hs_apps/CMakeLists.txt b/src/plugins/hs_apps/CMakeLists.txt
index ba03e39..eae1009 100644
--- a/src/plugins/hs_apps/CMakeLists.txt
+++ b/src/plugins/hs_apps/CMakeLists.txt
@@ -21,7 +21,7 @@
   hs_apps.c
   http_cli.c
   http_client_cli.c
-  http_simple_post.c
+  http_client.c
   http_tps.c
   proxy.c
   test_builtins.c
diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c
new file mode 100644
index 0000000..05a87ec
--- /dev/null
+++ b/src/plugins/hs_apps/http_client.c
@@ -0,0 +1,743 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#include <vnet/session/application.h>
+#include <vnet/session/application_interface.h>
+#include <vnet/session/session.h>
+#include <http/http.h>
+#include <http/http_header_names.h>
+#include <http/http_content_types.h>
+#include <http/http_status_codes.h>
+#include <vppinfra/unix.h>
+
+typedef struct
+{
+  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
+  u32 session_index;
+  u32 thread_index;
+  u32 vpp_session_index;
+  u64 to_recv;
+  u8 is_closed;
+} hc_session_t;
+
+typedef struct
+{
+  hc_session_t *sessions;
+  u32 thread_index;
+  vlib_main_t *vlib_main;
+} hc_worker_t;
+
+typedef struct
+{
+  u32 app_index;
+  u32 cli_node_index;
+  u8 attached;
+  u8 *uri;
+  session_endpoint_cfg_t connect_sep;
+  u8 *target;
+  u8 *headers_buf;
+  u8 *data;
+  u64 data_offset;
+  hc_worker_t *wrk;
+  u8 *resp_headers;
+  u8 *http_response;
+  u8 *response_status;
+  http_header_ht_t *custom_header;
+  u8 is_file;
+  u8 use_ptr;
+  u8 *filename;
+  bool verbose;
+  f64 timeout;
+  http_req_method_t req_method;
+} hc_main_t;
+
+typedef enum
+{
+  HC_CONNECT_FAILED = 1,
+  HC_TRANSPORT_CLOSED,
+  HC_REPLY_RECEIVED,
+} hc_cli_signal_t;
+
+static hc_main_t hc_main;
+
+static inline hc_worker_t *
+hc_worker_get (u32 thread_index)
+{
+  return &hc_main.wrk[thread_index];
+}
+
+static inline hc_session_t *
+hc_session_get (u32 session_index, u32 thread_index)
+{
+  hc_worker_t *wrk = hc_worker_get (thread_index);
+  wrk->vlib_main = vlib_get_main_by_index (thread_index);
+  return pool_elt_at_index (wrk->sessions, session_index);
+}
+
+static void
+hc_ho_session_free (u32 hs_index)
+{
+  hc_worker_t *wrk = hc_worker_get (0);
+  pool_put_index (wrk->sessions, hs_index);
+}
+
+static hc_session_t *
+hc_session_alloc (hc_worker_t *wrk)
+{
+  hc_session_t *s;
+
+  pool_get_zero (wrk->sessions, s);
+  s->session_index = s - wrk->sessions;
+  s->thread_index = wrk->thread_index;
+
+  return s;
+}
+
+static int
+hc_session_connected_callback (u32 app_index, u32 hc_session_index,
+			       session_t *s, session_error_t err)
+{
+  hc_main_t *hcm = &hc_main;
+  hc_session_t *hc_session, *new_hc_session;
+  hc_worker_t *wrk;
+  http_msg_t msg;
+  u64 to_send;
+  u32 n_enq;
+  u8 n_segs;
+  int rv;
+  http_header_ht_t *header;
+  http_header_t *req_headers = 0;
+  u32 new_hc_index;
+
+  HTTP_DBG (1, "ho hc_index: %d", hc_session_index);
+
+  if (err)
+    {
+      clib_warning ("hc_session_index[%d] connected error: %U",
+		    hc_session_index, format_session_error, err);
+      vlib_process_signal_event_mt (hcm->wrk->vlib_main, hcm->cli_node_index,
+				    HC_CONNECT_FAILED, 0);
+      return -1;
+    }
+
+  hc_session = hc_session_get (hc_session_index, 0);
+  wrk = hc_worker_get (s->thread_index);
+  new_hc_session = hc_session_alloc (wrk);
+  new_hc_index = new_hc_session->session_index;
+  clib_memcpy_fast (new_hc_session, hc_session, sizeof (*hc_session));
+  hc_session->vpp_session_index = s->session_index;
+
+  new_hc_session->session_index = new_hc_index;
+  new_hc_session->thread_index = s->thread_index;
+  new_hc_session->vpp_session_index = s->session_index;
+  HTTP_DBG (1, "new hc_index: %d", new_hc_session->session_index);
+  s->opaque = new_hc_index;
+
+  if (hcm->req_method == HTTP_REQ_POST)
+    {
+      if (hcm->is_file)
+	http_add_header (
+	  &req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+	  http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM));
+      else
+	http_add_header (
+	  &req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+	  http_content_type_token (HTTP_CONTENT_APP_X_WWW_FORM_URLENCODED));
+    }
+
+  vec_foreach (header, hcm->custom_header)
+    http_add_header (&req_headers, (const char *) header->name,
+		     vec_len (header->name), (const char *) header->value,
+		     vec_len (header->value));
+
+  hcm->headers_buf = http_serialize_headers (req_headers);
+  vec_free (req_headers);
+
+  msg.method_type = hcm->req_method;
+  if (hcm->req_method == HTTP_REQ_POST)
+    msg.data.body_len = vec_len (hcm->data);
+  else
+    msg.data.body_len = 0;
+
+  msg.type = HTTP_MSG_REQUEST;
+  /* request target */
+  msg.data.target_form = HTTP_TARGET_ORIGIN_FORM;
+  msg.data.target_path_len = vec_len (hcm->target);
+  /* custom headers */
+  msg.data.headers_len = vec_len (hcm->headers_buf);
+  /* total length */
+  msg.data.len =
+    msg.data.target_path_len + msg.data.headers_len + msg.data.body_len;
+
+  if (hcm->use_ptr)
+    {
+      uword target = pointer_to_uword (hcm->target);
+      uword headers = pointer_to_uword (hcm->headers_buf);
+      uword body = pointer_to_uword (hcm->data);
+      msg.data.type = HTTP_MSG_DATA_PTR;
+      svm_fifo_seg_t segs[4] = {
+	{ (u8 *) &msg, sizeof (msg) },
+	{ (u8 *) &target, sizeof (target) },
+	{ (u8 *) &headers, sizeof (headers) },
+	{ (u8 *) &body, sizeof (body) },
+      };
+
+      n_segs = (hcm->req_method == HTTP_REQ_GET) ? 3 : 4;
+      rv = svm_fifo_enqueue_segments (s->tx_fifo, segs, n_segs,
+				      0 /* allow partial */);
+      if (hcm->req_method == HTTP_REQ_POST)
+	ASSERT (rv == (sizeof (msg) + sizeof (target) + sizeof (headers) +
+		       sizeof (body)));
+      else
+	ASSERT (rv == (sizeof (msg) + sizeof (target) + sizeof (headers)));
+      goto done;
+    }
+
+  msg.data.type = HTTP_MSG_DATA_INLINE;
+  msg.data.target_path_offset = 0;
+  msg.data.headers_offset = msg.data.target_path_len;
+  msg.data.body_offset = msg.data.headers_offset + msg.data.headers_len;
+
+  rv = svm_fifo_enqueue (s->tx_fifo, sizeof (msg), (u8 *) &msg);
+  ASSERT (rv == sizeof (msg));
+
+  rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hcm->target), hcm->target);
+  ASSERT (rv == vec_len (hcm->target));
+
+  rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hcm->headers_buf),
+			 hcm->headers_buf);
+  ASSERT (rv == msg.data.headers_len);
+
+  if (hcm->req_method == HTTP_REQ_POST)
+    {
+      to_send = vec_len (hcm->data);
+      n_enq = clib_min (svm_fifo_size (s->tx_fifo), to_send);
+
+      rv = svm_fifo_enqueue (s->tx_fifo, n_enq, hcm->data);
+      if (rv < to_send)
+	{
+	  hcm->data_offset = (rv > 0) ? rv : 0;
+	  svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+	}
+    }
+
+done:
+  if (svm_fifo_set_event (s->tx_fifo))
+    session_program_tx_io_evt (s->handle, SESSION_IO_EVT_TX);
+
+  return 0;
+}
+
+static void
+hc_session_disconnect_callback (session_t *s)
+{
+  hc_main_t *hcm = &hc_main;
+  vnet_disconnect_args_t _a = { 0 }, *a = &_a;
+  int rv;
+
+  a->handle = session_handle (s);
+  a->app_index = hcm->app_index;
+  if ((rv = vnet_disconnect_session (a)))
+    clib_warning ("warning: disconnect returned: %U", format_session_error,
+		  rv);
+}
+
+static void
+hc_session_transport_closed_callback (session_t *s)
+{
+  hc_main_t *hcm = &hc_main;
+  vlib_process_signal_event_mt (hcm->wrk->vlib_main, hcm->cli_node_index,
+				HC_TRANSPORT_CLOSED, 0);
+}
+
+static void
+hc_ho_cleanup_callback (session_t *ts)
+{
+  HTTP_DBG (1, "ho hc_index: %d:", ts->opaque);
+  hc_ho_session_free (ts->opaque);
+}
+
+static void
+hc_session_reset_callback (session_t *s)
+{
+  hc_main_t *hcm = &hc_main;
+  hc_session_t *hc_session;
+  vnet_disconnect_args_t _a = { 0 }, *a = &_a;
+  int rv;
+
+  hc_session = hc_session_get (s->opaque, s->thread_index);
+  hc_session->is_closed = 1;
+
+  a->handle = session_handle (s);
+  a->app_index = hcm->app_index;
+  if ((rv = vnet_disconnect_session (a)))
+    clib_warning ("warning: disconnect returned: %U", format_session_error,
+		  rv);
+}
+
+static int
+hc_rx_callback (session_t *s)
+{
+  hc_main_t *hcm = &hc_main;
+  hc_session_t *hc_session;
+  http_msg_t msg;
+  int rv;
+
+  hc_session = hc_session_get (s->opaque, s->thread_index);
+
+  if (hc_session->is_closed)
+    {
+      clib_warning ("hc_session_index[%d] is closed", s->opaque);
+      return -1;
+    }
+
+  if (hc_session->to_recv == 0)
+    {
+      rv = svm_fifo_dequeue (s->rx_fifo, sizeof (msg), (u8 *) &msg);
+      ASSERT (rv == sizeof (msg));
+
+      if (msg.type != HTTP_MSG_REPLY)
+	{
+	  clib_warning ("unexpected msg type %d", msg.type);
+	  return -1;
+	}
+
+      if (msg.data.headers_len)
+	{
+	  http_header_table_t *ht;
+	  vec_validate (hcm->resp_headers, msg.data.headers_len - 1);
+	  rv = svm_fifo_peek (s->rx_fifo, msg.data.headers_offset,
+			      msg.data.headers_len, hcm->resp_headers);
+
+	  ASSERT (rv == msg.data.headers_len);
+	  HTTP_DBG (1, (char *) hcm->resp_headers);
+
+	  if (http_parse_headers (hcm->resp_headers, &ht))
+	    {
+	      clib_warning ("invalid headers received");
+	      return -1;
+	    }
+	  http_free_header_table (ht);
+
+	  hcm->response_status =
+	    format (0, "%U", format_http_status_code, msg.code);
+	}
+
+      if (msg.data.body_len == 0)
+	{
+	  svm_fifo_dequeue_drop_all (s->rx_fifo);
+	  goto done;
+	}
+
+      /* drop everything up to body */
+      svm_fifo_dequeue_drop (s->rx_fifo, msg.data.body_offset);
+      hc_session->to_recv = msg.data.body_len;
+      if (msg.code != HTTP_STATUS_OK && hc_session->to_recv == 0)
+	{
+	  goto done;
+	}
+      vec_validate (hcm->http_response, msg.data.body_len - 1);
+      vec_reset_length (hcm->http_response);
+    }
+
+  u32 max_deq = svm_fifo_max_dequeue (s->rx_fifo);
+
+  u32 n_deq = clib_min (hc_session->to_recv, max_deq);
+  u32 curr = vec_len (hcm->http_response);
+  rv = svm_fifo_dequeue (s->rx_fifo, n_deq, hcm->http_response + curr);
+  if (rv < 0)
+    {
+      clib_warning ("app dequeue(n=%d) failed; rv = %d", n_deq, rv);
+      return -1;
+    }
+
+  ASSERT (rv == n_deq);
+  vec_set_len (hcm->http_response, curr + n_deq);
+  ASSERT (hc_session->to_recv >= rv);
+  hc_session->to_recv -= rv;
+
+done:
+  if (hc_session->to_recv == 0)
+    {
+      hc_session_disconnect_callback (s);
+      vlib_process_signal_event_mt (hcm->wrk->vlib_main, hcm->cli_node_index,
+				    HC_REPLY_RECEIVED, 0);
+    }
+
+  return 0;
+}
+
+static int
+hc_tx_callback (session_t *s)
+{
+  hc_main_t *hcm = &hc_main;
+  u64 to_send;
+  int rv;
+
+  to_send = vec_len (hcm->data) - hcm->data_offset;
+  rv = svm_fifo_enqueue (s->tx_fifo, to_send, hcm->data + hcm->data_offset);
+
+  if (rv <= 0)
+    {
+      svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+      return 0;
+    }
+
+  if (rv < to_send)
+    {
+      hcm->data_offset += rv;
+      svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+    }
+
+  if (svm_fifo_set_event (s->tx_fifo))
+    session_program_tx_io_evt (s->handle, SESSION_IO_EVT_TX);
+
+  return 0;
+}
+
+static session_cb_vft_t hc_session_cb_vft = {
+  .session_connected_callback = hc_session_connected_callback,
+  .session_disconnect_callback = hc_session_disconnect_callback,
+  .session_transport_closed_callback = hc_session_transport_closed_callback,
+  .session_reset_callback = hc_session_reset_callback,
+  .builtin_app_rx_callback = hc_rx_callback,
+  .builtin_app_tx_callback = hc_tx_callback,
+  .half_open_cleanup_callback = hc_ho_cleanup_callback,
+};
+
+static clib_error_t *
+hc_attach ()
+{
+  hc_main_t *hcm = &hc_main;
+  vnet_app_attach_args_t _a, *a = &_a;
+  u64 options[18];
+  int rv;
+
+  clib_memset (a, 0, sizeof (*a));
+  clib_memset (options, 0, sizeof (options));
+
+  a->api_client_index = APP_INVALID_INDEX;
+  a->name = format (0, "http_client");
+  a->session_cb_vft = &hc_session_cb_vft;
+  a->options = options;
+  a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
+
+  if ((rv = vnet_application_attach (a)))
+    return clib_error_return (0, "attach returned: %U", format_session_error,
+			      rv);
+
+  hcm->app_index = a->app_index;
+  vec_free (a->name);
+  hcm->attached = 1;
+
+  return 0;
+}
+
+static int
+hc_connect_rpc (void *rpc_args)
+{
+  vnet_connect_args_t *a = rpc_args;
+  int rv;
+
+  rv = vnet_connect (a);
+  if (rv > 0)
+    clib_warning (0, "connect returned: %U", format_session_error, rv);
+
+  vec_free (a);
+  return rv;
+}
+
+static void
+hc_connect ()
+{
+  hc_main_t *hcm = &hc_main;
+  vnet_connect_args_t *a = 0;
+  hc_worker_t *wrk;
+  hc_session_t *hc_session;
+
+  vec_validate (a, 0);
+  clib_memset (a, 0, sizeof (a[0]));
+
+  clib_memcpy (&a->sep_ext, &hcm->connect_sep, sizeof (hcm->connect_sep));
+  a->app_index = hcm->app_index;
+
+  /* allocate http session on main thread */
+  wrk = hc_worker_get (0);
+  hc_session = hc_session_alloc (wrk);
+  a->api_context = hc_session->session_index;
+
+  session_send_rpc_evt_to_thread_force (transport_cl_thread (), hc_connect_rpc,
+					a);
+}
+
+static clib_error_t *
+hc_run (vlib_main_t *vm)
+{
+  hc_main_t *hcm = &hc_main;
+  vlib_thread_main_t *vtm = vlib_get_thread_main ();
+  u32 num_threads;
+  hc_worker_t *wrk;
+  uword event_type, *event_data = 0;
+  clib_error_t *err;
+  FILE *file_ptr;
+
+  num_threads = 1 /* main thread */ + vtm->n_threads;
+  vec_validate (hcm->wrk, num_threads - 1);
+  vec_foreach (wrk, hcm->wrk)
+    wrk->thread_index = wrk - hcm->wrk;
+
+  if ((err = hc_attach ()))
+    return clib_error_return (0, "http client attach: %U", format_clib_error,
+			      err);
+
+  hc_connect ();
+
+  vlib_process_wait_for_event_or_clock (vm, hcm->timeout);
+  event_type = vlib_process_get_events (vm, &event_data);
+  switch (event_type)
+    {
+    case ~0:
+      err = clib_error_return (0, "error: timeout");
+      break;
+    case HC_CONNECT_FAILED:
+      err = clib_error_return (0, "error: failed to connect");
+      break;
+    case HC_TRANSPORT_CLOSED:
+      err = clib_error_return (0, "error: transport closed");
+      break;
+    case HC_REPLY_RECEIVED:
+      if (hcm->filename)
+	{
+	  file_ptr =
+	    fopen ((char *) format (0, "/tmp/%v", hcm->filename), "w");
+	  if (file_ptr == NULL)
+	    {
+	      vlib_cli_output (vm, "couldn't open file %v", hcm->filename);
+	    }
+	  else
+	    {
+	      fprintf (file_ptr, "< %s\n< %s\n< %s", hcm->response_status,
+		       hcm->resp_headers, hcm->http_response);
+	      fclose (file_ptr);
+	      vlib_cli_output (vm, "file saved (/tmp/%v)", hcm->filename);
+	    }
+	}
+      if (hcm->verbose)
+	vlib_cli_output (vm, "< %v\n< %v", hcm->response_status,
+			 hcm->resp_headers);
+      vlib_cli_output (vm, "<\n%v", hcm->http_response);
+
+      break;
+    default:
+      err = clib_error_return (0, "error: unexpected event %d", event_type);
+      break;
+    }
+
+  vec_free (event_data);
+  return err;
+}
+
+static int
+hc_detach ()
+{
+  hc_main_t *hcm = &hc_main;
+  vnet_app_detach_args_t _da, *da = &_da;
+  int rv;
+
+  if (!hcm->attached)
+    return 0;
+
+  da->app_index = hcm->app_index;
+  da->api_client_index = APP_INVALID_INDEX;
+  rv = vnet_application_detach (da);
+  hcm->attached = 0;
+  hcm->app_index = APP_INVALID_INDEX;
+
+  return rv;
+}
+
+static void
+hcc_worker_cleanup (hc_worker_t *wrk)
+{
+  pool_free (wrk->sessions);
+}
+
+static void
+hc_cleanup ()
+{
+  hc_main_t *hcm = &hc_main;
+  hc_worker_t *wrk;
+  http_header_ht_t *header;
+
+  vec_foreach (wrk, hcm->wrk)
+    hcc_worker_cleanup (wrk);
+
+  vec_free (hcm->uri);
+  vec_free (hcm->target);
+  vec_free (hcm->headers_buf);
+  vec_free (hcm->data);
+  vec_free (hcm->resp_headers);
+  vec_free (hcm->http_response);
+  vec_free (hcm->response_status);
+  vec_free (hcm->wrk);
+  vec_free (hcm->filename);
+  vec_foreach (header, hcm->custom_header)
+    {
+      vec_free (header->name);
+      vec_free (header->value);
+    }
+  vec_free (hcm->custom_header);
+}
+
+static clib_error_t *
+hc_command_fn (vlib_main_t *vm, unformat_input_t *input,
+	       vlib_cli_command_t *cmd)
+{
+  hc_main_t *hcm = &hc_main;
+  clib_error_t *err = 0;
+  unformat_input_t _line_input, *line_input = &_line_input;
+  u8 *path = 0;
+  u8 *file_data;
+  http_header_ht_t new_header;
+  u8 *name;
+  u8 *value;
+  int rv;
+  hcm->timeout = 10;
+
+  if (hcm->attached)
+    return clib_error_return (0, "failed: already running!");
+
+  hcm->use_ptr = 0;
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return clib_error_return (0, "expected required arguments");
+
+  hcm->req_method =
+    (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) &&
+	unformat (line_input, "post") ?
+      HTTP_REQ_POST :
+      HTTP_REQ_GET;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "uri %s", &hcm->uri))
+	;
+      else if (unformat (line_input, "data %v", &hcm->data))
+	hcm->is_file = 0;
+      else if (unformat (line_input, "target %s", &hcm->target))
+	;
+      else if (unformat (line_input, "file %s", &path))
+	hcm->is_file = 1;
+      else if (unformat (line_input, "use-ptr"))
+	hcm->use_ptr = 1;
+      else if (unformat (line_input, "save-to %s", &hcm->filename))
+	{
+	  if (strstr ((char *) hcm->filename, "..") ||
+	      strchr ((char *) hcm->filename, '/'))
+	    {
+	      err = clib_error_return (
+		0, "illegal characters in filename '%v'", hcm->filename);
+	      goto done;
+	    }
+	}
+      else if (unformat (line_input, "header %v:%v", &name, &value))
+	{
+	  new_header.name = name;
+	  new_header.value = value;
+	  vec_add1 (hcm->custom_header, new_header);
+	}
+      else if (unformat (line_input, "verbose"))
+	hcm->verbose = true;
+      else if (unformat (line_input, "timeout %f", &hcm->timeout))
+	;
+      else
+	{
+	  err = clib_error_return (0, "unknown input `%U'",
+				   format_unformat_error, line_input);
+	  goto done;
+	}
+    }
+
+  if (!hcm->uri)
+    {
+      err = clib_error_return (0, "URI not defined");
+      goto done;
+    }
+  if (!hcm->target)
+    {
+      err = clib_error_return (0, "target not defined");
+      goto done;
+    }
+  if (!hcm->data && hcm->req_method == HTTP_REQ_POST)
+    {
+      if (path)
+	{
+	  err = clib_file_contents ((char *) path, &file_data);
+	  if (err)
+	    goto done;
+	  hcm->data = file_data;
+	}
+      else
+	{
+	  err = clib_error_return (0, "data not defined");
+	  goto done;
+	}
+    }
+
+  if ((rv = parse_uri ((char *) hcm->uri, &hcm->connect_sep)))
+    {
+      err =
+	clib_error_return (0, "URI parse error: %U", format_session_error, rv);
+      goto done;
+    }
+
+  session_enable_disable_args_t args = { .is_en = 1,
+					 .rt_engine_type =
+					   RT_BACKEND_ENGINE_RULE_TABLE };
+  vlib_worker_thread_barrier_sync (vm);
+  vnet_session_enable_disable (vm, &args);
+  vlib_worker_thread_barrier_release (vm);
+
+  hcm->cli_node_index = vlib_get_current_process (vm)->node_runtime.node_index;
+
+  err = hc_run (vm);
+
+  if ((rv = hc_detach ()))
+    {
+      /* don't override last error */
+      if (!err)
+	err = clib_error_return (0, "detach returned: %U",
+				 format_session_error, rv);
+      else
+	clib_warning ("warning: detach returned: %U", format_session_error,
+		      rv);
+    }
+
+done:
+  vec_free (path);
+  hc_cleanup ();
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (hc_command, static) = {
+  .path = "http client",
+  .short_help = "[post] uri http://<ip-addr> target <origin-form> "
+		"[data <form-urlencoded> | file <file-path>] [use-ptr] "
+		"[save-to <filename>] [header <Key:Value>] [verbose] "
+		"[timeout <seconds> (default = 10)]",
+  .function = hc_command_fn,
+  .is_mp_safe = 1,
+};
+
+static clib_error_t *
+hc_main_init ()
+{
+  hc_main_t *hcm = &hc_main;
+  hcm->app_index = APP_INVALID_INDEX;
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (hc_main_init);
diff --git a/src/plugins/hs_apps/http_simple_post.c b/src/plugins/hs_apps/http_simple_post.c
deleted file mode 100644
index 6212eac..0000000
--- a/src/plugins/hs_apps/http_simple_post.c
+++ /dev/null
@@ -1,581 +0,0 @@
-/* SPDX-License-Identifier: Apache-2.0
- * Copyright(c) 2024 Cisco Systems, Inc.
- */
-
-#include <vnet/session/application.h>
-#include <vnet/session/application_interface.h>
-#include <vnet/session/session.h>
-#include <http/http.h>
-#include <http/http_header_names.h>
-#include <http/http_content_types.h>
-#include <vppinfra/unix.h>
-
-typedef struct
-{
-  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
-  u32 session_index;
-  u32 thread_index;
-  u32 vpp_session_index;
-  u8 is_closed;
-} hsp_session_t;
-
-typedef struct
-{
-  hsp_session_t *sessions;
-  u32 thread_index;
-} hsp_worker_t;
-
-typedef struct
-{
-  u32 app_index;
-  vlib_main_t *vlib_main;
-  u32 cli_node_index;
-  u8 attached;
-  u8 *uri;
-  session_endpoint_cfg_t connect_sep;
-  u8 *target;
-  u8 *headers_buf;
-  u8 *data;
-  u64 data_offset;
-  hsp_worker_t *wrk;
-  u8 *http_response;
-  u8 is_file;
-  u8 use_ptr;
-} hsp_main_t;
-
-typedef enum
-{
-  HSP_CONNECT_FAILED = 1,
-  HSP_TRANSPORT_CLOSED,
-  HSP_REPLY_RECEIVED,
-} hsp_cli_signal_t;
-
-static hsp_main_t hsp_main;
-
-static inline hsp_worker_t *
-hsp_worker_get (u32 thread_index)
-{
-  return &hsp_main.wrk[thread_index];
-}
-
-static inline hsp_session_t *
-hsp_session_get (u32 session_index, u32 thread_index)
-{
-  hsp_worker_t *wrk = hsp_worker_get (thread_index);
-  return pool_elt_at_index (wrk->sessions, session_index);
-}
-
-static hsp_session_t *
-hsp_session_alloc (hsp_worker_t *wrk)
-{
-  hsp_session_t *s;
-
-  pool_get_zero (wrk->sessions, s);
-  s->session_index = s - wrk->sessions;
-  s->thread_index = wrk->thread_index;
-
-  return s;
-}
-
-static int
-hsp_session_connected_callback (u32 app_index, u32 hsp_session_index,
-				session_t *s, session_error_t err)
-{
-  hsp_main_t *hspm = &hsp_main;
-  hsp_session_t *hsp_session, *new_hsp_session;
-  hsp_worker_t *wrk;
-  http_header_t *headers = 0;
-  http_msg_t msg;
-  u64 to_send;
-  u32 n_enq;
-  int rv;
-
-  if (err)
-    {
-      clib_warning ("hsp_session_index[%d] connected error: %U",
-		    hsp_session_index, format_session_error, err);
-      vlib_process_signal_event_mt (hspm->vlib_main, hspm->cli_node_index,
-				    HSP_CONNECT_FAILED, 0);
-      return -1;
-    }
-
-  hsp_session = hsp_session_get (hsp_session_index, 0);
-  wrk = hsp_worker_get (s->thread_index);
-  new_hsp_session = hsp_session_alloc (wrk);
-  clib_memcpy_fast (new_hsp_session, hsp_session, sizeof (*hsp_session));
-  hsp_session->vpp_session_index = s->session_index;
-
-  if (hspm->is_file)
-    {
-      http_add_header (
-	&headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
-	http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM));
-    }
-  else
-    {
-      http_add_header (
-	&headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
-	http_content_type_token (HTTP_CONTENT_APP_X_WWW_FORM_URLENCODED));
-    }
-  hspm->headers_buf = http_serialize_headers (headers);
-  vec_free (headers);
-
-  msg.type = HTTP_MSG_REQUEST;
-  msg.method_type = HTTP_REQ_POST;
-  /* request target */
-  msg.data.target_form = HTTP_TARGET_ORIGIN_FORM;
-  msg.data.target_path_len = vec_len (hspm->target);
-  /* custom headers */
-  msg.data.headers_len = vec_len (hspm->headers_buf);
-  /* request body */
-  msg.data.body_len = vec_len (hspm->data);
-  /* total length */
-  msg.data.len =
-    msg.data.target_path_len + msg.data.headers_len + msg.data.body_len;
-
-  if (hspm->use_ptr)
-    {
-      uword target = pointer_to_uword (hspm->target);
-      uword headers = pointer_to_uword (hspm->headers_buf);
-      uword body = pointer_to_uword (hspm->data);
-      msg.data.type = HTTP_MSG_DATA_PTR;
-      svm_fifo_seg_t segs[4] = {
-	{ (u8 *) &msg, sizeof (msg) },
-	{ (u8 *) &target, sizeof (target) },
-	{ (u8 *) &headers, sizeof (headers) },
-	{ (u8 *) &body, sizeof (body) },
-      };
-
-      rv =
-	svm_fifo_enqueue_segments (s->tx_fifo, segs, 4, 0 /* allow partial */);
-      ASSERT (rv == (sizeof (msg) + sizeof (target) + sizeof (headers) +
-		     sizeof (body)));
-      goto done;
-    }
-
-  msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.target_path_offset = 0;
-  msg.data.headers_offset = msg.data.target_path_len;
-  msg.data.body_offset = msg.data.headers_offset + msg.data.headers_len;
-
-  rv = svm_fifo_enqueue (s->tx_fifo, sizeof (msg), (u8 *) &msg);
-  ASSERT (rv == sizeof (msg));
-
-  rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hspm->target), hspm->target);
-  ASSERT (rv == vec_len (hspm->target));
-
-  rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hspm->headers_buf),
-			 hspm->headers_buf);
-  ASSERT (rv == msg.data.headers_len);
-
-  to_send = vec_len (hspm->data);
-  n_enq = clib_min (svm_fifo_size (s->tx_fifo), to_send);
-
-  rv = svm_fifo_enqueue (s->tx_fifo, n_enq, hspm->data);
-
-  if (rv < to_send)
-    {
-      hspm->data_offset = (rv > 0) ? rv : 0;
-      svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-    }
-
-done:
-  if (svm_fifo_set_event (s->tx_fifo))
-    session_program_tx_io_evt (s->handle, SESSION_IO_EVT_TX);
-
-  return 0;
-}
-
-static void
-hsp_session_disconnect_callback (session_t *s)
-{
-  hsp_main_t *hspm = &hsp_main;
-  vnet_disconnect_args_t _a = { 0 }, *a = &_a;
-  int rv;
-
-  a->handle = session_handle (s);
-  a->app_index = hspm->app_index;
-  if ((rv = vnet_disconnect_session (a)))
-    clib_warning ("warning: disconnect returned: %U", format_session_error,
-		  rv);
-}
-
-static void
-hsp_session_transport_closed_callback (session_t *s)
-{
-  hsp_main_t *hspm = &hsp_main;
-
-  vlib_process_signal_event_mt (hspm->vlib_main, hspm->cli_node_index,
-				HSP_TRANSPORT_CLOSED, 0);
-}
-
-static void
-hsp_session_reset_callback (session_t *s)
-{
-  hsp_main_t *hspm = &hsp_main;
-  hsp_session_t *hsp_session;
-  vnet_disconnect_args_t _a = { 0 }, *a = &_a;
-  int rv;
-
-  hsp_session = hsp_session_get (s->opaque, s->thread_index);
-  hsp_session->is_closed = 1;
-
-  a->handle = session_handle (s);
-  a->app_index = hspm->app_index;
-  if ((rv = vnet_disconnect_session (a)))
-    clib_warning ("warning: disconnect returned: %U", format_session_error,
-		  rv);
-}
-
-static int
-hsp_rx_callback (session_t *s)
-{
-  hsp_main_t *hspm = &hsp_main;
-  hsp_session_t *hsp_session;
-  http_msg_t msg;
-  int rv;
-
-  hsp_session = hsp_session_get (s->opaque, s->thread_index);
-
-  if (hsp_session->is_closed)
-    {
-      clib_warning ("hsp_session_index[%d] is closed", s->opaque);
-      return -1;
-    }
-
-  rv = svm_fifo_dequeue (s->rx_fifo, sizeof (msg), (u8 *) &msg);
-  ASSERT (rv == sizeof (msg));
-
-  if (msg.type != HTTP_MSG_REPLY)
-    {
-      clib_warning ("unexpected msg type %d", msg.type);
-      return -1;
-    }
-
-  svm_fifo_dequeue_drop_all (s->rx_fifo);
-
-  if (msg.code == HTTP_STATUS_OK)
-    hspm->http_response = format (0, "request success");
-  else
-    hspm->http_response = format (0, "request failed");
-
-  hsp_session_disconnect_callback (s);
-  vlib_process_signal_event_mt (hspm->vlib_main, hspm->cli_node_index,
-				HSP_REPLY_RECEIVED, 0);
-  return 0;
-}
-
-static int
-hsp_tx_callback (session_t *s)
-{
-  hsp_main_t *hspm = &hsp_main;
-  u64 to_send;
-  u32 n_enq;
-  int rv;
-
-  to_send = vec_len (hspm->data) - hspm->data_offset;
-  n_enq = clib_min (svm_fifo_size (s->tx_fifo), to_send);
-
-  rv = svm_fifo_enqueue (s->tx_fifo, n_enq, hspm->data + hspm->data_offset);
-
-  if (rv <= 0)
-    {
-      svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-      return 0;
-    }
-
-  if (rv < to_send)
-    {
-      hspm->data_offset += rv;
-      svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
-    }
-
-  if (svm_fifo_set_event (s->tx_fifo))
-    session_program_tx_io_evt (s->handle, SESSION_IO_EVT_TX);
-
-  return 0;
-}
-
-static session_cb_vft_t hsp_session_cb_vft = {
-  .session_connected_callback = hsp_session_connected_callback,
-  .session_disconnect_callback = hsp_session_disconnect_callback,
-  .session_transport_closed_callback = hsp_session_transport_closed_callback,
-  .session_reset_callback = hsp_session_reset_callback,
-  .builtin_app_rx_callback = hsp_rx_callback,
-  .builtin_app_tx_callback = hsp_tx_callback,
-};
-
-static clib_error_t *
-hsp_attach ()
-{
-  hsp_main_t *hspm = &hsp_main;
-  vnet_app_attach_args_t _a, *a = &_a;
-  u64 options[18];
-  int rv;
-
-  clib_memset (a, 0, sizeof (*a));
-  clib_memset (options, 0, sizeof (options));
-
-  a->api_client_index = APP_INVALID_INDEX;
-  a->name = format (0, "http_simple_post");
-  a->session_cb_vft = &hsp_session_cb_vft;
-  a->options = options;
-  a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
-
-  if ((rv = vnet_application_attach (a)))
-    return clib_error_return (0, "attach returned: %U", format_session_error,
-			      rv);
-
-  hspm->app_index = a->app_index;
-  vec_free (a->name);
-  hspm->attached = 1;
-
-  return 0;
-}
-
-static int
-hsp_connect_rpc (void *rpc_args)
-{
-  vnet_connect_args_t *a = rpc_args;
-  int rv;
-
-  rv = vnet_connect (a);
-  if (rv)
-    clib_warning (0, "connect returned: %U", format_session_error, rv);
-
-  vec_free (a);
-  return rv;
-}
-
-static void
-hsp_connect ()
-{
-  hsp_main_t *hspm = &hsp_main;
-  vnet_connect_args_t *a = 0;
-  hsp_worker_t *wrk;
-  hsp_session_t *hsp_session;
-
-  vec_validate (a, 0);
-  clib_memset (a, 0, sizeof (a[0]));
-
-  clib_memcpy (&a->sep_ext, &hspm->connect_sep, sizeof (hspm->connect_sep));
-  a->app_index = hspm->app_index;
-
-  /* allocate http session on main thread */
-  wrk = hsp_worker_get (0);
-  hsp_session = hsp_session_alloc (wrk);
-  a->api_context = hsp_session->session_index;
-
-  session_send_rpc_evt_to_thread_force (transport_cl_thread (),
-					hsp_connect_rpc, a);
-}
-
-static clib_error_t *
-hsp_run (vlib_main_t *vm)
-{
-  hsp_main_t *hspm = &hsp_main;
-  vlib_thread_main_t *vtm = vlib_get_thread_main ();
-  u32 num_threads;
-  hsp_worker_t *wrk;
-  uword event_type, *event_data = 0;
-  clib_error_t *err;
-
-  num_threads = 1 /* main thread */ + vtm->n_threads;
-  vec_validate (hspm->wrk, num_threads);
-  vec_foreach (wrk, hspm->wrk)
-    wrk->thread_index = wrk - hspm->wrk;
-
-  if ((err = hsp_attach ()))
-    return clib_error_return (0, "http simple post attach: %U",
-			      format_clib_error, err);
-
-  hsp_connect ();
-
-  vlib_process_wait_for_event_or_clock (vm, 10);
-  event_type = vlib_process_get_events (vm, &event_data);
-  switch (event_type)
-    {
-    case ~0:
-      err = clib_error_return (0, "error: timeout");
-      break;
-    case HSP_CONNECT_FAILED:
-      err = clib_error_return (0, "error: failed to connect");
-      break;
-    case HSP_TRANSPORT_CLOSED:
-      err = clib_error_return (0, "error: transport closed");
-      break;
-    case HSP_REPLY_RECEIVED:
-      vlib_cli_output (vm, "%v", hspm->http_response);
-      break;
-    default:
-      err = clib_error_return (0, "error: unexpected event %d", event_type);
-      break;
-    }
-
-  vec_free (event_data);
-  return err;
-}
-
-static int
-hsp_detach ()
-{
-  hsp_main_t *hspm = &hsp_main;
-  vnet_app_detach_args_t _da, *da = &_da;
-  int rv;
-
-  if (!hspm->attached)
-    return 0;
-
-  da->app_index = hspm->app_index;
-  da->api_client_index = APP_INVALID_INDEX;
-  rv = vnet_application_detach (da);
-  hspm->attached = 0;
-  hspm->app_index = APP_INVALID_INDEX;
-
-  return rv;
-}
-
-static void
-hcc_worker_cleanup (hsp_worker_t *wrk)
-{
-  pool_free (wrk->sessions);
-}
-
-static void
-hsp_cleanup ()
-{
-  hsp_main_t *hspm = &hsp_main;
-  hsp_worker_t *wrk;
-
-  vec_foreach (wrk, hspm->wrk)
-    hcc_worker_cleanup (wrk);
-
-  vec_free (hspm->uri);
-  vec_free (hspm->target);
-  vec_free (hspm->headers_buf);
-  vec_free (hspm->data);
-  vec_free (hspm->http_response);
-  vec_free (hspm->wrk);
-}
-
-static clib_error_t *
-hsp_command_fn (vlib_main_t *vm, unformat_input_t *input,
-		vlib_cli_command_t *cmd)
-{
-  hsp_main_t *hspm = &hsp_main;
-  clib_error_t *err = 0;
-  unformat_input_t _line_input, *line_input = &_line_input;
-  u8 *path = 0;
-  u8 *file_data;
-  int rv;
-
-  if (hspm->attached)
-    return clib_error_return (0, "failed: already running!");
-
-  hspm->use_ptr = 0;
-
-  /* Get a line of input. */
-  if (!unformat_user (input, unformat_line_input, line_input))
-    return clib_error_return (0, "expected required arguments");
-
-  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
-    {
-      if (unformat (line_input, "uri %s", &hspm->uri))
-	;
-      else if (unformat (line_input, "data %v", &hspm->data))
-	hspm->is_file = 0;
-      else if (unformat (line_input, "target %s", &hspm->target))
-	;
-      else if (unformat (line_input, "file %s", &path))
-	hspm->is_file = 1;
-      else if (unformat (line_input, "use-ptr"))
-	hspm->use_ptr = 1;
-      else
-	{
-	  err = clib_error_return (0, "unknown input `%U'",
-				   format_unformat_error, line_input);
-	  goto done;
-	}
-    }
-
-  if (!hspm->uri)
-    {
-      err = clib_error_return (0, "URI not defined");
-      goto done;
-    }
-  if (!hspm->target)
-    {
-      err = clib_error_return (0, "target not defined");
-      goto done;
-    }
-  if (!hspm->data)
-    {
-      if (path)
-	{
-	  err = clib_file_contents ((char *) path, &file_data);
-	  if (err)
-	    goto done;
-	  hspm->data = file_data;
-	}
-      else
-	{
-	  err = clib_error_return (0, "data not defined");
-	  goto done;
-	}
-    }
-
-  if ((rv = parse_uri ((char *) hspm->uri, &hspm->connect_sep)))
-    {
-      err =
-	clib_error_return (0, "URI parse error: %U", format_session_error, rv);
-      goto done;
-    }
-
-  session_enable_disable_args_t args = { .is_en = 1,
-					 .rt_engine_type =
-					   RT_BACKEND_ENGINE_RULE_TABLE };
-  vlib_worker_thread_barrier_sync (vm);
-  vnet_session_enable_disable (vm, &args);
-  vlib_worker_thread_barrier_release (vm);
-
-  hspm->cli_node_index =
-    vlib_get_current_process (vm)->node_runtime.node_index;
-
-  err = hsp_run (vm);
-
-  if ((rv = hsp_detach ()))
-    {
-      /* don't override last error */
-      if (!err)
-	err = clib_error_return (0, "detach returned: %U",
-				 format_session_error, rv);
-      else
-	clib_warning ("warning: detach returned: %U", format_session_error,
-		      rv);
-    }
-
-done:
-  hsp_cleanup ();
-  unformat_free (line_input);
-  return err;
-}
-
-VLIB_CLI_COMMAND (hsp_command, static) = {
-  .path = "http post",
-  .short_help = "uri http://<ip-addr> target <origin-form> "
-		"[data <form-urlencoded> | file <file-path>] [use-ptr]",
-  .function = hsp_command_fn,
-  .is_mp_safe = 1,
-};
-
-static clib_error_t *
-hsp_main_init (vlib_main_t *vm)
-{
-  hsp_main_t *hspm = &hsp_main;
-
-  hspm->app_index = APP_INVALID_INDEX;
-  hspm->vlib_main = vm;
-  return 0;
-}
-
-VLIB_INIT_FUNCTION (hsp_main_init);