http: http2 client side 32/43432/10
authorMatus Fabian <[email protected]>
Thu, 10 Jul 2025 16:39:37 +0000 (12:39 -0400)
committerMatus Fabian <[email protected]>
Wed, 16 Jul 2025 15:03:34 +0000 (11:03 -0400)
- only with TLS
- request are serialized within one app session (no multiplexing)
- http version can be specified in http client

Type: feature

Change-Id: I2fe11bd3252985d1bd1732616837f7a91f37f6a3
Signed-off-by: Matus Fabian <[email protected]>
20 files changed:
extras/hs-test/docker/Dockerfile.nginx-server
extras/hs-test/http2_test.go
extras/hs-test/infra/suite_envoy_proxy.go
extras/hs-test/infra/suite_http1.go
extras/hs-test/infra/suite_http2.go
extras/hs-test/infra/suite_no_topo.go
extras/hs-test/infra/suite_no_topo6.go
extras/hs-test/infra/suite_veth.go
extras/hs-test/infra/suite_vpp_proxy.go
extras/hs-test/resources/nginx/nginx_server.conf
extras/hs-test/topo-containers/envoyProxy.yaml
extras/hs-test/topo-containers/single.yaml
extras/hs-test/topo-containers/vppProxy.yaml
src/plugins/hs_apps/http_client.c
src/plugins/http/http.c
src/plugins/http/http.h
src/plugins/http/http2/frame.c
src/plugins/http/http2/http2.c
src/plugins/http/http2/http2.h
src/plugins/http/http_private.h

index 5284ea7..30f32a5 100644 (file)
@@ -9,6 +9,7 @@ COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh
 
 COPY resources/nginx/html/index.html /usr/share/nginx/index.html
 RUN fallocate -l 10MB /usr/share/nginx/httpTestFile
+RUN touch /usr/share/nginx/test_upload
 RUN mkdir /usr/share/nginx/upload && chmod 777 /usr/share/nginx/upload
 
 ENTRYPOINT ["nginx_server_entrypoint.sh"]
index 011e4b9..238d335 100644 (file)
@@ -10,8 +10,10 @@ import (
 )
 
 func init() {
-       RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest)
+       RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest,
+               Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest)
        RegisterH2MWTests(Http2MultiplexingMWTest)
+       RegisterVethTests(Http2CliTlsTest, Http2ClientContinuationTest)
 }
 
 func Http2TcpGetTest(s *Http2Suite) {
@@ -155,3 +157,90 @@ func Http2ServerMemLeakTest(s *Http2Suite) {
        s.AssertNil(err, fmt.Sprint(err))
        vpp.MemLeakCheck(traces1, traces2)
 }
+
+func Http2CliTlsTest(s *VethsSuite) {
+       uri := "https://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+
+       s.Containers.ServerVpp.VppInstance.Vppctl("http cli server uri " + uri)
+
+       o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" +
+               " uri " + uri + "/show/version")
+       s.Log(o)
+       s.AssertContains(o, "<html>", "<html> not found in the result!")
+       s.AssertContains(o, "</html>", "</html> not found in the result!")
+
+       /* second request to test postponed ho-cleanup */
+       o = s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" +
+               " uri " + uri + "/show/vlib/graph")
+       s.Log(o)
+       s.AssertContains(o, "<html>", "<html> not found in the result!")
+       s.AssertContains(o, "</html>", "</html> not found in the result!")
+}
+
+func Http2ClientGetTest(s *Http2Suite) {
+       vpp := s.Containers.Vpp.VppInstance
+       serverAddress := s.HostAddr() + ":" + s.Ports.Port2
+
+       s.CreateNginxServer()
+       s.AssertNil(s.Containers.NginxServer.Start())
+
+       uri := "https://" + serverAddress + "/httpTestFile"
+       o := vpp.Vppctl("http client save-to response.txt verbose uri " + uri)
+       s.Log(o)
+       s.AssertContains(o, "HTTP/2 200 OK")
+       s.AssertContains(o, "10000000 bytes saved to file")
+}
+
+func http2ClientPostFile(s *Http2Suite, usePtr bool, fileSize int) {
+       vpp := s.Containers.Vpp.VppInstance
+       serverAddress := s.HostAddr() + ":" + s.Ports.Port2
+
+       fileName := "/tmp/test_file.txt"
+       s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(fileSize)+" "+fileName))
+       s.Log(vpp.Container.Exec(false, "ls -la "+fileName))
+
+       s.CreateNginxServer()
+       s.AssertNil(s.Containers.NginxServer.Start())
+
+       uri := "https://" + serverAddress + "/test_upload"
+       cmd := "http client post verbose uri " + uri + " file " + fileName
+       if usePtr {
+               cmd += " use-ptr"
+       }
+       o := vpp.Vppctl(cmd)
+       s.Log(o)
+       s.AssertContains(o, "HTTP/2 200 OK")
+}
+
+func Http2ClientPostTest(s *Http2Suite) {
+       http2ClientPostFile(s, false, 131072)
+}
+
+func Http2ClientPostPtrTest(s *Http2Suite) {
+       http2ClientPostFile(s, true, 131072)
+}
+
+func Http2ClientGetRepeatTest(s *Http2Suite) {
+       vpp := s.Containers.Vpp.VppInstance
+       serverAddress := s.HostAddr() + ":" + s.Ports.Port2
+
+       s.CreateNginxServer()
+       s.AssertNil(s.Containers.NginxServer.Start())
+
+       uri := "https://" + serverAddress + "/64B"
+       cmd := fmt.Sprintf("http client http2 repeat %d uri %s", 10, uri)
+       o := vpp.Vppctl(cmd)
+       s.Log(o)
+}
+
+func Http2ClientContinuationTest(s *VethsSuite) {
+       serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+
+       s.Containers.ServerVpp.VppInstance.Vppctl("http tps uri tls://" + serverAddress + " no-zc")
+
+       uri := fmt.Sprintf("https://%s/test_file_64?test_header=32k", serverAddress)
+       o := s.Containers.ClientVpp.VppInstance.Vppctl("http client fifo-size 64k verbose save-to response.txt uri " + uri)
+       s.Log(o)
+       s.AssertContains(o, "HTTP/2 200 OK")
+       s.AssertGreaterThan(strings.Count(o, "x"), 32768)
+}
index 42a5b93..1932f18 100644 (file)
@@ -31,6 +31,7 @@ type EnvoyProxySuite struct {
        }
        Ports struct {
                Nginx      uint16
+               NginxSsl   uint16
                Proxy      uint16
                EnvoyAdmin uint16
        }
@@ -64,6 +65,7 @@ func (s *EnvoyProxySuite) SetupSuite() {
        s.Containers.EnvoyProxy = s.GetContainerByName("envoy-vcl")
        s.Containers.Curl = s.GetContainerByName("curl")
        s.Ports.Nginx = s.GeneratePortAsInt()
+       s.Ports.NginxSsl = s.GeneratePortAsInt()
        s.Ports.Proxy = s.GeneratePortAsInt()
        s.Ports.EnvoyAdmin = s.GeneratePortAsInt()
 }
@@ -89,11 +91,15 @@ func (s *EnvoyProxySuite) SetupTest() {
                LogPrefix string
                Address   string
                Port      uint16
+               PortSsl   uint16
+               Http2     string
                Timeout   int
        }{
                LogPrefix: s.Containers.NginxServerTransient.Name,
                Address:   s.Interfaces.Server.Ip4AddressString(),
                Port:      s.Ports.Nginx,
+               PortSsl:   s.Ports.NginxSsl,
+               Http2:     "off",
                Timeout:   s.maxTimeout,
        }
        s.Containers.NginxServerTransient.CreateConfigFromTemplate(
index ff2a65f..248a413 100644 (file)
@@ -24,8 +24,9 @@ type Http1Suite struct {
                Wrk         *Container
        }
        Ports struct {
-               NginxServer string
-               Http        string
+               NginxServer    string
+               NginxServerSsl string
+               Http           string
        }
 }
 
