http: client code improvement

Client app can sends request target, custom header and body to
HTTP layer. In response it receives all bytes as received from
transport, aditionally we provide offset and length of headers
and body.
In addtion client app is now able to receive response with all
status codes and Host header field is set in request at protocol
layer.

Type: improvement

Change-Id: I8c8e2c8f99cdf500126b7c2c722aebc254aa0d9f
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index dcfe561..75db60d 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"fmt"
+	"github.com/onsi/gomega/ghttp"
 	"github.com/onsi/gomega/gmeasure"
 	"io"
 	"net"
@@ -18,6 +19,7 @@
 
 func init() {
 	RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest)
+	RegisterSoloVethTests(HttpClientGetMemLeakTest)
 	RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest,
 		HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest,
 		HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest,
@@ -25,7 +27,7 @@
 		HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
 		HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
 		HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
-		HttpHeadersTest, HttpStaticFileHandler)
+		HttpHeadersTest, HttpStaticFileHandler, HttpClientTest, HttpClientErrRespTest)
 	RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest, PromConcurrentConnections,
 		PromMemLeakTest)
 }
@@ -169,6 +171,7 @@
 
 	s.Log(o)
 	s.AssertContains(o, "<html>", "<html> not found in the result!")
+	s.AssertContains(o, "</html>", "</html> not found in the result!")
 }
 
 func HttpCliConnectErrorTest(s *VethsSuite) {
@@ -185,6 +188,51 @@
 	s.AssertContains(o, "failed to connect")
 }
 
+func HttpClientTest(s *NoTopoSuite) {
+	serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+	server := ghttp.NewUnstartedServer()
+	l, err := net.Listen("tcp", serverAddress+":80")
+	s.AssertNil(err, fmt.Sprint(err))
+	server.HTTPTestServer.Listener = l
+	server.AppendHandlers(
+		ghttp.CombineHandlers(
+			ghttp.VerifyRequest("GET", "/test"),
+			ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}),
+			ghttp.VerifyHeader(http.Header{"Accept": []string{"text / html"}}),
+			ghttp.RespondWith(http.StatusOK, "<html><body><p>Hello</p></body></html>"),
+		))
+	server.Start()
+	defer server.Close()
+	uri := "http://" + serverAddress + "/80"
+	vpp := s.GetContainerByName("vpp").VppInstance
+	o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+	s.Log(o)
+	s.AssertContains(o, "<html>", "<html> not found in the result!")
+	s.AssertContains(o, "</html>", "</html> not found in the result!")
+}
+
+func HttpClientErrRespTest(s *NoTopoSuite) {
+	serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+	server := ghttp.NewUnstartedServer()
+	l, err := net.Listen("tcp", serverAddress+":80")
+	s.AssertNil(err, fmt.Sprint(err))
+	server.HTTPTestServer.Listener = l
+	server.AppendHandlers(
+		ghttp.CombineHandlers(
+			ghttp.VerifyRequest("GET", "/test"),
+			ghttp.RespondWith(http.StatusNotFound, "404: Not Found"),
+		))
+	server.Start()
+	defer server.Close()
+	uri := "http://" + serverAddress + "/80"
+	vpp := s.GetContainerByName("vpp").VppInstance
+	o := vpp.Vppctl("http cli client uri " + uri + " query /test")
+
+	s.Log(o)
+	s.AssertContains(o, "404: Not Found", "error not found in the result!")
+}
+
 func HttpStaticPromTest(s *NoTopoSuite) {
 	query := "stats.prom"
 	vpp := s.GetContainerByName("vpp").VppInstance
@@ -276,9 +324,40 @@
 	vpp.MemLeakCheck(traces1, traces2)
 }
 
+func HttpClientGetMemLeakTest(s *VethsSuite) {
+	s.SkipUnlessLeakCheck()
+
+	serverContainer := s.GetContainerByName("server-vpp").VppInstance
+	clientContainer := s.GetContainerByName("client-vpp").VppInstance
+	serverVeth := s.GetInterfaceByName(ServerInterfaceName)
+
+	/* no goVPP less noise */
+	clientContainer.Disconnect()
+
+	serverContainer.Vppctl("http cli server")
+
+	uri := "http://" + serverVeth.Ip4AddressString() + "/80"
+
+	/* warmup request (FIB) */
+	clientContainer.Vppctl("http cli client uri " + uri + " query /show/version")
+
+	clientContainer.EnableMemoryTrace()
+	traces1, err := clientContainer.GetMemoryTrace()
+	s.AssertNil(err, fmt.Sprint(err))
+
+	clientContainer.Vppctl("http cli client uri " + uri + " query /show/vlib/graph")
+
+	/* let's give it some time to clean up sessions */
+	time.Sleep(time.Second * 12)
+
+	traces2, err := clientContainer.GetMemoryTrace()
+	s.AssertNil(err, fmt.Sprint(err))
+	clientContainer.MemLeakCheck(traces1, traces2)
+}
+
 func HttpStaticFileHandler(s *NoTopoSuite) {
-	content := "<http><body><p>Hello</p></body></http>"
-	content2 := "<http><body><p>Page</p></body></http>"
+	content := "<html><body><p>Hello</p></body></html>"
+	content2 := "<html><body><p>Page</p></body></html>"
 	vpp := s.GetContainerByName("vpp").VppInstance
 	vpp.Container.Exec("mkdir -p " + wwwRootPath)
 	vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
@@ -357,7 +436,7 @@
 func HttpStaticMovedTest(s *NoTopoSuite) {
 	vpp := s.GetContainerByName("vpp").VppInstance
 	vpp.Container.Exec("mkdir -p " + wwwRootPath + "/tmp.aaa")
-	vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<http><body><p>Hello</p></body></http>")
+	vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<html><body><p>Hello</p></body></html>")
 	serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
 	s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
 
diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c
index dfe50c5..814f5f1 100644
--- a/src/plugins/hs_apps/http_cli.c
+++ b/src/plugins/hs_apps/http_cli.c
@@ -207,7 +207,7 @@
 done:
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 }
 
 static void
@@ -469,7 +469,7 @@
     }
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 
   return 0;
 }
diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c
index a99169b..722499b 100644
--- a/src/plugins/hs_apps/http_client_cli.c
+++ b/src/plugins/hs_apps/http_client_cli.c
@@ -16,6 +16,9 @@
 #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>
 
 #define HCC_DEBUG 0
 
@@ -34,12 +37,12 @@
   u32 vpp_session_index;
   u32 to_recv;
   u8 is_closed;
+  http_header_t *req_headers;
 } hcc_session_t;
 
 typedef struct
 {
   hcc_session_t *sessions;
-  u8 *rx_buf;
   u32 thread_index;
 } hcc_worker_t;
 
@@ -95,13 +98,6 @@
   return pool_elt_at_index (wrk->sessions, hs_index);
 }
 
-static void
-hcc_session_free (u32 thread_index, hcc_session_t *hs)
-{
-  hcc_worker_t *wrk = hcc_worker_get (thread_index);
-  pool_put (wrk->sessions, hs);
-}
-
 static int
 hcc_ts_accept_callback (session_t *ts)
 {
@@ -128,6 +124,7 @@
   hcc_session_t *hs, *new_hs;
   hcc_worker_t *wrk;
   http_msg_t msg;
+  u8 *headers_buf;
   int rv;
 
   HCC_DBG ("hc_index: %d", hc_index);
@@ -149,24 +146,42 @@
 
   hs->vpp_session_index = as->session_index;
 
+  http_add_header (&hs->req_headers,
+		   http_header_name_token (HTTP_HEADER_ACCEPT),
+		   http_content_type_token (HTTP_CONTENT_TEXT_HTML));
+  headers_buf = http_serialize_headers (hs->req_headers);
+  vec_free (hs->req_headers);
+
   msg.type = HTTP_MSG_REQUEST;
   msg.method_type = HTTP_REQ_GET;
-  msg.content_type = HTTP_CONTENT_TEXT_HTML;
+  /* request target */
+  msg.data.target_form = HTTP_TARGET_ORIGIN_FORM;
+  msg.data.target_path_offset = 0;
+  msg.data.target_path_len = vec_len (hcm->http_query);
+  /* custom headers */
+  msg.data.headers_offset = msg.data.target_path_len;
+  msg.data.headers_len = vec_len (headers_buf);
+  /* request body */
+  msg.data.body_len = 0;
+  /* data type and total length */
   msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.len = vec_len (hcm->http_query);
+  msg.data.len =
+    msg.data.target_path_len + msg.data.headers_len + msg.data.body_len;
 
-  svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
-			     { hcm->http_query, vec_len (hcm->http_query) } };
+  svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) },
+			     { hcm->http_query, vec_len (hcm->http_query) },
+			     { headers_buf, vec_len (headers_buf) } };
 
-  rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 2, 0 /* allow partial */);
-  if (rv < 0 || rv != sizeof (msg) + vec_len (hcm->http_query))
+  rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */);
+  vec_free (headers_buf);
+  if (rv < 0 || rv != sizeof (msg) + msg.data.len)
     {
       clib_warning ("failed app enqueue");
       return -1;
     }
 
   if (svm_fifo_set_event (as->tx_fifo))
-    session_send_io_evt_to_thread (as->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (as->handle, SESSION_IO_EVT_TX);
 
   return 0;
 }
@@ -221,17 +236,26 @@
 
   if (hs->to_recv == 0)
     {
+      /* read the http message header */
       rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
       ASSERT (rv == sizeof (msg));
 
-      if (msg.type != HTTP_MSG_REPLY || msg.code != HTTP_STATUS_OK)
+      if (msg.type != HTTP_MSG_REPLY)
 	{
 	  clib_warning ("unexpected msg type %d", msg.type);
 	  return 0;
 	}
-      vec_validate (hcm->http_response, msg.data.len - 1);
+      /* drop everything up to body */
+      svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.body_offset);
+      hs->to_recv = msg.data.body_len;
+      if (msg.code != HTTP_STATUS_OK && hs->to_recv == 0)
+	{
+	  hcm->http_response = format (0, "request failed, response code: %U",
+				       format_http_status_code, msg.code);
+	  goto done;
+	}
+      vec_validate (hcm->http_response, msg.data.body_len - 1);
       vec_reset_length (hcm->http_response);
