http: client POST method 48/41348/3
authorMatus Fabian <matfabia@cisco.com>
Wed, 31 Jul 2024 14:08:40 +0000 (16:08 +0200)
committerFlorin Coras <florin.coras@gmail.com>
Tue, 6 Aug 2024 16:01:02 +0000 (16:01 +0000)
Type: improvement

Change-Id: Iaa70abcee02866f9a6426a6e8e4709eeba0e8114
Signed-off-by: Matus Fabian <matfabia@cisco.com>
extras/hs-test/http_test.go
extras/hs-test/infra/hst_suite.go
src/plugins/hs_apps/CMakeLists.txt
src/plugins/hs_apps/http_simple_post.c [new file with mode: 0644]
src/plugins/http/http.c
src/plugins/http/http.h
src/plugins/http/http_buffer.c
src/plugins/http/http_plugin.rst

index 75db60d..c45f7c2 100644 (file)
@@ -10,6 +10,7 @@ import (
        "net/http"
        "net/http/httptrace"
        "os"
+       "strconv"
        "sync"
        "time"
 
@@ -27,9 +28,10 @@ func init() {
                HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
                HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
                HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
-               HttpHeadersTest, HttpStaticFileHandler, HttpClientTest, HttpClientErrRespTest)
-       RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest, PromConcurrentConnections,
-               PromMemLeakTest)
+               HttpHeadersTest, HttpStaticFileHandlerTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
+               HttpClientPostFileTest, HttpClientPostFilePtrTest)
+       RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest, PromConcurrentConnectionsTest,
+               PromMemLeakTest, HttpClientPostMemLeakTest)
 }
 
 const wwwRootPath = "/tmp/www_root"
@@ -46,6 +48,7 @@ func httpDownloadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data in
        defer resp.Body.Close()
        s.AssertEqual(200, resp.StatusCode)
        _, err = io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
        duration := time.Since(t)
        experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2))
 }