@@ -49,6 +50,7 @@ func (s *Http1Suite) SetupSuite() {
        s.Containers.Wrk = s.GetContainerByName("wrk")
        s.Ports.Http = s.GeneratePort()
        s.Ports.NginxServer = s.GeneratePort()
+       s.Ports.NginxServerSsl = s.GeneratePort()
 }
 
 func (s *Http1Suite) SetupTest() {
@@ -90,11 +92,15 @@ func (s *Http1Suite) CreateNginxServer() {
                LogPrefix string
                Address   string
                Port      string
+               PortSsl   string
+               Http2     string
                Timeout   int
        }{
                LogPrefix: s.Containers.NginxServer.Name,
                Address:   s.Interfaces.Tap.Ip4AddressString(),
                Port:      s.Ports.NginxServer,
+               PortSsl:   s.Ports.NginxServerSsl,
+               Http2:     "off",
                Timeout:   600,
        }
        s.Containers.NginxServer.CreateConfigFromTemplate(
index a0507a3..d67399b 100644 (file)
@@ -30,13 +30,15 @@ type Http2Suite struct {
                Tap *NetInterface
        }
        Containers struct {
-               Vpp    *Container
-               Curl   *Container
-               H2load *Container
+               Vpp         *Container
+               Curl        *Container
+               H2load      *Container
+               NginxServer *Container
        }
        Ports struct {
                Port1      string
                Port1AsInt int
+               Port2      string
        }
 }
 
@@ -58,7 +60,9 @@ func (s *Http2Suite) SetupSuite() {
        s.Containers.Vpp = s.GetContainerByName("vpp")
        s.Containers.Curl = s.GetContainerByName("curl")
        s.Containers.H2load = s.GetContainerByName("h2load")
+       s.Containers.NginxServer = s.GetTransientContainerByName("nginx-server")
        s.Ports.Port1 = s.GeneratePort()
+       s.Ports.Port2 = s.GeneratePort()
        var err error
        s.Ports.Port1AsInt, err = strconv.Atoi(s.Ports.Port1)
        s.AssertNil(err)
@@ -92,6 +96,34 @@ func (s *Http2Suite) VppAddr() string {
        return s.Interfaces.Tap.Peer.Ip4AddressString()
 }
 
+func (s *Http2Suite) HostAddr() string {
+       return s.Interfaces.Tap.Ip4AddressString()
+}
+
+func (s *Http2Suite) CreateNginxServer() {
+       s.AssertNil(s.Containers.NginxServer.Create())
+       nginxSettings := struct {
+               LogPrefix string
+               Address   string
+               Port      string
+               PortSsl   string
+               Http2     string
+               Timeout   int
+       }{
+               LogPrefix: s.Containers.NginxServer.Name,
+               Address:   s.Interfaces.Tap.Ip4AddressString(),
+               Port:      s.Ports.Port1,
+               PortSsl:   s.Ports.Port2,
+               Http2:     "on",
+               Timeout:   600,
+       }
+       s.Containers.NginxServer.CreateConfigFromTemplate(
+               "/nginx.conf",
+               "./resources/nginx/nginx_server.conf",
+               nginxSettings,
+       )
+}
+
 var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() {
        var s Http2Suite
        BeforeAll(func() {
index 1749d13..5a411ad 100644 (file)
@@ -29,9 +29,10 @@ type NoTopoSuite struct {
                Ab          *Container
        }
        Ports struct {
-               NginxServer string
-               NginxHttp3  string
-               Http        string
+               NginxServer    string
+               NginxServerSsl string
+               NginxHttp3     string
+               Http           string
        }
 }
 
@@ -59,6 +60,7 @@ func (s *NoTopoSuite) SetupSuite() {
        s.Containers.Ab = s.GetContainerByName("ab")
        s.Ports.Http = s.GeneratePort()
        s.Ports.NginxServer = s.GeneratePort()
+       s.Ports.NginxServerSsl = s.GeneratePort()
        s.Ports.NginxHttp3 = s.GeneratePort()
 }
 
@@ -125,11 +127,15 @@ func (s *NoTopoSuite) CreateNginxServer() {
                LogPrefix string
                Address   string
                Port      string
+               PortSsl   string
+               Http2     string
                Timeout   int
        }{
                LogPrefix: s.Containers.NginxServer.Name,
                Address:   s.Interfaces.Tap.Ip4AddressString(),
                Port:      s.Ports.NginxServer,
+               PortSsl:   s.Ports.NginxServerSsl,
+               Http2:     "off",
                Timeout:   600,
        }
        s.Containers.NginxServer.CreateConfigFromTemplate(
index a57cb29..0997e8a 100644 (file)
@@ -28,8 +28,9 @@ type NoTopo6Suite struct {
                Ab          *Container
        }
        Ports struct {
-               NginxServer string
-               Http        string
+               NginxServer    string
+               NginxServerSsl string
+               Http           string
        }
 }
 
@@ -54,6 +55,7 @@ func (s *NoTopo6Suite) SetupSuite() {
        s.Containers.Ab = s.GetContainerByName("ab")
        s.Ports.Http = s.GeneratePort()
        s.Ports.NginxServer = s.GeneratePort()
+       s.Ports.NginxServerSsl = s.GeneratePort()
 }
 
 func (s *NoTopo6Suite) SetupTest() {
@@ -119,11 +121,15 @@ func (s *NoTopo6Suite) CreateNginxServer() {
                LogPrefix string
                Address   string
                Port      string
+               PortSsl   string
+               Http2     string
                Timeout   int
        }{
                LogPrefix: s.Containers.NginxServer.Name,
                Address:   "[" + s.Interfaces.Tap.Ip6AddressString() + "]",
                Port:      s.Ports.NginxServer,
+               PortSsl:   s.Ports.NginxServerSsl,
+               Http2:     "off",
                Timeout:   600,
        }
        s.Containers.NginxServer.CreateConfigFromTemplate(
index 2b7832e..65506c4 100644 (file)
@@ -70,13 +70,16 @@ func (s *VethsSuite) SetupTest() {
        } else {
                sessionConfig.Close()
        }
+       // For http/2 continuation frame test between http tps and http client
+       var httpConfig Stanza
+       httpConfig.NewStanza("http").NewStanza("http2").Append("max-header-list-size 65536")
 
        // ... For server
        serverVpp, err := s.Containers.ServerVpp.newVppInstance(s.Containers.ServerVpp.AllocatedCpus, sessionConfig)
        s.AssertNotNil(serverVpp, fmt.Sprint(err))
 
        // ... For client
-       clientVpp, err := s.Containers.ClientVpp.newVppInstance(s.Containers.ClientVpp.AllocatedCpus, sessionConfig)
+       clientVpp, err := s.Containers.ClientVpp.newVppInstance(s.Containers.ClientVpp.AllocatedCpus, sessionConfig, httpConfig)
        s.AssertNotNil(clientVpp, fmt.Sprint(err))
 
        s.SetupServerVpp()
index d43a588..7b108b9 100644 (file)
@@ -41,8 +41,9 @@ type VppProxySuite struct {
                IperfC               *Container
        }
        Ports struct {
-               Server uint16
-               Proxy  uint16
+               Server    uint16
+               ServerSsl uint16
+               Proxy     uint16
        }
 }
 
@@ -65,6 +66,7 @@ func (s *VppProxySuite) SetupSuite() {
        s.LoadNetworkTopology("2taps")
        s.LoadContainerTopology("vppProxy")
        s.Ports.Server = s.GeneratePortAsInt()
+       s.Ports.ServerSsl = s.GeneratePortAsInt()
        s.Ports.Proxy = s.GeneratePortAsInt()
 
        if *IsVppDebug {
@@ -117,11 +119,15 @@ func (s *VppProxySuite) SetupNginxServer() {
                LogPrefix string
                Address   string
                Port      uint16
+               PortSsl   uint16
+               Http2     string
                Timeout   int
        }{
                LogPrefix: s.Containers.NginxServerTransient.Name,
                Address:   s.Interfaces.Server.Ip4AddressString(),
                Port:      s.Ports.Server,
+               PortSsl:   s.Ports.ServerSsl,
+               Http2:     "off",
                Timeout:   s.maxTimeout,
        }
        s.Containers.NginxServerTransient.CreateConfigFromTemplate(
index 26d5834..a40ed7c 100644 (file)
@@ -22,9 +22,15 @@ http {
   server {
     access_log /tmp/nginx/{{.LogPrefix}}-access.log;
     listen {{.Port}};
+    listen {{.PortSsl}} ssl;
     server_name {{.Address}};
     root /usr/share/nginx;
+    ssl_certificate     /etc/nginx/ssl/localhost.crt;
+    ssl_certificate_key /etc/nginx/ssl/localhost.key;
+    http2 {{.Http2}};
     index index.html index.htm;
+    # to allow POST on static pages
+    error_page 405 =200 $uri;
     location ~ "/upload/([0-9a-zA-Z-.]*)$" {
       alias /usr/share/nginx/upload/$1;
       client_body_temp_path /tmp;
@@ -39,5 +45,13 @@ http {
     location / {
       sendfile on;
     }
+    # HTTP2 will not wait for the post body and return 200
+    location = /test_upload {
+      proxy_pass http://127.0.0.1:{{.Port}}/dev-null;
+    }
+    location = /dev-null {
+      return 200;
+    }
+    # HTTP2 upload fix end
   }
 }
index cb2d673..3189080 100644 (file)
@@ -28,6 +28,8 @@ containers:
       - <<: *shared-vol
         container-dir: "/tmp/nginx"
         is-default-work-dir: true
+      - host-dir: $HST_DIR/resources/cert
+        container-dir: "/etc/nginx/ssl"
     image: "hs-test/nginx-server"
     is-optional: true
   - name: "curl"
index d1f43ad..c862a79 100644 (file)
@@ -33,6 +33,8 @@ containers:
       - <<: *shared-vol
         container-dir: "/tmp/nginx"
         is-default-work-dir: true
+      - host-dir: $HST_DIR/resources/cert
+        container-dir: "/etc/nginx/ssl"
     image: "hs-test/nginx-server"
     is-optional: true
 
index 5f02480..54aac03 100644 (file)
@@ -26,6 +26,8 @@ containers:
       - <<: *shared-vol
         container-dir: "/tmp/nginx"
         is-default-work-dir: true
+      - host-dir: $HST_DIR/resources/cert
+        container-dir: "/etc/nginx/ssl"
     image: "hs-test/nginx-server"
     is-optional: true
   - name: "curl"
index ddb92b3..6ddf0b5 100644 (file)
@@ -10,6 +10,7 @@
 #include <http/http_content_types.h>
 #include <http/http_status_codes.h>
 #include <vppinfra/unix.h>
+#include <vnet/tls/tls_types.h>
 
 #define foreach_hc_s_flag                                                     \
   _ (1, IS_CLOSED)                                                            \
@@ -100,6 +101,7 @@ typedef struct
   bool was_transport_closed;
   u32 ckpair_index;
   u64 max_body_size;
+  http_version_t http_version;
 } hc_main_t;
 
 typedef enum
@@ -411,6 +413,7 @@ hc_rx_callback (session_t *s)
   u32 max_deq;
   session_error_t session_err = 0;
   int send_err = 0;
+  http_version_t http_version;
 
   if (hc_session->session_flags & HC_S_FLAG_IS_CLOSED)
     {
@@ -441,21 +444,24 @@ hc_rx_callback (session_t *s)
          http_init_header_table_buf (&hc_session->resp_headers, msg);
 
          if (!hcm->repeat)
-           hc_session->response_status =
-             format (0, "%U", format_http_status_code, msg.code);
+           {
+             http_version = http_session_get_version (s);
+             hc_session->response_status =
+               format (0, "%U %U", format_http_version, http_version,
+                       format_http_status_code, msg.code);
+           }
 
          svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset);
 
          rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len,
                                 hc_session->resp_headers.buf);
          ASSERT (rv == msg.data.headers_len);
-         HTTP_DBG (1,
-                   (char *) format (0, "%U", format_hash,
-                                    hc_session->resp_headers.value_by_name));
          msg.data.body_offset -=
            msg.data.headers_len + msg.data.headers_offset;
 
          http_build_header_table (&hc_session->resp_headers, msg);
+         HTTP_DBG (2, "%U", format_hash,
+                   hc_session->resp_headers.value_by_name);
          const http_token_t *content_type = http_get_header (
            &hc_session->resp_headers,
            http_header_name_token (HTTP_HEADER_CONTENT_TYPE));
@@ -522,6 +528,11 @@ hc_rx_callback (session_t *s)
     }
 
   ASSERT (rv == n_deq);
+  if (svm_fifo_needs_deq_ntf (s->rx_fifo, n_deq))
+    {
+      svm_fifo_clear_deq_ntf (s->rx_fifo);
+      session_program_transport_io_evt (s->handle, SESSION_IO_EVT_RX);
+    }
   if (!(hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY))
     vec_set_len (hc_session->http_response, curr + n_deq);
   ASSERT (hc_session->to_recv >= rv);
@@ -552,10 +563,12 @@ done:
          if (hc_session->stats.elapsed_time >= hcm->duration &&
              hc_session->stats.request_count >= hc_session->stats.req_per_wrk)
            {
+             HTTP_DBG (1, "repeat done");
              hc_session_disconnect_callback (s);
            }
          else
            {
+             HTTP_DBG (1, "doing another repeat");
              send_err = hc_request (s, wrk, hc_session, session_err);
              if (send_err)
                clib_warning ("failed to send request, error %d", send_err);
@@ -633,7 +646,7 @@ hc_attach ()
   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
-    hcm->fifo_size ? hcm->fifo_size : 8 << 10;
+    hcm->fifo_size ? hcm->fifo_size : 32 << 10;
   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
     hcm->fifo_size ? hcm->fifo_size : 32 << 10;
   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
@@ -707,6 +720,17 @@ hc_connect ()
        &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO,
        sizeof (transport_endpt_crypto_cfg_t));
       ext_cfg->crypto.ckpair_index = hcm->ckpair_index;
+      switch (hcm->http_version)
+       {
+       case HTTP_VERSION_1:
+         ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1;
+         break;
+       case HTTP_VERSION_2:
+         ext_cfg->crypto.alpn_protos[1] = TLS_ALPN_PROTO_HTTP_2;
+         break;
+       default:
+         break;
+       }
     }
 
   session_send_rpc_evt_to_thread_force (transport_cl_thread (), hc_connect_rpc,
@@ -955,6 +979,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input,
   hcm->fifo_size = 0;
   hcm->was_transport_closed = false;
   hcm->verbose = false;
+  hcm->http_version = HTTP_VERSION_NA;
   /* default max - 64MB */
   hcm->max_body_size = 64 << 20;
   hc_stats.request_count = 0;
@@ -1035,7 +1060,10 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input,
        ;
       else if (unformat (line_input, "secret %lu", &hcm->appns_secret))
        ;
-
+      else if (unformat (line_input, "http1"))
+       hcm->http_version = HTTP_VERSION_1;
+      else if (unformat (line_input, "http2"))
+       hcm->http_version = HTTP_VERSION_2;
       else
        {
          err = clib_error_return (0, "unknown input `%U'",
@@ -1140,7 +1168,7 @@ VLIB_CLI_COMMAND (hc_command, static) = {
     "[timeout <seconds> (default = 10)] [repeat <count> | duration <seconds>] "
     "[sessions <# of sessions>] [appns <app-ns> secret <appns-secret>] "
     "[fifo-size <nM|G>] [private-segment-size <nM|G>] [prealloc-fifos <n>]"
-    "[max-body-size <nM|G>]",
+    "[max-body-size <nM|G>] [http1|http2]",
   .function = hc_command_fn,
   .is_mp_safe = 1,
 };
index 5a61b0d..ccf987a 100644 (file)
@@ -869,6 +869,7 @@ http_transport_connect (transport_endpoint_cfg_t *tep)
   u32 hc_index;
   session_t *ho;
   transport_endpt_ext_cfg_t *ext_cfg;
+  segment_manager_props_t *props;
   app_worker_t *app_wrk = app_worker_get (sep->app_wrk_index);
 
   clib_memset (cargs, 0, sizeof (*cargs));
@@ -883,7 +884,6 @@ http_transport_connect (transport_endpoint_cfg_t *tep)
   hc->hc_pa_wrk_index = sep->app_wrk_index;
   hc->hc_pa_app_api_ctx = sep->opaque;
   hc->state = HTTP_CONN_STATE_CONNECTING;
-  /* TODO: set to HTTP_VERSION_NA in case of TLS */
   hc->version = HTTP_VERSION_1;
   cargs->api_context = hc_index;
 
@@ -900,7 +900,15 @@ http_transport_connect (transport_endpoint_cfg_t *tep)
   if (ext_cfg)
     {
       HTTP_DBG (1, "app set tls");
+      hc->version = HTTP_VERSION_NA;
       cargs->sep.transport_proto = TRANSPORT_PROTO_TLS;
+      if (ext_cfg->crypto.alpn_protos[0] == TLS_ALPN_PROTO_NONE)
+       {
+         HTTP_DBG (1,
+                   "app do not set alpn list, using default (h2,http/1.1)");
+         ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_2;
+         ext_cfg->crypto.alpn_protos[1] = TLS_ALPN_PROTO_HTTP_1_1;
+       }
     }
 
   if (vec_len (app->name))
@@ -928,6 +936,8 @@ http_transport_connect (transport_endpoint_cfg_t *tep)
     session_type_from_proto_and_ip (TRANSPORT_PROTO_HTTP, sep->is_ip4);
   hc->hc_tc_session_handle = cargs->sh;
   hc->c_s_index = ho->session_index;
+  props = application_segment_manager_properties (app);
+  hc->app_rx_fifo_size = props->rx_fifo_size;
 
   return 0;
 }
index 5777bd5..61c387b 100644 (file)
@@ -20,6 +20,7 @@
 #include <vnet/plugin/plugin.h>
 #include <vnet/ip/format.h>
 #include <vnet/ip/ip46_address.h>
+#include <vnet/session/session.h>
 
 #define HTTP_DEBUG 0
 
 #define HTTP_DBG(_lvl, _fmt, _args...)
 #endif
 
+typedef enum http_version_
+{
+  HTTP_VERSION_1,
+  HTTP_VERSION_2,
+  HTTP_VERSION_3,
+  HTTP_VERSION_NA = 7,
+} http_version_t;
+
 typedef enum http_udp_tunnel_mode_
 {
   HTTP_UDP_TUNNEL_CAPSULE, /**< app receive raw capsule */
@@ -393,6 +402,66 @@ typedef struct http_msg_
   http_msg_data_t data;
 } http_msg_t;
 
+typedef union
+{
+  struct
+  {
+    u32 version : 3;
+    u32 req_index : 29;
+  };
+  u32 as_u32;
+} http_req_handle_t;
+
+STATIC_ASSERT (sizeof (http_req_handle_t) == sizeof (u32), "must fit in u32");
+
+always_inline http_version_t
+http_session_get_version (session_t *s)
+{
+  http_req_handle_t h;
+  h.as_u32 = s->connection_index;
+  return (http_version_t) h.version;
+}
+
+always_inline u8 *
+format_http_version (u8 *s, va_list *va)
+{
+  http_version_t v = va_arg (*va, http_version_t);
+  switch (v)
+    {
+    case HTTP_VERSION_1:
+      s = format (s, "HTTP/1.1");
+      break;
+    case HTTP_VERSION_2:
+      s = format (s, "HTTP/2");
+      break;
+    case HTTP_VERSION_3:
+      s = format (s, "HTTP/3");
+      break;
+    default:
+      s = format (s, "unknown");
+      break;
+    }
+  return s;
+}
+
+always_inline u8 *
+format_http_method (u8 *s, va_list *va)
+{
+  http_req_method_t method = va_arg (*va, http_req_method_t);
+  u8 *t = 0;
+
+  switch (method)
+    {
+#define _(s, str)                                                             \
+  case HTTP_REQ_##s:                                                          \
+    t = (u8 *) str;                                                           \
+    break;
+      foreach_http_method
+#undef _
+       default : return format (s, "unknown");
+    }
+  return format (s, "%s", t);
+}
 always_inline u8 *
 format_http_bytes (u8 *s, va_list *va)
 {
index 07821de..fe593a0 100644 (file)
@@ -73,7 +73,8 @@ http2_frame_read_settings (http2_conn_settings_t *settings, u8 *payload,
       entry = (http2_settings_entry_t *) payload;
       switch (clib_net_to_host_u16 (entry->identifier))
        {
-#define _(v, label, member, min, max, default_value, err_code)                \
+#define _(v, label, member, min, max, default_value, err_code, server,        \
+         client)                                                             \
   case HTTP2_SETTINGS_##label:                                                \
     value = clib_net_to_host_u32 (entry->value);                              \
     if (!(value >= min && value <= max))                                      \
index 8e9e6d9..9060a73 100644 (file)
@@ -30,7 +30,8 @@ typedef enum http2_stream_state_
 
 #define foreach_http2_req_flags                                               \
   _ (APP_CLOSED, "app-closed")                                                \
-  _ (NEED_WINDOW_UPDATE, "need-window-update")
+  _ (NEED_WINDOW_UPDATE, "need-window-update")                                \
+  _ (IS_PARENT, "is-parent")
 
 typedef enum http2_req_flags_bit_
 {
@@ -67,6 +68,7 @@ typedef struct http2_req_
 #define foreach_http2_conn_flags                                              \
   _ (EXPECT_PREFACE, "expect-preface")                                        \
   _ (EXPECT_CONTINUATION, "expect-continuation")                              \
+  _ (EXPECT_SERVER_SETTINGS, "expect-server-settings")                        \
   _ (PREFACE_VERIFIED, "preface-verified")                                    \
   _ (TS_DESCHED, "ts-descheduled")
 
@@ -206,7 +208,7 @@ http2_conn_ctx_free (http_conn_t *hc)
 }
 
 static inline http2_req_t *
-http2_conn_alloc_req (http_conn_t *hc, u32 stream_id)
+http2_conn_alloc_req (http_conn_t *hc)
 {
   http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
   http2_conn_ctx_t *h2c;
@@ -224,19 +226,29 @@ http2_conn_alloc_req (http_conn_t *hc, u32 stream_id)
   req->base.hr_hc_index = hc->hc_hc_index;
   req->base.c_thread_index = hc->c_thread_index;
   req->base.c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP;
-  req->stream_id = stream_id;
   req->stream_state = HTTP2_STREAM_STATE_IDLE;
   req->sched_list.next = CLIB_LLIST_INVALID_INDEX;
   req->sched_list.prev = CLIB_LLIST_INVALID_INDEX;
   h2c = http2_conn_ctx_get_w_thread (hc);
-  HTTP_DBG (1, "h2c [%u]%x req_index %x stream_id %u", hc->c_thread_index,
-           h2c - wrk->conn_pool, req_index, stream_id);
+  HTTP_DBG (1, "h2c [%u]%x req_index %x", hc->c_thread_index,
+           h2c - wrk->conn_pool, req_index);
   req->peer_window = h2c->peer_settings.initial_window_size;
   req->our_window = h2c->settings.initial_window_size;
-  hash_set (h2c->req_by_stream_id, stream_id, req_index);
   return req;
 }
 
+static_always_inline void
+http2_req_set_stream_id (http2_req_t *req, http2_conn_ctx_t *h2c,
+                        u32 stream_id)
+{
+  HTTP_DBG (1, "req_index [%u]%x stream_id %u", req->base.c_thread_index,
+           ((http_req_handle_t) req->base.hr_req_handle).req_index,
+           stream_id);
+  req->stream_id = stream_id;
+  hash_set (h2c->req_by_stream_id, stream_id,
+           ((http_req_handle_t) req->base.hr_req_handle).req_index);
+}
+
 static inline void
 http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req,
                     clib_thread_index_t thread_index)
@@ -252,12 +264,30 @@ http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req,
   vec_free (req->base.headers);
   vec_free (req->base.target);
   http_buffer_free (&req->base.tx_buf);
-  hash_unset (h2c->req_by_stream_id, req->stream_id);
+  if (req->stream_id)
+    hash_unset (h2c->req_by_stream_id, req->stream_id);
   if (CLIB_DEBUG)
     memset (req, 0xba, sizeof (*req));
   pool_put (wrk->req_pool, req);
 }
 
+static inline void
+http2_conn_reset_req (http2_conn_ctx_t *h2c, http2_req_t *req,
+                     clib_thread_index_t thread_index)
+{
+  http2_worker_ctx_t *wrk = http2_get_worker (thread_index);
+
+  if (clib_llist_elt_is_linked (req, sched_list))
+    clib_llist_remove (wrk->req_pool, sched_list, req);
+  http_buffer_free (&req->base.tx_buf);
+  if (req->stream_id)
+    hash_unset (h2c->req_by_stream_id, req->stream_id);
+  req->flags = 0;
+  req->stream_state = HTTP2_STREAM_STATE_IDLE;
+  req->peer_window = h2c->peer_settings.initial_window_size;
+  req->our_window = h2c->settings.initial_window_size;
+}
+
 http2_req_t *
 http2_conn_get_req (http_conn_t *hc, u32 stream_id)
 {
@@ -287,6 +317,16 @@ http2_req_get (u32 req_index, clib_thread_index_t thread_index)
   return pool_elt_at_index (wrk->req_pool, req_index);
 }
 
+always_inline u32
+http2_conn_get_next_stream_id (http2_conn_ctx_t *h2c)
+{
+  if (h2c->last_opened_stream_id)
+    h2c->last_opened_stream_id += 2;
+  else
+    h2c->last_opened_stream_id = 1;
+  return h2c->last_opened_stream_id;
+}
+
 always_inline void
 http2_conn_schedule (http2_conn_ctx_t *h2c, clib_thread_index_t thread_index)
 {
@@ -451,8 +491,9 @@ http2_send_server_preface (http_conn_t *hc)
   http2_settings_entry_t *setting, *settings_list = 0;
   http2_conn_ctx_t *h2c = http2_conn_ctx_get_w_thread (hc);
 
-#define _(v, label, member, min, max, default_value, err_code)                \
-  if (h2c->settings.member != default_value)                                  \
+#define _(v, label, member, min, max, default_value, err_code, server,        \
+         client)                                                             \
+  if (h2c->settings.member != default_value && server)                        \
     {                                                                         \
       vec_add2 (settings_list, setting, 1);                                   \
       setting->identifier = HTTP2_SETTINGS_##label;                           \
@@ -471,6 +512,37 @@ http2_send_server_preface (http_conn_t *hc)
   vec_free (settings_list);
 }
 
+always_inline void
+http2_send_client_preface (http_conn_t *hc)
+{
+  u8 *response, *p;
+  http2_settings_entry_t *setting, *settings_list = 0;
+  http2_conn_ctx_t *h2c = http2_conn_ctx_get_w_thread (hc);
+
+  response = http_get_tx_buf (hc);
+  vec_add2 (response, p, http2_conn_preface.len);
+  clib_memcpy_fast (p, http2_conn_preface.base, http2_conn_preface.len);
+
+#define _(v, label, member, min, max, default_value, err_code, server,        \
+         client)                                                             \
+  if (h2c->settings.member != default_value && client)                        \
+    {                                                                         \
+      vec_add2 (settings_list, setting, 1);                                   \
+      setting->identifier = HTTP2_SETTINGS_##label;                           \
+      setting->value = h2c->settings.member;                                  \
+    }
+  foreach_http2_settings
+#undef _
+
+    http2_frame_write_settings (settings_list, &response);
+  /* send also connection window update */
+  http2_frame_write_window_update (h2c->our_window - HTTP2_INITIAL_WIN_SIZE, 0,
+                                  &response);
+  http_io_ts_write (hc, response, vec_len (response), 0);
+  http_io_ts_after_write (hc, 1);
+  vec_free (settings_list);
+}
+
 /***********************/
 /* stream TX scheduler */
 /***********************/
@@ -530,10 +602,15 @@ http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions)
       if (hc->flags & HTTP_CONN_F_IS_SERVER)
        http2_stream_close (req, hc);
       else
-       req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED;
+       {
+         req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED;
+         http_req_state_change (&req->base,
+                                HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+       }
     }
   else
     {
+      http_io_as_dequeue_notify (&req->base, n_written);
       if (req->peer_window == 0)
        {
          /* mark that we need window update on stream */
@@ -546,7 +623,6 @@ http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions)
          HTTP_DBG (1, "adding to data queue req_index %x",
                    ((http_req_handle_t) req->base.hr_req_handle).req_index);
          http2_req_schedule_data_tx (hc, req);
-         http_io_as_dequeue_notify (&req->base, n_written);
        }
     }
 
@@ -748,7 +824,6 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc,
       vec_free (h2c->unsent_headers);
       *next_ri = clib_llist_next_index (req, sched_list);
       clib_llist_remove (wrk->req_pool, sched_list, req);
-      flags |= HTTP2_FRAME_FLAG_END_HEADERS;
       if (http_buffer_bytes_left (&req->base.tx_buf))
        {
          /* start sending the actual data */
@@ -758,7 +833,16 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc,
          http2_req_schedule_data_tx (hc, req);
        }
       else
-       http2_stream_close (req, hc);
+       {
+         if (hc->flags & HTTP_CONN_F_IS_SERVER)
+           http2_stream_close (req, hc);
+         else
+           {
+             req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED;
+             http_req_state_change (&req->base,
+                                    HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+           }
+       }
     }
   else
     {
@@ -769,8 +853,9 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc,
 }
 
 static void
-http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc,
-                             u8 *n_emissions, clib_llist_index_t *next_ri)
+http2_sched_dispatch_resp_headers (http2_req_t *req, http_conn_t *hc,
+                                  u8 *n_emissions,
+                                  clib_llist_index_t *next_ri)
 {
   http_msg_t msg;
   u8 *response, *date, *app_headers = 0;
@@ -903,6 +988,122 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc,
   http_io_ts_after_write (hc, 0);
 }
 
+static void
+http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc,
+                                 u8 *n_emissions, clib_llist_index_t *next_ri)
+{
+  http_msg_t msg;
+  u8 *request, *app_headers = 0;
+  u8 fh[HTTP2_FRAME_HEADER_SIZE];
+  hpack_request_control_data_t control_data;
+  u8 flags = 0;
+  u32 n_written, stream_id, n_deq, max_write, headers_len, headers_left;
+  http2_conn_ctx_t *h2c;
+  http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
+
+  req->stream_state = HTTP2_STREAM_STATE_OPEN;
+
+  http_get_app_msg (&req->base, &msg);
+  ASSERT (msg.type == HTTP_MSG_REQUEST);
+  n_deq = sizeof (msg);
+  *n_emissions += msg.data.type == HTTP_MSG_DATA_PTR ?
+                   HTTP2_SCHED_WEIGHT_HEADERS_PTR :
+                   HTTP2_SCHED_WEIGHT_HEADERS_INLINE;
+
+  request = http_get_tx_buf (hc);
+
+  control_data.method = msg.method_type;
+  control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED;
+  control_data.scheme = HTTP_URL_SCHEME_HTTPS;
+  control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED;
+  control_data.path = http_get_app_target (&req->base, &msg);
+  control_data.path_len = msg.data.target_path_len;
+  control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED;
+  control_data.authority = hc->host;
+  control_data.authority_len = vec_len (hc->host);
+  control_data.user_agent = hc->app_name;
+  control_data.user_agent_len = vec_len (hc->app_name);
+
+  HTTP_DBG (1, "%U %U", format_http_method, control_data.method,
+           format_http_bytes, control_data.path, control_data.path_len);
+  if (msg.data.body_len)
+    {
+      control_data.content_len = msg.data.body_len;
+      http_req_tx_buffer_init (&req->base, &msg);
+    }
+  else
+    {
+      control_data.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN;
+      flags |= HTTP2_FRAME_FLAG_END_STREAM;
+    }
+
+  if (msg.data.headers_len)
+    {
+      n_deq += msg.data.type == HTTP_MSG_DATA_PTR ? sizeof (uword) :
+                                                   msg.data.headers_len;
+      app_headers = http_get_app_header_list (&req->base, &msg);
+    }
+
+  hpack_serialize_request (app_headers, msg.data.headers_len, &control_data,
+                          &request);
+  headers_len = vec_len (request);
+
+  h2c = http2_conn_ctx_get_w_thread (hc);
+
+  max_write = http_io_ts_max_write (hc, 0);
+  max_write -= HTTP2_FRAME_HEADER_SIZE;
+  max_write = clib_min (max_write, h2c->peer_settings.max_frame_size);
+
+  stream_id = http2_conn_get_next_stream_id (h2c);
+  http2_req_set_stream_id (req, h2c, stream_id);
+
+  http_io_as_dequeue_notify (&req->base, n_deq);
+
+  if (headers_len <= max_write)
+    {
+      *next_ri = clib_llist_next_index (req, sched_list);
+      clib_llist_remove (wrk->req_pool, sched_list, req);
+      flags |= HTTP2_FRAME_FLAG_END_HEADERS;
+      if (msg.data.body_len)
+       {
+         /* start sending the actual data */
+         req->dispatch_data_cb = http2_sched_dispatch_data;
+         HTTP_DBG (1, "adding to data queue req_index %x",
+                   ((http_req_handle_t) req->base.hr_req_handle).req_index);
+         http2_req_schedule_data_tx (hc, req);
+       }
+      else
+       {
+         req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED;
+         http_req_state_change (&req->base,
+                                HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+       }
+    }
+  else
+    {
+      /* we need to send CONTINUATION frame as next */
+      HTTP_DBG (1, "response headers need to be fragmented");
+      *next_ri = clib_llist_entry_index (wrk->req_pool, req);
+      headers_len = max_write;
+      headers_left = vec_len (request) - headers_len;
+      req->dispatch_headers_cb = http2_sched_dispatch_continuation;
+      /* move unsend portion of headers to connection ctx */
+      ASSERT (h2c->unsent_headers == 0);
+      vec_validate (h2c->unsent_headers, headers_left - 1);
+      clib_memcpy_fast (h2c->unsent_headers, request + headers_len,
+                       headers_left);
+      h2c->unsent_headers_offset = 0;
+      *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION;
+    }
+
+  http2_frame_write_headers_header (headers_len, stream_id, flags, fh);
+  svm_fifo_seg_t segs[2] = { { fh, HTTP2_FRAME_HEADER_SIZE },
+                            { request, headers_len } };
+  n_written = http_io_ts_write_segs (hc, segs, 2, 0);
+  ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len));
+  http_io_ts_after_write (hc, 0);
+}
+
 static void
 http2_update_time_callback (f64 now, u8 thread_index)
 {
@@ -992,6 +1193,101 @@ http2_update_time_callback (f64 now, u8 thread_index)
 /* request state machine handlers RX */
 /*************************************/
 
+static http_sm_result_t
+http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req,
+                                     transport_send_params_t *sp,
+                                     http2_error_t *error)
+{
+  http2_conn_ctx_t *h2c;
+  hpack_response_control_data_t control_data;
+  http_msg_t msg;
+  int rv;
+  http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
+
+  h2c = http2_conn_ctx_get_w_thread (hc);
+
+  vec_reset_length (req->base.headers);
+  *error =
+    hpack_parse_response (req->payload, req->payload_len, wrk->header_list,
+                         vec_len (wrk->header_list), &control_data,
+                         &req->base.headers, &h2c->decoder_dynamic_table);
+  if (*error != HTTP2_ERROR_NO_ERROR)
+    {
+      HTTP_DBG (1, "hpack_parse_response failed");
+      return HTTP_SM_ERROR;
+    }
+
+  HTTP_DBG (1, "decompressed headers size %u", control_data.headers_len);
+  HTTP_DBG (1, "dynamic table size %u", h2c->decoder_dynamic_table.used);
+
+  req->base.control_data_len = control_data.control_data_len;
+  req->base.headers_offset = control_data.headers - wrk->header_list;
+  req->base.headers_len = control_data.headers_len;
+  req->base.status_code = control_data.sc;
+
+  if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED))
+    {
+      HTTP_DBG (1, ":status pseudo-header missing in request");
+      http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp);
+      return HTTP_SM_STOP;
+    }
+
+  if (control_data.content_len_header_index != ~0)
+    {
+      req->base.content_len_header_index =
+       control_data.content_len_header_index;
+      rv = http_parse_content_length (&req->base, wrk->header_list);
+      if (rv)
+       {
+         http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp);
+         return HTTP_SM_STOP;
+       }
+    }
+  /* TODO: message framing without content length using END_STREAM flag */
+  if (req->base.body_len == 0 &&
+      req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED)
+    {
+      HTTP_DBG (1, "no content-length and DATA frame expected");
+      *error = HTTP2_ERROR_INTERNAL_ERROR;
+      return HTTP_SM_ERROR;
+    }
+  req->base.to_recv = req->base.body_len;
+
+  msg.type = HTTP_MSG_REPLY;
+  msg.code = req->base.status_code;
+  msg.data.headers_offset = req->base.headers_offset;
+  msg.data.headers_len = req->base.headers_len;
+  msg.data.headers_ctx = pointer_to_uword (req->base.headers);
+  msg.data.body_offset = req->base.control_data_len;
+  msg.data.body_len = req->base.body_len;
+  msg.data.type = HTTP_MSG_DATA_INLINE;
+
+  svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
+                            { wrk->header_list,
+                              req->base.control_data_len } };
+  HTTP_DBG (3, "%U", format_http_bytes, wrk->header_list,
+           req->base.control_data_len);
+  http_io_as_write_segs (&req->base, segs, 2);
+
+  if (req->base.body_len)
+    {
+      http_req_state_change (&req->base,
+                            HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA);
+      http_io_as_add_want_read_ntf (&req->base);
+    }
+  else
+    {
+      /* we are done wait for the next app request */
+      http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD);
+      transport_connection_reschedule (&req->base.connection);
+      http2_conn_reset_req (h2c, req, hc->c_thread_index);
+    }
+
+  http_app_worker_rx_notify (&req->base);
+
+  return HTTP_SM_STOP;
+}
+
 static http_sm_result_t
 http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req,
                                       transport_send_params_t *sp,