-      hs->to_recv = msg.data.len;
     }
 
   u32 max_deq = svm_fifo_max_dequeue (ts->rx_fifo);
@@ -253,8 +277,10 @@
   hs->to_recv -= rv;
   HCC_DBG ("app rcvd %d, remains %d", rv, hs->to_recv);
 
+done:
   if (hs->to_recv == 0)
     {
+      HCC_DBG ("all data received, going to disconnect");
       hcc_session_disconnect (ts);
       vlib_process_signal_event_mt (hcm->vlib_main, hcm->cli_node_index,
 				    HCC_REPLY_RECEIVED, 0);
@@ -264,18 +290,6 @@
 }
 
 static void
-hcc_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
-{
-  hcc_session_t *hs;
-
-  hs = hcc_session_get (s->thread_index, s->opaque);
-  if (!hs)
-    return;
-
-  hcc_session_free (s->thread_index, hs);
-}
-
-static void
 hcc_ts_transport_closed (session_t *s)
 {
   hcc_main_t *hcm = &hcc_main;
@@ -293,7 +307,6 @@
   .builtin_app_rx_callback = hcc_ts_rx_callback,
   .builtin_app_tx_callback = hcc_ts_tx_callback,
   .session_reset_callback = hcc_ts_reset_callback,
-  .session_cleanup_callback = hcc_ts_cleanup_callback,
   .session_transport_closed_callback = hcc_ts_transport_closed,
 };
 
@@ -423,7 +436,6 @@
     case HCC_REPLY_RECEIVED:
       if (print_output)
 	vlib_cli_output (vm, "%v", hcm->http_response);
-      vec_free (hcm->http_response);
       break;
     case HCC_TRANSPORT_CLOSED:
       err = clib_error_return (0, "error, transport closed");
@@ -460,6 +472,28 @@
   return rv;
 }
 
+static void
+hcc_worker_cleanup (hcc_worker_t *wrk)
+{
+  pool_free (wrk->sessions);
+}
+
+static void
+hcc_cleanup ()
+{
+  hcc_main_t *hcm = &hcc_main;
+  hcc_worker_t *wrk;
+
+  vec_foreach (wrk, hcm->wrk)
+    hcc_worker_cleanup (wrk);
+
+  vec_free (hcm->uri);
+  vec_free (hcm->http_query);
+  vec_free (hcm->http_response);
+  vec_free (hcm->appns_id);
+  vec_free (hcm->wrk);
+}
+
 static clib_error_t *
 hcc_command_fn (vlib_main_t *vm, unformat_input_t *input,
 		vlib_cli_command_t *cmd)
@@ -509,7 +543,6 @@
 	}
     }
 
-  vec_free (hcm->appns_id);
   hcm->appns_id = appns_id;
   hcm->cli_node_index = vlib_get_current_process (vm)->node_runtime.node_index;
 
@@ -540,8 +573,7 @@
     }
 
 done:
-  vec_free (hcm->uri);
-  vec_free (hcm->http_query);
+  hcc_cleanup ();
   unformat_free (line_input);
   return err;
 }
diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c
index 9cc592c..b37c447 100644
--- a/src/plugins/hs_apps/http_tps.c
+++ b/src/plugins/hs_apps/http_tps.c
@@ -154,7 +154,7 @@
     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 }
 
 static void
@@ -201,7 +201,7 @@
     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 }
 
 static inline void
@@ -263,7 +263,7 @@
   if (!msg.data.body_len)
     {
       if (svm_fifo_set_event (ts->tx_fifo))
-	session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+	session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
       return;
     }
 
diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c
index 416ba8f..017fd50 100644
--- a/src/plugins/http/http.c
+++ b/src/plugins/http/http.c
@@ -16,11 +16,11 @@
 #include <http/http.h>
 #include <vnet/session/session.h>
 #include <http/http_timer.h>
+#include <http/http_status_codes.h>
 
 static http_main_t http_main;
 
 #define HTTP_FIFO_THRESH (16 << 10)
-#define CONTENT_LEN_STR	 "Content-Length: "
 
 /* HTTP state machine result */
 typedef enum http_sm_result_t_
@@ -30,18 +30,12 @@
   HTTP_SM_ERROR = -1,
 } http_sm_result_t;
 