@@ -114,7 +117,6 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) {
        body, err = io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
        s.AssertEqual(string(body), "some data")
-       s.AssertNil(err, fmt.Sprint(err))
        o2 := vpp.Vppctl("show session verbose proto http state ready")
        s.Log(o2)
        s.AssertContains(o2, "ESTABLISHED")
@@ -196,6 +198,7 @@ func HttpClientTest(s *NoTopoSuite) {
        server.HTTPTestServer.Listener = l
        server.AppendHandlers(
                ghttp.CombineHandlers(
+                       s.LogHttpReq(true),
                        ghttp.VerifyRequest("GET", "/test"),
                        ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}),
                        ghttp.VerifyHeader(http.Header{"Accept": []string{"text / html"}}),
@@ -220,6 +223,7 @@ func HttpClientErrRespTest(s *NoTopoSuite) {
        server.HTTPTestServer.Listener = l
        server.AppendHandlers(
                ghttp.CombineHandlers(
+                       s.LogHttpReq(true),
                        ghttp.VerifyRequest("GET", "/test"),
                        ghttp.RespondWith(http.StatusNotFound, "404: Not Found"),
                ))
@@ -233,6 +237,74 @@ func HttpClientErrRespTest(s *NoTopoSuite) {
        s.AssertContains(o, "404: Not Found", "error not found in the result!")
 }
 
+func HttpClientPostFormTest(s *NoTopoSuite) {
+       serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+       body := "field1=value1&field2=value2"
+
+       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(true),
+                       ghttp.VerifyRequest("POST", "/test"),
+                       ghttp.VerifyContentType("application / x-www-form-urlencoded"),
+                       ghttp.VerifyBody([]byte(body)),
+                       ghttp.RespondWith(http.StatusOK, nil),
+               ))
+       server.Start()
+       defer server.Close()
+
+       uri := "http://" + serverAddress + "/80"
+       vpp := s.GetContainerByName("vpp").VppInstance
+       o := vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+       s.Log(o)
+       s.AssertNotContains(o, "error")
+}
+
+func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) {
+       serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+       vpp := s.GetContainerByName("vpp").VppInstance
+       fileName := "/tmp/test_file.txt"
+       s.Log(vpp.Container.Exec("fallocate -l " + strconv.Itoa(fileSize) + " " + fileName))
+       s.Log(vpp.Container.Exec("ls -la " + fileName))
+
+       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("POST", "/test"),
+                       ghttp.VerifyHeader(http.Header{"Content-Length": []string{strconv.Itoa(fileSize)}}),
+                       ghttp.VerifyContentType("application / octet - stream"),
+                       ghttp.RespondWith(http.StatusOK, nil),
+               ))
+       server.Start()
+       defer server.Close()
+
+       uri := "http://" + serverAddress + "/80"
+       cmd := "http post uri " + uri + " target /test file " + fileName
+       if usePtr {
+               cmd += " use-ptr"
+       }
+       o := vpp.Vppctl(cmd)
+
+       s.Log(o)
+       s.AssertNotContains(o, "error")
+}
+
+func HttpClientPostFileTest(s *NoTopoSuite) {
+       httpClientPostFile(s, false, 32768)
+}
+
+func HttpClientPostFilePtrTest(s *NoTopoSuite) {
+       httpClientPostFile(s, true, 131072)
+}
+
 func HttpStaticPromTest(s *NoTopoSuite) {
        query := "stats.prom"
        vpp := s.GetContainerByName("vpp").VppInstance
@@ -252,6 +324,7 @@ func HttpStaticPromTest(s *NoTopoSuite) {
        s.AssertContains(resp.Header.Get("Content-Type"), "plain")
        s.AssertNotEqual(int64(0), resp.ContentLength)
        _, err = io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
 }
 
 func promReq(s *NoTopoSuite, url string) {
@@ -263,6 +336,7 @@ func promReq(s *NoTopoSuite, url string) {
        defer resp.Body.Close()
        s.AssertEqual(200, resp.StatusCode)
        _, err = io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
 }
 
 func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) {
@@ -271,7 +345,7 @@ func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) {
        promReq(s, url)
 }
 
-func PromConcurrentConnections(s *NoTopoSuite) {
+func PromConcurrentConnectionsTest(s *NoTopoSuite) {
        vpp := s.GetContainerByName("vpp").VppInstance
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
        url := "http://" + serverAddress + ":80/stats.prom"
@@ -341,6 +415,9 @@ func HttpClientGetMemLeakTest(s *VethsSuite) {
        /* warmup request (FIB) */
        clientContainer.Vppctl("http cli client uri " + uri + " query /show/version")
 
+       /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+       time.Sleep(time.Second * 12)
+
        clientContainer.EnableMemoryTrace()
        traces1, err := clientContainer.GetMemoryTrace()
        s.AssertNil(err, fmt.Sprint(err))
@@ -355,13 +432,64 @@ func HttpClientGetMemLeakTest(s *VethsSuite) {
        clientContainer.MemLeakCheck(traces1, traces2)
 }
 
-func HttpStaticFileHandler(s *NoTopoSuite) {
+func HttpClientPostMemLeakTest(s *NoTopoSuite) {
+       s.SkipUnlessLeakCheck()
+
+       serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString()
+       body := "field1=value1&field2=value2"
+
+       uri := "http://" + serverAddress + "/80"
+       vpp := s.GetContainerByName("vpp").VppInstance
+
+       /* no goVPP less noise */
+       vpp.Disconnect()
+
+       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("POST", "/test"),
+                       ghttp.RespondWith(http.StatusOK, nil),
+               ),
+               ghttp.CombineHandlers(
+                       ghttp.VerifyRequest("POST", "/test"),
+                       ghttp.RespondWith(http.StatusOK, nil),
+               ),
+       )
+       server.Start()
+       defer server.Close()
+
+       /* warmup request (FIB) */
+       vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+       /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */
+       time.Sleep(time.Second * 12)
+
+       vpp.EnableMemoryTrace()
+       traces1, err := vpp.GetMemoryTrace()
+       s.AssertNil(err, fmt.Sprint(err))
+
+       vpp.Vppctl("http post uri " + uri + " target /test data " + body)
+
+       /* let's give it some time to clean up sessions */
+       time.Sleep(time.Second * 12)
+
+       traces2, err := vpp.GetMemoryTrace()
+       s.AssertNil(err, fmt.Sprint(err))
+       vpp.MemLeakCheck(traces1, traces2)
+}
+
+func HttpStaticFileHandlerTest(s *NoTopoSuite) {
        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)
-       vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
+       err := vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
+       s.AssertNil(err, fmt.Sprint(err))
+       err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
+       s.AssertNil(err, fmt.Sprint(err))
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
        s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m"))
 
@@ -377,6 +505,7 @@ func HttpStaticFileHandler(s *NoTopoSuite) {
        s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
        s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
        body, err := io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
        s.AssertEqual(string(body), content)
        o := vpp.Vppctl("show http static server cache verbose")
        s.Log(o)
@@ -392,6 +521,7 @@ func HttpStaticFileHandler(s *NoTopoSuite) {
        s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
        s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
        body, err = io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
        s.AssertEqual(string(body), content)
 
        req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil)
@@ -405,6 +535,7 @@ func HttpStaticFileHandler(s *NoTopoSuite) {
        s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
        s.AssertEqual(int64(len([]rune(content2))), resp.ContentLength)
        body, err = io.ReadAll(resp.Body)
+       s.AssertNil(err, fmt.Sprint(err))
        s.AssertEqual(string(body), content2)
        o = vpp.Vppctl("show http static server cache verbose")
        s.Log(o)
@@ -416,7 +547,8 @@ func HttpStaticPathTraversalTest(s *NoTopoSuite) {
        vpp := s.GetContainerByName("vpp").VppInstance
        vpp.Container.Exec("mkdir -p " + wwwRootPath)
        vpp.Container.Exec("mkdir -p " + "/tmp/secret_folder")
-       vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
+       err := vpp.Container.CreateFile("/tmp/secret_folder/secret_file.txt", "secret")
+       s.AssertNil(err, fmt.Sprint(err))
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
        s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
 
@@ -436,7 +568,8 @@ func HttpStaticPathTraversalTest(s *NoTopoSuite) {
 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", "<html><body><p>Hello</p></body></html>")
+       err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<html><body><p>Hello</p></body></html>")
+       s.AssertNil(err, fmt.Sprint(err))
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
        s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug"))
 
index 028ab0b..ff5b02e 100644 (file)
@@ -7,6 +7,8 @@ import (
        "fmt"
        "io"
        "log"
+       "net/http"
+       "net/http/httputil"
        "os"
        "os/exec"
        "path/filepath"
@@ -561,7 +563,7 @@ func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs
 }
 
 /*
-runBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
+RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
 passing in suite context, experiment and your data.
 
 You can also instruct runBenchmark to run with multiple concurrent workers.
@@ -578,3 +580,19 @@ func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callba
        }, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
        AddReportEntry(experiment.Name, experiment)
 }
+
+/*
+LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
+
+You should put it at the first place, so request is logged always.
+*/
+func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
+       return func(w http.ResponseWriter, req *http.Request) {
+               dump, err := httputil.DumpRequest(req, body)
+               if err == nil {
+                       s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
+                               string(dump) +
+                               "\n------------------------------\n")
+               }
+       }
+}
index 3553a25..ba03e39 100644 (file)
@@ -21,6 +21,7 @@ add_vpp_plugin(hs_apps
   hs_apps.c
   http_cli.c
   http_client_cli.c
+  http_simple_post.c
   http_tps.c
   proxy.c
   test_builtins.c
diff --git a/src/plugins/hs_apps/http_simple_post.c b/src/plugins/hs_apps/http_simple_post.c
new file mode 100644 (file)
index 0000000..b0b9a38
--- /dev/null
@@ -0,0 +1,569 @@
+/* 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;
+  u32 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;
+  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);
+
+  rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hspm->data), hspm->data);
+  if (rv != vec_len (hspm->data))
+    {
+      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;
+  u32 to_send;
+  int rv;
+
+  to_send = vec_len (hspm->data) - hspm->data_offset;
+  rv = svm_fifo_enqueue (s->tx_fifo, to_send, 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;
+    }
+
+  vlib_worker_thread_barrier_sync (vm);
+  vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */);
+  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);
\ No newline at end of file
index 017fd50..c504b0c 100644 (file)
@@ -385,10 +385,16 @@ static const char *http_response_template = "HTTP/1.1 %s\r\n"
 /**
  * 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"
-                                          "%s";
+static const char *http_get_request_template = "GET %s HTTP/1.1\r\n"
+                                              "Host: %v\r\n"
+                                              "User-Agent: %v\r\n"
+                                              "%s";
+
+static const char *http_post_request_template = "POST %s HTTP/1.1\r\n"
+                                               "Host: %v\r\n"
+                                               "User-Agent: %v\r\n"
+                                               "Content-Length: %u\r\n"
+                                               "%s";
 
 static u32
 http_send_data (http_conn_t *hc, u8 *data, u32 length, u32 offset)
@@ -1134,9 +1140,11 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp)
 {
   http_msg_t msg;
   session_t *as;
-  u8 *target = 0, *request;
+  u8 *target_buff = 0, *request = 0, *target;
   u32 offset;
   int rv;
+  http_sm_result_t sm_result = HTTP_SM_ERROR;
+  http_state_t next_state;
 
   as = session_get_from_handle (hc->h_pa_session_handle);
 
@@ -1155,47 +1163,107 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp)
       goto error;
     }
 
-  /* currently we support only GET method */
-  if (msg.method_type != HTTP_REQ_GET)
+  /* read request target */
+  if (msg.data.type == HTTP_MSG_DATA_PTR)
     {
-      clib_warning ("unsupported method %d", msg.method_type);
-      goto error;
+      uword target_ptr;
+      rv = svm_fifo_dequeue (as->tx_fifo, sizeof (target_ptr),
+                            (u8 *) &target_ptr);
+      ASSERT (rv == sizeof (target_ptr));
+      target = uword_to_pointer (target_ptr, u8 *);
     }
-  if (msg.data.body_len != 0)
+  else
     {
-      clib_warning ("GET request shouldn't include data");
-      goto error;
+      vec_validate (target_buff, msg.data.target_path_len - 1);
+      rv =
+       svm_fifo_dequeue (as->tx_fifo, msg.data.target_path_len, target_buff);
+      ASSERT (rv == msg.data.target_path_len);
+      target = target_buff;
     }
 
-  /* 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);
-
-  /*
-   * 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");
+  /* currently we support only GET and POST method */
+  if (msg.method_type == HTTP_REQ_GET)
+    {
+      if (msg.data.body_len)
+       {
+         clib_warning ("GET request shouldn't include data");
+         goto error;
+       }
+      /*
+       * Add "protocol layer" headers:
+       * - host
+       * - user agent
+       */
+      request = format (0, http_get_request_template,
+                       /* target */
+                       target,
+                       /* Host */
+                       hc->host,
+                       /* User-Agent */
+                       hc->app_name,
+                       /* Any headers from app? */
+                       msg.data.headers_len ? "" : "\r\n");
+
+      next_state = HTTP_STATE_WAIT_SERVER_REPLY;
+      sm_result = HTTP_SM_STOP;
+    }
+  else if (msg.method_type == HTTP_REQ_POST)
+    {
+      if (!msg.data.body_len)
+       {
+         clib_warning ("POST request should include data");
+         goto error;
+       }
+      /*
+       * Add "protocol layer" headers:
+       * - host
+       * - user agent
+       * - content length
+       */
+      request = format (0, http_post_request_template,
+                       /* target */
+                       target,
+                       /* Host */
+                       hc->host,
+                       /* User-Agent */
+                       hc->app_name,
+                       /* Content-Length */
+                       msg.data.body_len,
+                       /* Any headers from app? */
+                       msg.data.headers_len ? "" : "\r\n");
+
+      http_buffer_init (&hc->tx_buf, msg_to_buf_type[msg.data.type],
+                       as->tx_fifo, msg.data.body_len);
+
+      next_state = HTTP_STATE_APP_IO_MORE_DATA;
+      sm_result = HTTP_SM_CONTINUE;
+    }
+  else
+    {
+      clib_warning ("unsupported method %d", msg.method_type);
+      goto error;
+    }
 
   /* 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, "got headers from app, len %d", msg.data.headers_len);
+      if (msg.data.type == HTTP_MSG_DATA_PTR)
+       {
+         uword app_headers_ptr;
+         rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr),
+                                (u8 *) &app_headers_ptr);
+         ASSERT (rv == sizeof (app_headers_ptr));
+         vec_append (request, uword_to_pointer (app_headers_ptr, u8 *));
+       }
+      else
+       {
+         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);
 
@@ -1203,22 +1271,23 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp)
   if (offset != vec_len (request))
     {
       clib_warning ("sending request failed!");
+      sm_result = HTTP_SM_ERROR;
       goto error;
     }
 
-  http_state_change (hc, HTTP_STATE_WAIT_SERVER_REPLY);
-
-  vec_free (target);
-  vec_free (request);
-
-  return HTTP_SM_STOP;
+  http_state_change (hc, next_state);
+  goto done;
 
 error:
   svm_fifo_dequeue_drop_all (as->tx_fifo);
   session_transport_closing_notify (&hc->connection);
   session_transport_closed_notify (&hc->connection);
   http_disconnect_transport (hc);
-  return HTTP_SM_ERROR;
+
+done:
+  vec_free (target_buff);
+  vec_free (request);
+  return sm_result;
 }
 
 static http_sm_result_t
@@ -1334,8 +1403,11 @@ http_state_app_io_more_data (http_conn_t *hc, transport_send_params_t *sp)
       if (sent && svm_fifo_set_event (ts->tx_fifo))
        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);
+      /* Finished transaction:
+       * server back to HTTP_STATE_WAIT_METHOD
+       * client to HTTP_STATE_WAIT_SERVER_REPLY */
+      http_state_change (hc, hc->is_server ? HTTP_STATE_WAIT_CLIENT_METHOD :
+                                                  HTTP_STATE_WAIT_SERVER_REPLY);
       http_buffer_free (&hc->tx_buf);
     }
 
index e3267df..63d0586 100644 (file)
@@ -132,6 +132,8 @@ typedef enum http_target_form_
   _ (APP_XSLX, ".xlsx",                                                       \
      "application / vnd.openxmlformats - officedocument.spreadsheetml.sheet") \
   _ (APP_XUL, ".xul", "application / vnd.mozilla.xul + xml")                  \
+  _ (APP_X_WWW_FORM_URLENCODED, ".invalid",                                   \
+     "application / x-www-form-urlencoded")                                   \
   _ (APP_ZIP, ".zip", "application / zip")                                    \
   _ (AUDIO_AAC, ".aac", "audio / aac")                                        \
   _ (AUDIO_CD, ".cda", "application / x - cdf")                               \
index f3dc308..bc1b8c0 100644 (file)
@@ -173,7 +173,7 @@ buf_ptr_drain (http_buffer_t *hb, u32 len)
   bf->segs[1].data += len;
   bf->segs[0].len -= len;
 
-  HTTP_DBG (1, "drained %u left %u", len, bf->segs[1].len);
+  HTTP_DBG (1, "drained %u left %u", len, bf->segs[0].len);
 
   if (!bf->segs[0].len)
     {
index 2738127..4daef79 100644 (file)
@@ -9,14 +9,14 @@ Overview
 --------
 
 This plugin adds the HTTP protocol to VPP's Host Stack.
-As a result parsing of HTTP/1 request or response is available for internal VPP applications.
+As a result parsing and serializing of HTTP/1 requests or responses are available for internal VPP applications.
 
 Usage
 -----
 
 The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``,
 ``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_parse_headers``, ``http_get_header``,
-``http_free_header_table``.
+``http_free_header_table``, ``http_add_header``, ``http_serialize_headers``.
 
 It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications.
 
@@ -238,12 +238,18 @@ Finally application sends response body:
   if (svm_fifo_set_event (ts->tx_fifo))
     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:
+Examples above shows how to send body and headers by copy, alternatively you could pass them as pointer:
 
 .. code-block:: C
 
   msg.data.type = HTTP_MSG_DATA_PTR;
   /* code omitted for brevity */
+  if (msg.data.headers_len)
+    {
+      uword headers = pointer_to_uword (headers_buf);
+      rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (headers), (u8 *) &headers);
+      ASSERT (rv == sizeof (headers));
+    }
   uword data = pointer_to_uword (tx_buf);
   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
   ASSERT (rv == sizeof (data));
@@ -332,6 +338,28 @@ Finally application sends everything to HTTP layer:
   if (svm_fifo_set_event (as->tx_fifo))
     session_program_tx_io_evt (as->handle, SESSION_IO_EVT_TX);
 
+Examples above shows how to send buffers by copy, alternatively you could pass them as pointer:
+
+.. code-block:: C
+
+  msg.data.type = HTTP_MSG_DATA_PTR;
+  msg.method_type = HTTP_REQ_POST;
+  msg.data.body_len = vec_len (data);
+  /* code omitted for brevity */
+  uword target = pointer_to_uword (target);
+  uword headers = pointer_to_uword (headers_buf);
+  uword body = pointer_to_uword (data);
+  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)));
+
+In this case you need to free data when you receive response or when session is closed.
+
 Receiving data
 """"""""""""""