@@ -1222,6 +1518,8 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req,
                                        http2_error_t *error)
 {
   u32 max_enq;
+  http2_stream_state_t expected_state;
+  http2_conn_ctx_t *h2c;
 
   if (req->payload_len > req->base.to_recv)
     {
@@ -1230,8 +1528,10 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req,
       return HTTP_SM_STOP;
     }
   req->base.to_recv -= req->payload_len;
-  if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED &&
-      req->base.to_recv != 0)
+  expected_state = hc->flags & HTTP_CONN_F_IS_SERVER ?
+                    HTTP2_STREAM_STATE_HALF_CLOSED :
+                    HTTP2_STREAM_STATE_CLOSED;
+  if (req->stream_state == expected_state && req->base.to_recv != 0)
     {
       HTTP_DBG (1, "peer closed stream but don't send all data");
       http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp);
@@ -1245,7 +1545,23 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req,
       return HTTP_SM_STOP;
     }
   if (req->base.to_recv == 0)
-    http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_REPLY);
+    {
+      if (hc->flags & HTTP_CONN_F_IS_SERVER)
+       {
+         http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_REPLY);
+       }
+      else
+       {
+         /* we are done wait for the next app request */
+         http_req_state_change (&req->base,
+                                hc->flags & HTTP_CONN_F_IS_SERVER ?
+                                  HTTP_REQ_STATE_WAIT_APP_REPLY :
+                                  HTTP_REQ_STATE_WAIT_APP_METHOD);
+         transport_connection_reschedule (&req->base.connection);
+         h2c = http2_conn_ctx_get_w_thread (hc);
+         http2_conn_reset_req (h2c, req, hc->c_thread_index);
+       }
+    }
   http_io_as_write (&req->base, req->payload, req->payload_len);
   http_app_worker_rx_notify (&req->base);
 