-const char *http_status_code_str[] = {
-#define _(c, s, str) str,
-  foreach_http_status_code
-#undef _
-};
-
 const http_buffer_type_t msg_to_buf_type[] = {
   [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO,
   [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR,
 };
 
-u8 *
+static u8 *
 format_http_state (u8 *s, va_list *va)
 {
   http_state_t state = va_arg (*va, http_state_t);
@@ -388,9 +382,13 @@
 					    "Content-Length: %u\r\n"
 					    "%s";
 
+/**
+ * http request boilerplate
+ */
 static const char *http_request_template = "GET %s HTTP/1.1\r\n"
+					   "Host: %v\r\n"
 					   "User-Agent: %v\r\n"
-					   "Accept: */*\r\n\r\n";
+					   "%s";
 
 static u32
 http_send_data (http_conn_t *hc, u8 *data, u32 length, u32 offset)
@@ -409,7 +407,7 @@
     return offset;
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 
   return (offset + sent);
 }
@@ -655,6 +653,111 @@
   return 0;
 }
 
+#define expect_char(c)                                                        \
+  if (*p++ != c)                                                              \
+    {                                                                         \
+      clib_warning ("unexpected character");                                  \
+      return -1;                                                              \
+    }
+
+#define parse_int(val, mul)                                                   \
+  do                                                                          \
+    {                                                                         \
+      if (!isdigit (*p))                                                      \
+	{                                                                     \
+	  clib_warning ("expected digit");                                    \
+	  return -1;                                                          \
+	}                                                                     \
+      val += mul * (*p++ - '0');                                              \
+    }                                                                         \
+  while (0)
+
+static int
+http_parse_status_line (http_conn_t *hc)
+{
+  int i;
+  u32 next_line_offset;
+  u8 *p, *end;
+  u16 status_code = 0;
+
+  i = v_find_index (hc->rx_buf, 0, 0, "\r\n");
+  /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */
+  if (i < 0)
+    {
+      clib_warning ("status line incomplete");
+      return -1;
+    }
+  HTTP_DBG (0, "status line length: %d", i);
+  if (i < 12)
+    {
+      clib_warning ("status line too short (%d)", i);
+      return -1;
+    }
+  next_line_offset = i + 2;
+  p = hc->rx_buf;
+  end = hc->rx_buf + i;
+
+  /* there should be at least one more CRLF */
+  if (vec_len (hc->rx_buf) < (next_line_offset + 2))
+    {
+      clib_warning ("malformed message, too short");
+      return -1;
+    }
+
+  /* parse version */
+  expect_char ('H');
+  expect_char ('T');
+  expect_char ('T');
+  expect_char ('P');
+  expect_char ('/');
+  expect_char ('1');
+  expect_char ('.');
+  if (!isdigit (*p++))
+    {
+      clib_warning ("invalid HTTP minor version");
+      return -1;
+    }
+
+  /* skip space(s) */
+  if (*p != ' ')
+    {
+      clib_warning ("no space after HTTP version");
+      return -1;
+    }
+  do
+    {
+      p++;
+      if (p == end)
+	{
+	  clib_warning ("no status code");
+	  return -1;
+	}
+    }
+  while (*p == ' ');
+
+  /* parse status code */
+  if ((end - p) < 3)
+    {
+      clib_warning ("not enough characters for status code");
+      return -1;
+    }
+  parse_int (status_code, 100);
+  parse_int (status_code, 10);
+  parse_int (status_code, 1);
+  if (status_code < 100 || status_code > 599)
+    {
+      clib_warning ("invalid status code %d", status_code);
+      return -1;
+    }
+  hc->status_code = status_code;
+  HTTP_DBG (0, "status code: %d", hc->status_code);
+
+  /* set buffer offset to nex line start */
+  hc->rx_buf_offset = next_line_offset;
+
+  return 0;
+}
+
 static int
 http_identify_headers (http_conn_t *hc, http_status_code_t *ec)
 {
@@ -692,6 +795,7 @@
   unformat_input_t input;
   int i, len;
   u8 *line;
+  u32 body_len;
 
   hc->body_len = 0;
 
@@ -733,13 +837,14 @@
   HTTP_DBG (0, "%v", line);
 
   unformat_init_vector (&input, line);
-  if (!unformat (&input, "%lu", &hc->body_len))
+  if (!unformat (&input, "%u", &body_len))
     {
       clib_warning ("failed to unformat content length value");
       *ec = HTTP_STATUS_BAD_REQUEST;
       return -1;
     }
   unformat_free (&input);
+  hc->body_len = body_len;
 
   hc->body_offset = hc->headers_offset + hc->headers_len + 2;
   HTTP_DBG (0, "body length: %u", hc->body_len);
@@ -748,61 +853,16 @@
   return 0;
 }
 
-static int
-http_parse_header (http_conn_t *hc, int *content_length)
-{
-  unformat_input_t input;
-  int i, len;
-  u8 *line;
-
-  i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, CONTENT_LEN_STR);
-  if (i < 0)
-    {
-      clib_warning ("cannot find '%s' in the header!", CONTENT_LEN_STR);
-      return -1;
-    }
-
-  hc->rx_buf_offset = i;
-
-  i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "\n");
-  if (i < 0)
-    {
-      clib_warning ("end of line missing; incomplete data");
-      return -1;
-    }
-
-  len = i - hc->rx_buf_offset;
-  line = vec_new (u8, len);
-  clib_memcpy (line, hc->rx_buf + hc->rx_buf_offset, len);
-
-  unformat_init_vector (&input, line);
-  if (!unformat (&input, CONTENT_LEN_STR "%d", content_length))
-    {
-      clib_warning ("failed to unformat content length!");
-      return -1;
-    }
-  unformat_free (&input);
-
-  /* skip rest of the header */
-  hc->rx_buf_offset += len;
-  i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "<html>");
-  if (i < 0)
-    {
-      clib_warning ("<html> tag not found");
-      return -1;
-    }
-  hc->rx_buf_offset = i;
-
-  return 0;
-}
-
 static http_sm_result_t
 http_state_wait_server_reply (http_conn_t *hc, transport_send_params_t *sp)
 {
-  int i, rv, content_length;
+  int rv;
   http_msg_t msg = {};
   app_worker_t *app_wrk;
   session_t *as;
+  u32 len;
+  http_status_code_t ec;
+  http_main_t *hm = &http_main;
 
   rv = http_read_message (hc);
 
@@ -813,57 +873,58 @@
       return HTTP_SM_STOP;
     }
 
