summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatus Fabian <matfabia@cisco.com>2024-07-31 16:08:40 +0200
committerFlorin Coras <florin.coras@gmail.com>2024-08-06 16:01:02 +0000
commitd46e674abc5605b58584bbf9a0607ef621ca0eee (patch)
tree4f42d19311c0036726d72d5d17af97082a05bed0
parentfc3464dac99c9e3e56616e6c6bc6d10886b2f567 (diff)
http: client POST method
Type: improvement Change-Id: Iaa70abcee02866f9a6426a6e8e4709eeba0e8114 Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r--extras/hs-test/http_test.go153
-rw-r--r--extras/hs-test/infra/hst_suite.go20
-rw-r--r--src/plugins/hs_apps/CMakeLists.txt1
-rw-r--r--src/plugins/hs_apps/http_simple_post.c569
-rw-r--r--src/plugins/http/http.c164
-rw-r--r--src/plugins/http/http.h2
-rw-r--r--src/plugins/http/http_buffer.c2
-rw-r--r--src/plugins/http/http_plugin.rst34
8 files changed, 884 insertions, 61 deletions
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index 75db60d43ae..c45f7c2d57c 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -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"))
diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go
index 028ab0bef1f..ff5b02eb95b 100644
--- a/extras/hs-test/infra/hst_suite.go
+++ b/extras/hs-test/infra/hst_suite.go
@@ -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")
+ }
+ }
+}
diff --git a/src/plugins/hs_apps/CMakeLists.txt b/src/plugins/hs_apps/CMakeLists.txt
index 3553a25837e..ba03e393f44 100644
--- a/src/plugins/hs_apps/CMakeLists.txt
+++ b/src/plugins/hs_apps/CMakeLists.txt
@@ -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
index 00000000000..b0b9a38db2d
--- /dev/null
+++ b/src/plugins/hs_apps/http_simple_post.c
@@ -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
diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c
index 017fd50ebae..c504b0c5028 100644
--- a/src/plugins/http/http.c
+++ b/src/plugins/http/http.c
@@ -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);
}
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h
index e3267dfc3b2..63d05860099 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -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") \
diff --git a/src/plugins/http/http_buffer.c b/src/plugins/http/http_buffer.c
index f3dc308dbf8..bc1b8c08630 100644
--- a/src/plugins/http/http_buffer.c
+++ b/src/plugins/http/http_buffer.c
@@ -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)
{
diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst
index 273812735f7..4daef79b9cf 100644
--- a/src/plugins/http/http_plugin.rst
+++ b/src/plugins/http/http_plugin.rst
@@ -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
""""""""""""""