@@ -1348,6 +1664,7 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req,
 
   return HTTP_SM_STOP;
 }
+
 /*************************************/
 /* request state machine handlers TX */
 /*************************************/
@@ -1399,6 +1716,40 @@ http2_req_state_app_io_more_data (http_conn_t *hc, http2_req_t *req,
   return HTTP_SM_STOP;
 }
 
+static http_sm_result_t
+http2_req_state_wait_app_method (http_conn_t *hc, http2_req_t *req,
+                                transport_send_params_t *sp,
+                                http2_error_t *error)
+{
+  http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
+  http2_req_t *he;
+  http2_conn_ctx_t *h2c;
+
+  ASSERT (!clib_llist_elt_is_linked (req, sched_list));
+
+  h2c = http2_conn_ctx_get_w_thread (hc);
+
+  if (!(hc->flags & HTTP_CONN_F_HAS_REQUEST))
+    {
+      hc->flags |= HTTP_CONN_F_HAS_REQUEST;
+      hpack_dynamic_table_init (&h2c->decoder_dynamic_table,
+                               http2_default_conn_settings.header_table_size);
+    }
+
+  /* add response to stream scheduler */
+  HTTP_DBG (1, "adding to headers queue req_index %x",
+           ((http_req_handle_t) req->base.hr_req_handle).req_index);
+  he = clib_llist_elt (wrk->req_pool, h2c->new_tx_streams);
+  clib_llist_add_tail (wrk->req_pool, sched_list, req, he);
+  http2_conn_schedule (h2c, hc->c_thread_index);
+
+  req->dispatch_headers_cb = http2_sched_dispatch_req_headers;
+  http_req_state_change (&req->base, HTTP_REQ_STATE_APP_IO_MORE_DATA);
+  http_req_deschedule (&req->base, sp);
+
+  return HTTP_SM_STOP;
+}
+
 static http_sm_result_t
 http2_req_state_tunnel_tx (http_conn_t *hc, http2_req_t *req,
                           transport_send_params_t *sp, http2_error_t *error)