+  HTTP_DBG (0, "%v", hc->rx_buf);
+
   if (vec_len (hc->rx_buf) < 8)
     {
       clib_warning ("response buffer too short");
       goto error;
     }
 
-  if ((i = v_find_index (hc->rx_buf, 0, 0, "200 OK")) >= 0)
+  rv = http_parse_status_line (hc);
+  if (rv)
+    goto error;
+
+  rv = http_identify_headers (hc, &ec);
+  if (rv)
+    goto error;
+
+  rv = http_identify_message_body (hc, &ec);
+  if (rv)
+    goto error;
+
+  len = vec_len (hc->rx_buf);
+
+  msg.type = HTTP_MSG_REPLY;
+  msg.code = hm->sc_by_u16[hc->status_code];
+  msg.data.headers_offset = hc->headers_offset;
+  msg.data.headers_len = hc->headers_len;
+  msg.data.body_offset = hc->body_offset;
+  msg.data.body_len = hc->body_len;
+  msg.data.type = HTTP_MSG_DATA_INLINE;
+  msg.data.len = len;
+
+  as = session_get_from_handle (hc->h_pa_session_handle);
+  svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
+			     { hc->rx_buf, len } };
+
+  rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */);
+  if (rv < 0)
     {
-      msg.type = HTTP_MSG_REPLY;
-      msg.content_type = HTTP_CONTENT_TEXT_HTML;
-      msg.code = HTTP_STATUS_OK;
-      msg.data.type = HTTP_MSG_DATA_INLINE;
-      msg.data.len = 0;
+      clib_warning ("error enqueue");
+      return HTTP_SM_ERROR;
+    }
 
-      rv = http_parse_header (hc, &content_length);
-      if (rv)
-	{
-	  clib_warning ("failed to parse http reply");
-	  goto error;
-	}
-      msg.data.len = content_length;
-      u32 dlen = vec_len (hc->rx_buf) - hc->rx_buf_offset;
-      as = session_get_from_handle (hc->h_pa_session_handle);
-      svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
-				 { &hc->rx_buf[hc->rx_buf_offset], dlen } };
+  vec_free (hc->rx_buf);
 
-      rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2,
-				      0 /* allow partial */);
-      if (rv < 0)
-	{
-	  clib_warning ("error enqueue");
-	  return HTTP_SM_ERROR;
-	}
-
-      hc->rx_buf_offset += dlen;
-      hc->to_recv = content_length - dlen;
-
-      if (hc->rx_buf_offset == vec_len (hc->rx_buf))
-	{
-	  vec_reset_length (hc->rx_buf);
-	  hc->rx_buf_offset = 0;
-	}
-
-      if (hc->to_recv == 0)
-	{
-	  hc->rx_buf_offset = 0;
-	  vec_reset_length (hc->rx_buf);
-	  http_state_change (hc, HTTP_STATE_WAIT_APP_METHOD);
-	}
+  if (hc->body_len <= (len - hc->body_offset))
+    {
+      hc->to_recv = 0;
+      http_state_change (hc, HTTP_STATE_WAIT_APP_METHOD);
+    }
       else
 	{
+	  hc->to_recv = hc->body_len - (len - hc->body_offset);
 	  http_state_change (hc, HTTP_STATE_CLIENT_IO_MORE_DATA);
 	}
 
@@ -871,12 +932,6 @@
       if (app_wrk)
 	app_worker_rx_notify (app_wrk, as);
       return HTTP_SM_STOP;
-    }
-  else
-    {
-      clib_warning ("Unknown http method %v", hc->rx_buf);
-      goto error;
-    }
 
 error:
   session_transport_closing_notify (&hc->connection);
@@ -925,7 +980,6 @@
 
   msg.type = HTTP_MSG_REQUEST;
   msg.method_type = hc->method;
-  msg.content_type = HTTP_CONTENT_TEXT_HTML;
   msg.data.type = HTTP_MSG_DATA_INLINE;
   msg.data.len = len;
   msg.data.target_form = hc->target_form;
@@ -1080,7 +1134,7 @@
 {
   http_msg_t msg;
   session_t *as;
-  u8 *buf = 0, *request;
+  u8 *target = 0, *request;
   u32 offset;
   int rv;
 
@@ -1107,12 +1161,44 @@
       clib_warning ("unsupported method %d", msg.method_type);
       goto error;
     }
