http: client can receive response while sending 29/43229/2 master
authorMatus Fabian <[email protected]>
Thu, 19 Jun 2025 13:48:04 +0000 (13:48 +0000)
committerFlorin Coras <[email protected]>
Thu, 19 Jun 2025 16:11:41 +0000 (16:11 +0000)
Client can receive response (error) from server while still sending
body bytes, handle this as exception in state machine instead of error.

Type: improvement

Change-Id: I6aa3f7f5aaa299ac781109dd75295a7eb3a42cf9
Signed-off-by: Matus Fabian <[email protected]>
extras/hs-test/http_test.go
src/plugins/hs_apps/http_client.c
src/plugins/http/http1.c

index 6047270..8670b1d 100644 (file)
@@ -38,7 +38,7 @@ func init() {
                HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest,
                HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest,
                HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest,
-               HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest)
+               HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest, HttpClientPostRejectedTest)
        RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
                PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
                PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest)
@@ -793,6 +793,36 @@ func HttpClientPostFilePtrTest(s *NoTopoSuite) {
        httpClientPostFile(s, true, 131072)
 }
 
+func HttpClientPostRejectedTest(s *NoTopoSuite) {
+       serverAddress := s.HostAddr() + ":" + s.Ports.Http
+       vpp := s.Containers.Vpp.VppInstance
+       fileName := "/tmp/test_file.txt"
+       // send something big so we are sure that server respond when we are still sending body
+       s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(10<<20)+" "+fileName))
+       s.Log(vpp.Container.Exec(false, "ls -la "+fileName))
+
+       server := ghttp.NewUnstartedServer()
+       l, err := net.Listen("tcp", serverAddress)
+       s.AssertNil(err, fmt.Sprint(err))
+       server.HTTPTestServer.Listener = l
+       server.AppendHandlers(
+               ghttp.CombineHandlers(
+                       s.LogHttpReq(false),
+                       ghttp.VerifyRequest("POST", "/test"),
+                       ghttp.RespondWith(http.StatusForbidden, nil),
+               ))
+       server.Start()
+       defer server.Close()
+
+       uri := "http://" + serverAddress + "/test"
+       cmd := "http client post verbose uri " + uri + " file " + fileName
+       o := vpp.Vppctl(cmd)
+
+       s.Log(o)
+       s.AssertContains(o, "403")
+       s.Log(vpp.Vppctl("show session verbose 2"))
+}
+
 func HttpStaticPromTest(s *NoTopoSuite) {
        query := "stats.prom"
        vpp := s.Containers.Vpp.VppInstance
index d49c6a4..937d98e 100644 (file)
@@ -483,6 +483,8 @@ hc_rx_callback (session_t *s)
       if (msg.data.body_len == 0)
        {
          svm_fifo_dequeue_drop_all (s->rx_fifo);
+         /* we don't need to print warning about binary content */
+         hc_session->session_flags |= HC_S_FLAG_PRINTABLE_BODY;
          goto done;
        }
 
index a0aaf06..106a1fd 100644 (file)
@@ -1975,15 +1975,30 @@ http1_transport_rx_callback (http_conn_t *hc)
   if (!http1_req_state_is_rx_valid (req))
     {
       if (http_io_ts_max_read (hc))
-       clib_warning ("hc [%u]%x invalid rx state: http req state "
-                     "'%U', session state '%U'",
-                     hc->c_thread_index, hc->hc_hc_index,
-                     format_http_req_state, req->state,
-                     format_http_conn_state, hc);
-      http_io_ts_drain_all (hc);
+       {
+         if (req->state == HTTP_REQ_STATE_APP_IO_MORE_DATA &&
+             !(hc->flags & HTTP_CONN_F_IS_SERVER))
+           {
+             /* client can receive error response from server when still
+              * sending content */
+             /* TODO: 100 continue support */
+             HTTP_DBG (1, "server send response while client sending data");
+             http_io_as_drain_all (req);
+             hc->state = HTTP_CONN_STATE_CLOSED;
+             http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+             goto run_sm;
+           }
+         clib_warning ("hc [%u]%x invalid rx state: http req state "
+                       "'%U', session state '%U'",
+                       hc->c_thread_index, hc->hc_hc_index,
+                       format_http_req_state, req->state,
+                       format_http_conn_state, hc);
+         http_io_ts_drain_all (hc);
+       }
       return;
     }
 
+run_sm:
   HTTP_DBG (1, "run state machine");
   http1_req_run_state_machine (hc, req, 0, 0);
 }