@@ -1433,7 +1784,7 @@ typedef http_sm_result_t (*http2_sm_handler) (http_conn_t *hc,
 
 static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = {
   0, /* idle */
-  0, /* wait app method */
+  http2_req_state_wait_app_method,
   0, /* wait transport reply */
   0, /* transport io more data */
   0, /* wait transport method */
@@ -1447,7 +1798,7 @@ static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = {
 static http2_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = {
   0, /* idle */
   0, /* wait app method */
-  0, /* wait transport reply */
+  http2_req_state_wait_transport_reply,
   http2_req_state_transport_io_more_data,
   http2_req_state_wait_transport_method,
   0, /* wait app reply */
@@ -1462,6 +1813,12 @@ http2_req_state_is_tx_valid (http2_req_t *req)
   return tx_state_funcs[req->base.state] ? 1 : 0;
 }
 
+static_always_inline int
+http2_req_state_is_rx_valid (http2_req_t *req)
+{
+  return rx_state_funcs[req->base.state] ? 1 : 0;
+}
+
 static_always_inline http2_error_t
 http2_req_run_state_machine (http_conn_t *hc, http2_req_t *req,
                             transport_send_params_t *sp, u8 is_tx)
@@ -1501,9 +1858,10 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh)
   http2_error_t rv;
   http2_conn_ctx_t *h2c;
 
+  h2c = http2_conn_ctx_get_w_thread (hc);
+
   if (hc->flags & HTTP_CONN_F_IS_SERVER)
     {
-      h2c = http2_conn_ctx_get_w_thread (hc);
       /* streams initiated by client must use odd-numbered stream id */
       if ((fh->stream_id & 1) == 0)
        {
@@ -1526,8 +1884,9 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh)
                                   HTTP2_ERROR_REFUSED_STREAM, 0);
          return HTTP2_ERROR_NO_ERROR;
        }
-      req = http2_conn_alloc_req (hc, fh->stream_id);
-      req->dispatch_headers_cb = http2_sched_dispatch_headers;
+      req = http2_conn_alloc_req (hc);
+      http2_req_set_stream_id (req, h2c, fh->stream_id);
+      req->dispatch_headers_cb = http2_sched_dispatch_resp_headers;
       http_conn_accept_request (hc, &req->base);
       http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD);
       req->stream_state = HTTP2_STREAM_STATE_OPEN;