+  if (msg.data.body_len != 0)
+    {
+      clib_warning ("GET request shouldn't include data");
+      goto error;
+    }
 
-  vec_validate (buf, msg.data.len - 1);
-  rv = svm_fifo_dequeue (as->tx_fifo, msg.data.len, buf);
-  ASSERT (rv == msg.data.len);
+  /* read request target */
+  vec_validate (target, msg.data.target_path_len - 1);
+  rv = svm_fifo_dequeue (as->tx_fifo, msg.data.target_path_len, target);
+  ASSERT (rv == msg.data.target_path_len);
 
-  request = format (0, http_request_template, buf, hc->app_name);
+  /*
+   * Add "protocol layer" headers:
+   * - host
+   * - user agent
+   */
+  request = format (0, http_request_template,
+		    /* target */
+		    target,
+		    /* Host */
+		    hc->host,
+		    /* User-Agent*/
+		    hc->app_name,
+		    /* Any headers from app? */
+		    msg.data.headers_len ? "" : "\r\n");
+
+  /* Add headers from app (if any) */
+  if (msg.data.headers_len)
+    {
+      HTTP_DBG (0, "get headers from app, len %d", msg.data.headers_len);
+      u32 orig_len = vec_len (request);
+      vec_resize (request, msg.data.headers_len);
+      u8 *p = request + orig_len;
+      rv = svm_fifo_dequeue (as->tx_fifo, msg.data.headers_len, p);
+      ASSERT (rv == msg.data.headers_len);
+    }
+  HTTP_DBG (0, "%v", request);
+
   offset = http_send_data (hc, request, vec_len (request), 0);
   if (offset != vec_len (request))
     {
@@ -1122,7 +1208,7 @@
 
   http_state_change (hc, HTTP_STATE_WAIT_SERVER_REPLY);
 
-  vec_free (buf);
+  vec_free (target);
   vec_free (request);
 
   return HTTP_SM_STOP;
@@ -1232,7 +1318,7 @@
   if (!http_buffer_is_drained (hb))
     {
       if (sent && svm_fifo_set_event (ts->tx_fifo))
-	session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+	session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 
       if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH)
 	{
@@ -1246,7 +1332,7 @@
   else
     {
       if (sent && svm_fifo_set_event (ts->tx_fifo))
-	session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX_FLUSH);
+	session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH);
 
       /* Finished transaction, back to HTTP_STATE_WAIT_METHOD */
       http_state_change (hc, HTTP_STATE_WAIT_CLIENT_METHOD);
@@ -1346,6 +1432,7 @@
       clib_warning ("no http connection for %u", ts->session_index);
       return;
     }
+  HTTP_DBG (1, "going to free session %x", ts->opaque);
 
   vec_free (hc->rx_buf);
 
@@ -1353,6 +1440,12 @@
   http_conn_timer_stop (hc);
 
   session_transport_delete_notify (&hc->connection);
+
+  if (!hc->is_server)
+    {
+      vec_free (hc->app_name);
+      vec_free (hc->host);
+    }
   http_conn_free (hc);
 }
 
@@ -1458,11 +1551,20 @@
   hc->state = HTTP_CONN_STATE_CONNECTING;
   cargs->api_context = hc_index;
 
+  hc->is_server = 0;
+
   if (vec_len (app->name))
     hc->app_name = vec_dup (app->name);
   else
     hc->app_name = format (0, "VPP HTTP client");
 
+  if (sep->is_ip4)
+    hc->host = format (0, "%U:%d", format_ip4_address, &sep->ip.ip4,
+		       clib_net_to_host_u16 (sep->port));
+  else
+    hc->host = format (0, "%U:%d", format_ip6_address, &sep->ip.ip6,
+		       clib_net_to_host_u16 (sep->port));
+
   HTTP_DBG (1, "hc ho_index %x", hc_index);
 
   if ((error = vnet_connect (cargs)))
@@ -1515,6 +1617,8 @@
   lhc->c_s_index = app_listener_index;
   lhc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
 
+  lhc->is_server = 1;
+
   if (vec_len (app->name))
     lhc->app_name = vec_dup (app->name);
   else
@@ -1762,6 +1866,7 @@
 http_transport_init (vlib_main_t *vm)
 {
   http_main_t *hm = &http_main;
+  int i;
 
   transport_register_protocol (TRANSPORT_PROTO_HTTP, &http_proto,
 			       FIB_PROTOCOL_IP4, ~0);
@@ -1773,7 +1878,26 @@
   hm->first_seg_size = 32 << 20;
   hm->fifo_size = 512 << 10;
 
-  return 0;
+  /* Setup u16 to http_status_code_t map */
+  /* Unrecognized status code is equivalent to the x00 status */
+  vec_validate (hm->sc_by_u16, 599);
+  for (i = 100; i < 200; i++)
+    hm->sc_by_u16[i] = HTTP_STATUS_CONTINUE;
+  for (i = 200; i < 300; i++)
+    hm->sc_by_u16[i] = HTTP_STATUS_OK;
+  for (i = 300; i < 400; i++)
+    hm->sc_by_u16[i] = HTTP_STATUS_MULTIPLE_CHOICES;
+  for (i = 400; i < 500; i++)
+    hm->sc_by_u16[i] = HTTP_STATUS_BAD_REQUEST;
+  for (i = 500; i < 600; i++)
+    hm->sc_by_u16[i] = HTTP_STATUS_INTERNAL_ERROR;
+
+    /* Registered status codes */
+#define _(c, s, str) hm->sc_by_u16[c] = HTTP_STATUS_##s;
+  foreach_http_status_code
+#undef _
+
+    return 0;
 }
 
 VLIB_INIT_FUNCTION (http_transport_init);
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h
index 07d5472..e3267df 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -368,7 +368,6 @@
     http_req_method_t method_type;
     http_status_code_t code;
   };