@@ -1569,8 +1928,54 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh)
     }
   else
     {
-      /* TODO: client */
-      return HTTP2_ERROR_INTERNAL_ERROR;
+      req = http2_conn_get_req (hc, fh->stream_id);
+      if (!req)
+       return HTTP2_ERROR_PROTOCOL_ERROR;
+
+      if (!http2_req_state_is_rx_valid (req))
+       {
+         if (req->base.state == HTTP_REQ_STATE_APP_IO_MORE_DATA)
+           {
+             /* 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->base);
+             hc->state = HTTP_CONN_STATE_CLOSED;
+             http_req_state_change (&req->base,
+                                    HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY);
+           }
+         else
+           return HTTP2_ERROR_INTERNAL_ERROR;
+       }
+
+      if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM)
+       req->stream_state = HTTP2_STREAM_STATE_CLOSED;
+
+      if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS))
+       {
+         HTTP_DBG (1, "fragmented headers stream id %u", fh->stream_id);
+         h2c->flags |= HTTP2_CONN_F_EXPECT_CONTINUATION;
+         vec_validate (h2c->unparsed_headers, fh->length - 1);
+         http_io_ts_read (hc, h2c->unparsed_headers, fh->length, 0);
+         rv = http2_frame_read_headers (&headers_start, &headers_len,
+                                        h2c->unparsed_headers, fh->length,
+                                        fh->flags);
+         if (rv != HTTP2_ERROR_NO_ERROR)
+           return rv;
+
+         /* in case frame has padding */
+         if (PREDICT_FALSE (headers_start != h2c->unparsed_headers))
+           {
+             n_dec = fh->length - headers_len;
+             n_del = headers_start - h2c->unparsed_headers;
+             n_dec -= n_del;
+             vec_delete (h2c->unparsed_headers, n_del, 0);
+             vec_dec_len (h2c->unparsed_headers, n_dec);
+           }
+
+         return HTTP2_ERROR_NO_ERROR;
+       }
     }
 
   rx_buf = http_get_rx_buf (hc);
@@ -1594,42 +1999,34 @@ http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh)
   u8 *p;
   http2_error_t rv = HTTP2_ERROR_NO_ERROR;
 
-  if (hc->flags & HTTP_CONN_F_IS_SERVER)
-    {
-      h2c = http2_conn_ctx_get_w_thread (hc);
+  h2c = http2_conn_ctx_get_w_thread (hc);
 
-      if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION))
-       {
-         HTTP_DBG (1, "unexpected CONTINUATION frame");
-         return HTTP2_ERROR_PROTOCOL_ERROR;
-       }
+  if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION))
+    {
+      HTTP_DBG (1, "unexpected CONTINUATION frame");
+      return HTTP2_ERROR_PROTOCOL_ERROR;
+    }
 
-      if (fh->stream_id != h2c->last_opened_stream_id)
-       {
-         HTTP_DBG (1, "invalid stream id %u", fh->stream_id);
-         return HTTP2_ERROR_PROTOCOL_ERROR;
-       }
+  if (fh->stream_id != h2c->last_opened_stream_id)
+    {
+      HTTP_DBG (1, "invalid stream id %u", fh->stream_id);
+      return HTTP2_ERROR_PROTOCOL_ERROR;
+    }
 
-      vec_add2 (h2c->unparsed_headers, p, fh->length);
-      http_io_ts_read (hc, p, fh->length, 0);
+  vec_add2 (h2c->unparsed_headers, p, fh->length);
+  http_io_ts_read (hc, p, fh->length, 0);
 
-      if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)
-       {
-         req = http2_conn_get_req (hc, fh->stream_id);
-         if (!req)
-           return HTTP2_ERROR_PROTOCOL_ERROR;
-         h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION;
-         req->payload = h2c->unparsed_headers;
-         req->payload_len = vec_len (h2c->unparsed_headers);
-         HTTP_DBG (1, "run state machine");
-         rv = http2_req_run_state_machine (hc, req, 0, 0);
-         vec_free (h2c->unparsed_headers);
-       }
-    }
-  else
+  if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)
     {
-      /* TODO: client */
-      return HTTP2_ERROR_INTERNAL_ERROR;
+      req = http2_conn_get_req (hc, fh->stream_id);
+      if (!req)
+       return HTTP2_ERROR_PROTOCOL_ERROR;
+      h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION;
+      req->payload = h2c->unparsed_headers;
+      req->payload_len = vec_len (h2c->unparsed_headers);
+      HTTP_DBG (1, "run state machine");
+      rv = http2_req_run_state_machine (hc, req, 0, 0);
+      vec_free (h2c->unparsed_headers);
     }
 
   return rv;
@@ -1712,7 +2109,9 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh)
            }
        }
       else
-       req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED;
+       req->stream_state = hc->flags & HTTP_CONN_F_IS_SERVER ?
+                             HTTP2_STREAM_STATE_HALF_CLOSED :
+                             HTTP2_STREAM_STATE_CLOSED;
     }
 
   rx_buf = http_get_rx_buf (hc);
@@ -1820,8 +2219,12 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh)
   if (fh->stream_id != 0)
     return HTTP2_ERROR_PROTOCOL_ERROR;
 
+  h2c = http2_conn_ctx_get_w_thread (hc);
+
   if (fh->flags == HTTP2_FRAME_FLAG_ACK)
     {
+      if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS)
+       return HTTP2_ERROR_PROTOCOL_ERROR;
       if (fh->length != 0)
        return HTTP2_ERROR_FRAME_SIZE_ERROR;
       /* TODO: we can start using non-default settings */