-  http_content_type_t content_type;
   http_msg_data_t data;
 } http_msg_t;
 
@@ -388,6 +387,8 @@
   http_conn_state_t state;
   u32 timer_handle;
   u8 *app_name;
+  u8 *host;
+  u8 is_server;
 
   /*
    * Current request
@@ -408,6 +409,7 @@
   u32 headers_len;
   u32 body_offset;
   u32 body_len;
+  u16 status_code;
 } http_conn_t;
 
 typedef struct http_worker_
@@ -423,6 +425,7 @@
 
   clib_timebase_t timebase;
 
+  u16 *sc_by_u16;
   /*
    * Runtime config
    */
diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst
index 2f7a58e..2738127 100644
--- a/src/plugins/http/http_plugin.rst
+++ b/src/plugins/http/http_plugin.rst
@@ -191,10 +191,10 @@
   http_add_header (resp_headers,
 		   http_header_name_token (HTTP_HEADER_CACHE_CONTROL),
 		   http_token_lit ("max-age=600"));
-  http_add_header (&hs->resp_headers,
+  http_add_header (resp_headers,
 		   http_header_name_token (HTTP_HEADER_LOCATION),
 		   (const char *) redirect, vec_len (redirect));
-  headers_buf = http_serialize_headers (hs->resp_headers);
+  headers_buf = http_serialize_headers (resp_headers);
 
 The example below show how to create and send response HTTP message metadata:
 
@@ -236,7 +236,7 @@
       vec_free (tx_buf);
     }
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 
 Example above shows how to send body data by copy, alternatively you could pass it as pointer:
 
@@ -249,3 +249,199 @@
   ASSERT (rv == sizeof (data));
 
 In this case you need to free data when you receive next request or when session is closed.