@@ -1835,12 +2238,21 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh)
       vec_validate (rx_buf, fh->length - 1);
       http_io_ts_read (hc, rx_buf, fh->length, 0);
 
-      h2c = http2_conn_ctx_get_w_thread (hc);
       new_settings = h2c->peer_settings;
       rv = http2_frame_read_settings (&new_settings, rx_buf, fh->length);
       if (rv != HTTP2_ERROR_NO_ERROR)
        return rv;
 
+      if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS)
+       {
+         h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS;
+         /* client connection is now established */
+         req = http2_conn_alloc_req (hc);
+         http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD);
+         if (http_conn_established (hc, &req->base))
+           return HTTP2_ERROR_INTERNAL_ERROR;
+       }
+
       /* ACK peer settings */
       http2_frame_write_settings_ack (&resp);
       http_io_ts_write (hc, resp, vec_len (resp), 0);
@@ -2135,6 +2547,7 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index,
   http2_req_t *req;
   u8 *response;
   u32 increment;
+  http2_stream_state_t expected_state;
 
   req = http2_req_get (req_index, thread_index);
   if (!req)
@@ -2145,7 +2558,10 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index,
   HTTP_DBG (1, "received app read notification stream id %u", req->stream_id);
   /* send stream window update if app read data in rx fifo and we expect more
    * data (stream is still open) */
-  if (req->stream_state == HTTP2_STREAM_STATE_OPEN)
+  expected_state = hc->flags & HTTP_CONN_F_IS_SERVER ?
+                    HTTP2_STREAM_STATE_OPEN :
+                    HTTP2_STREAM_STATE_HALF_CLOSED;
+  if (req->stream_state == expected_state)
     {
       http_io_as_reset_has_read_ntf (&req->base);
       response = http_get_tx_buf (hc);
@@ -2176,6 +2592,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index,
     }
 
   if (req->stream_state == HTTP2_STREAM_STATE_CLOSED ||
+      req->stream_state == HTTP2_STREAM_STATE_IDLE ||
       hc->state == HTTP_CONN_STATE_CLOSED)
     {
       HTTP_DBG (1, "nothing more to send, confirm close");
@@ -2232,8 +2649,15 @@ http2_app_reset_callback (http_conn_t *hc, u32 req_index,
 static int
 http2_transport_connected_callback (http_conn_t *hc)
 {
-  /* TODO */
-  return -1;
+  http2_conn_ctx_t *h2c;
+
+  HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index);
+  h2c = http2_conn_ctx_alloc_w_thread (hc);
+  h2c->flags |= HTTP2_CONN_F_EXPECT_SERVER_SETTINGS;
+
+  http2_send_client_preface (hc);
+
+  return 0;
 }
 
 static void
@@ -2324,6 +2748,14 @@ http2_transport_rx_callback (http_conn_t *hc)
          return;
        }
 
+      if ((h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) &&
+         fh.type != HTTP2_FRAME_TYPE_SETTINGS)
+       {
+         HTTP_DBG (1, "expected SETTINGS frame (server preface)");
+         http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0);
+         return;
+       }
+
       switch (fh.type)
        {
        case HTTP2_FRAME_TYPE_HEADERS:
@@ -2530,7 +2962,8 @@ http2_update_settings (http_settings_t type, u32 value)
 
   switch (type)
     {
-#define _(v, label, member, min, max, default_value, err_code)                \
+#define _(v, label, member, min, max, default_value, err_code, server,        \
+         client)                                                             \
   case HTTP2_SETTINGS_##label:                                                \
     if (!(value >= min && value <= max))                                      \
       return -1;                                                              \
@@ -2611,6 +3044,7 @@ http2_init (vlib_main_t *vm)
   h2m->settings.max_concurrent_streams = 100; /* by default unlimited */
   h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */
   h2m->settings.enable_connect_protocol = 1;   /* enable extended connect */
+  h2m->settings.enable_push = 0;               /* by default enabled */
   http_register_engine (&http2_engine, HTTP_VERSION_2);
 
   return 0;
index b48c2a2..67277f1 100644 (file)
@@ -59,25 +59,26 @@ format_http2_error (u8 *s, va_list *va)
   _ (4, STATUS, "status")                                                     \
   _ (5, PROTOCOL, "protocol")
 
-/* value, label, member, min, max, default_value, err_code */
+/* value, label, member, min, max, default_value, err_code, server, client */
 #define foreach_http2_settings                                                \
   _ (1, HEADER_TABLE_SIZE, header_table_size, 0, CLIB_U32_MAX, 4096,          \
-     HTTP2_ERROR_NO_ERROR)                                                    \
-  _ (2, ENABLE_PUSH, enable_push, 0, 1, 1, HTTP2_ERROR_PROTOCOL_ERROR)        \
+     HTTP2_ERROR_NO_ERROR, 1, 1)                                              \
+  _ (2, ENABLE_PUSH, enable_push, 0, 1, 1, HTTP2_ERROR_PROTOCOL_ERROR, 0, 1)  \
   _ (3, MAX_CONCURRENT_STREAMS, max_concurrent_streams, 0, CLIB_U32_MAX,      \
-     CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR)                                      \
+     CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR, 1, 1)                                \
   _ (4, INITIAL_WINDOW_SIZE, initial_window_size, 0, 0x7FFFFFFF, 65535,       \
-     HTTP2_ERROR_FLOW_CONTROL_ERROR)                                          \
+     HTTP2_ERROR_FLOW_CONTROL_ERROR, 1, 1)                                    \
   _ (5, MAX_FRAME_SIZE, max_frame_size, 16384, 16777215, 16384,               \
-     HTTP2_ERROR_PROTOCOL_ERROR)                                              \
+     HTTP2_ERROR_PROTOCOL_ERROR, 1, 1)                                        \
   _ (6, MAX_HEADER_LIST_SIZE, max_header_list_size, 0, CLIB_U32_MAX,          \
-     CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR)                                      \
+     CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR, 1, 1)                                \
   _ (8, ENABLE_CONNECT_PROTOCOL, enable_connect_protocol, 0, 1, 0,            \
-     HTTP2_ERROR_NO_ERROR)
+     HTTP2_ERROR_NO_ERROR, 1, 0)
 
 typedef enum
 {
-#define _(value, label, member, min, max, default_value, err_code)            \
+#define _(value, label, member, min, max, default_value, err_code, server,    \
+         client)                                                             \
   HTTP2_SETTINGS_##label = value,
   foreach_http2_settings
 #undef _
@@ -85,13 +86,16 @@ typedef enum
 
 typedef struct
 {
-#define _(value, label, member, min, max, default_value, err_code) u32 member;
+#define _(value, label, member, min, max, default_value, err_code, server,    \
+         client)                                                             \
+  u32 member;
   foreach_http2_settings
 #undef _
 } http2_conn_settings_t;
 
 static const http2_conn_settings_t http2_default_conn_settings = {
-#define _(value, label, member, min, max, default_value, err_code)            \
+#define _(value, label, member, min, max, default_value, err_code, server,    \
+         client)                                                             \
   default_value,
   foreach_http2_settings
 #undef _
index 5ad940d..51fdfd6 100644 (file)
@@ -29,18 +29,6 @@ typedef union
 
 STATIC_ASSERT (sizeof (http_conn_handle_t) == sizeof (u32), "must fit in u32");
 
-typedef union
-{
-  struct
-  {
-    u32 version : 3;
-    u32 req_index : 29;
-  };
-  u32 as_u32;
-} http_req_handle_t;
-
-STATIC_ASSERT (sizeof (http_req_handle_t) == sizeof (u32), "must fit in u32");
-
 #define foreach_http_conn_state                                               \
   _ (LISTEN, "LISTEN")                                                        \
   _ (CONNECTING, "CONNECTING")                                                \
@@ -84,14 +72,6 @@ typedef enum http_target_form_
   HTTP_TARGET_ASTERISK_FORM
 } http_target_form_t;
 
-typedef enum http_version_
-{
-  HTTP_VERSION_1,
-  HTTP_VERSION_2,
-  HTTP_VERSION_3,
-  HTTP_VERSION_NA = 7,
-} http_version_t;
-
 typedef struct http_req_id_
 {
   session_handle_t app_session_handle;