+
+
+Client application
+^^^^^^^^^^^^^^^^^^
+
+Client application opens connection with vnet URI where transport protocol is set to ``http``.
+
+Sending data
+""""""""""""""
+
+HTTP request is sent when connection is successfully established in ``session_connected_callback``.
+
+When client application sends message to HTTP layer it starts with message metadata, followed by request target, optional headers and body (if any) buffers.
+
+Application should set following items:
+
+* HTTP method
+* target form, offset and length
+* header section offset and length
+* body offset and length
+
+Application could pass headers to HTTP layer. Header list is created dynamically as vector of ``http_header_t``,
+where we store only pointers to buffers (zero copy).
+Well known header names are predefined.
+The list is serialized just before you send buffer to HTTP layer.
+
+.. note::
+    Following headers are added at protocol layer and **MUST NOT** be set by application: Host, User-Agent
+
+
+The example below shows how to create headers section:
+
+.. code-block:: C
+
+  #include <http/http.h>
+  #include <http/http_header_names.h>
+  #include <http/http_content_types.h>
+  http_header_t *req_headers = 0;
+  u8 *headers_buf = 0;
+  http_add_header (req_headers,
+		   http_header_name_token (HTTP_HEADER_ACCEPT),
+		   http_content_type_token (HTTP_CONTENT_TEXT_HTML));
+  headers_buf = http_serialize_headers (req_headers);
+  vec_free (hs->req_headers);
+
+Following example shows how to set message metadata:
+
+.. code-block:: C
+
+  http_msg_t msg;
+  msg.type = HTTP_MSG_REQUEST;
+  msg.method_type = HTTP_REQ_GET;
+  msg.data.headers_offset = 0;
+  /* request target */
+  msg.data.target_form = HTTP_TARGET_ORIGIN_FORM;
+  msg.data.target_path_offset = 0;
+  msg.data.target_path_len = vec_len (target);
+  /* custom headers */
+  msg.data.headers_offset = msg.data.target_path_len;
+  msg.data.headers_len = vec_len (headers_buf);
+  /* no request body because we are doing GET request */
+  msg.data.body_len = 0;
+  /* data type and total length */
+  msg.data.type = HTTP_MSG_DATA_INLINE;
+  msg.data.len = msg.data.target_path_len + msg.data.headers_len + msg.data.body_len;
+
+Finally application sends everything to HTTP layer:
+
+.. code-block:: C
+
+  svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, /* message metadata */
+			     { target, vec_len (target) }, /* request target */
+			     { headers_buf, vec_len (headers_buf) } }; /* serialized headers */
+  rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */);
+  vec_free (headers_buf);
+  if (rv < 0 || rv != sizeof (msg) + msg.data.len)
+    {
+      clib_warning ("failed app enqueue");
+      return -1;
+    }
+  if (svm_fifo_set_event (as->tx_fifo))
+    session_program_tx_io_evt (as->handle, SESSION_IO_EVT_TX);
+
+Receiving data
+""""""""""""""
+
+HTTP plugin sends message header with metadata for parsing, in form of offset and length, followed by all data bytes as received from transport.
+
+Application will get pre-parsed following items:
+
+* status code
+* header section offset and length
+* body offset and length
+
+The example below reads HTTP message header in ``builtin_app_rx_callback``, which is first step application should do:
+
+.. code-block:: C
+
+  #include <http/http.h>
+  http_msg_t msg;
+  rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
+  ASSERT (rv == sizeof (msg));
+
+As next step application might validate message type and status code:
+
+.. code-block:: C
+
+  if (msg.type != HTTP_MSG_REPLY)
+    {
+      /* your error handling */
+    }
+  if (msg.code != HTTP_STATUS_OK)
+    {
+      /* your error handling */
+      /* of course you can continue with steps bellow */
+      /* you might be interested in some headers or body content (if any) */
+    }
+
+Headers are parsed using a generic algorithm, independent of the individual header names.
+When header is repeated, its combined value consists of all values separated by comma, concatenated in order as received.
+Following example shows how to parse headers:
+
+.. code-block:: C
+
+  #include <http/http_header_names.h>
+  if (msg.data.headers_len)
+    {
+      u8 *headers = 0;
+      http_header_table_t *ht;
+      vec_validate (headers, msg.data.headers_len - 1);
+      rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
+			  msg.data.headers_len, headers);
+      ASSERT (rv == msg.data.headers_len);
+      if (http_parse_headers (headers, &ht))
+        {
+          /* your error handling */
+        }
+      /* get Content-Type header */
+      const char *content_type = http_get_header (ht, http_header_name_str (HTTP_HEADER_CONTENT_TYPE));
+      if (content_type)
+        {
+          /* do something interesting */
+        }
+      http_free_header_table (ht);
+      vec_free (headers);
+    }
+
+Finally application reads body, which might be received in multiple pieces (depends on size), so we might need some state machine in ``builtin_app_rx_callback``.
+We will add following members to our session context structure:
+
+.. code-block:: C
+
+  typedef struct
+  {
+    /* ... */
+    u32 to_recv;
+    u8 *resp_body;
+  } session_ctx_t;
+
+First we prepare vector for response body, do it only once when you are reading metadata:
+
+.. code-block:: C
+
+  /* drop everything up to body */
+  svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.body_offset);
+  ctx->to_recv = msg.data.body_len;
+  /* prepare vector for response body */
+  vec_validate (ctx->resp_body, msg.data.body_len - 1);
+  vec_reset_length (ctx->resp_body);
+
+Now we can start reading body content, following block of code could be executed multiple times:
+
+.. code-block:: C
+
+  /* dequeue */
+  u32 max_deq = svm_fifo_max_dequeue (ts->rx_fifo);
+  u32 n_deq = clib_min (to_recv, max_deq);
+  /* current offset */
+  u32 curr = vec_len (ctx->resp_body);
+  rv = svm_fifo_dequeue (ts->rx_fifo, n_deq, ctx->resp_body + curr);
+  if (rv < 0 || rv != n_deq)
+    {
+      /* your error handling */
+    }
+  /* update length of the vector */
+  vec_set_len (ctx->resp_body, curr + n_deq);
+  /* update number of remaining bytes to receive */
+  ASSERT (to_recv >= rv);
+  ctx->to_recv -= rv;
+  /* check if all data received */
+  if (ctx->to_recv == 0)
+    {
+      /* we are done */
+      /* close the session if you don't want to send another request */
+      /* and update state machine... */
+    }
diff --git a/src/plugins/http/http_status_codes.h b/src/plugins/http/http_status_codes.h
new file mode 100644
index 0000000..14b6b7d
--- /dev/null
+++ b/src/plugins/http/http_status_codes.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_
+#define SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_
+
+#include <http/http.h>
+
+const char *http_status_code_str[] = {
+#define _(c, s, str) str,
+  foreach_http_status_code
+#undef _
+};
+
+static inline u8 *
+format_http_status_code (u8 *s, va_list *va)
+{
+  http_status_code_t status_code = va_arg (*va, http_status_code_t);
+  if (status_code < HTTP_N_STATUS)
+    s = format (s, "%s", http_status_code_str[status_code]);
+  else
+    s = format (s, "invalid status code %d", status_code);
+  return s;
+}
+
+#endif /* SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_ */
diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c
index d987a3e..b47fc9c 100644
--- a/src/plugins/http_static/static_server.c
+++ b/src/plugins/http_static/static_server.c
@@ -158,7 +158,7 @@
 done:
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 }
 
 __clib_export void
@@ -618,7 +618,7 @@
     }
 
   if (svm_fifo_set_event (ts->tx_fifo))
-    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+    session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX);
 
   return 0;
 }