http: return more than data from server app 65/41165/14
authorMatus Fabian <[email protected]>
Thu, 20 Jun 2024 15:08:26 +0000 (17:08 +0200)
committerFlorin Coras <[email protected]>
Tue, 23 Jul 2024 15:22:34 +0000 (15:22 +0000)
Server app could return headers in front of body/data buffer.
Offers apis for building and serialization of headers section.
HTTP layer now only add Date, Server and Content-Lengths headers,
rest is up to app. Well known header names are predefined.

Type: improvement

Change-Id: If778bdfc9acf6b0d11a48f0a745a3a56c96c2436
Signed-off-by: Matus Fabian <[email protected]>
15 files changed:
extras/hs-test/http_test.go
extras/hs-test/infra/utils.go
src/plugins/hs_apps/http_cli.c
src/plugins/hs_apps/http_tps.c
src/plugins/http/http.c
src/plugins/http/http.h
src/plugins/http/http_content_types.h [new file with mode: 0644]
src/plugins/http/http_header_names.h [new file with mode: 0644]
src/plugins/http/http_plugin.rst
src/plugins/http_static/builtinurl/json_urls.c
src/plugins/http_static/http_cache.c
src/plugins/http_static/http_static.h
src/plugins/http_static/static_server.c
src/plugins/mactime/builtins.c
src/plugins/prom/prom.c

index a5694bf..e20efd6 100644 (file)
@@ -9,7 +9,6 @@ import (
        "time"
 
        . "fd.io/hs-test/infra"
-       . "github.com/onsi/ginkgo/v2"
 )
 
 func init() {
@@ -21,7 +20,7 @@ func init() {
                HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
                HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
                HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
-               HttpHeadersTest)
+               HttpHeadersTest, HttpStaticFileHandler)
        RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest)
 }
 
@@ -89,19 +88,81 @@ func HttpCliConnectErrorTest(s *VethsSuite) {
 }
 
 func HttpStaticPromTest(s *NoTopoSuite) {
-       finished := make(chan error, 1)
        query := "stats.prom"
        vpp := s.GetContainerByName("vpp").VppInstance
        serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
        s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
        s.Log(vpp.Vppctl("prom enable"))
        time.Sleep(time.Second * 5)
-       go func() {
-               defer GinkgoRecover()
-               s.StartWget(finished, serverAddress, "80", query, "")
-       }()
-       err := <-finished
-       s.AssertNil(err)
+       client := NewHttpClient()
+       req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil)
+       s.AssertNil(err, fmt.Sprint(err))
+       resp, err := client.Do(req)
+       s.AssertNil(err, fmt.Sprint(err))
+       defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, false))
+       s.AssertEqual(200, resp.StatusCode)
+       s.AssertContains(resp.Header.Get("Content-Type"), "text")
+       s.AssertContains(resp.Header.Get("Content-Type"), "plain")
+       s.AssertNotEqual(int64(0), resp.ContentLength)
+       _, err = io.ReadAll(resp.Body)
+}
+
+func HttpStaticFileHandler(s *NoTopoSuite) {
+       content := "<http><body><p>Hello</p></body></http>"
+       content2 := "<http><body><p>Page</p></body></http>"
+       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)
+       serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+       s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m"))
+
+       client := NewHttpClient()
+       req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil)
+       s.AssertNil(err, fmt.Sprint(err))
+       resp, err := client.Do(req)
+       s.AssertNil(err, fmt.Sprint(err))
+       defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
+       s.AssertEqual(200, resp.StatusCode)
+       s.AssertContains(resp.Header.Get("Content-Type"), "html")
+       s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+       s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+       body, err := io.ReadAll(resp.Body)
+       s.AssertEqual(string(body), content)
+       o := vpp.Vppctl("show http static server cache verbose")
+       s.Log(o)
+       s.AssertContains(o, "index.html")
+       s.AssertNotContains(o, "page.html")
+
+       resp, err = client.Do(req)
+       s.AssertNil(err, fmt.Sprint(err))
+       defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
+       s.AssertEqual(200, resp.StatusCode)
+       s.AssertContains(resp.Header.Get("Content-Type"), "html")
+       s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+       s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+       body, err = io.ReadAll(resp.Body)
+       s.AssertEqual(string(body), content)
+
+       req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil)
+       s.AssertNil(err, fmt.Sprint(err))
+       resp, err = client.Do(req)
+       s.AssertNil(err, fmt.Sprint(err))
+       defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
+       s.AssertEqual(200, resp.StatusCode)
+       s.AssertContains(resp.Header.Get("Content-Type"), "html")
+       s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+       s.AssertEqual(int64(len([]rune(content2))), resp.ContentLength)
+       body, err = io.ReadAll(resp.Body)
+       s.AssertEqual(string(body), content2)
+       o = vpp.Vppctl("show http static server cache verbose")
+       s.Log(o)
+       s.AssertContains(o, "index.html")
+       s.AssertContains(o, "page.html")
 }
 
 func HttpStaticPathTraversalTest(s *NoTopoSuite) {
@@ -118,7 +179,11 @@ func HttpStaticPathTraversalTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(404, resp.StatusCode)
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEmpty(resp.Header.Get("Cache-Control"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpStaticMovedTest(s *NoTopoSuite) {
@@ -134,8 +199,12 @@ func HttpStaticMovedTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(301, resp.StatusCode)
-       s.AssertNotEqual("", resp.Header.Get("Location"))
+       s.AssertEqual("http://"+serverAddress+"/tmp.aaa/index.html", resp.Header.Get("Location"))
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEmpty(resp.Header.Get("Cache-Control"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpStaticNotFoundTest(s *NoTopoSuite) {
@@ -150,7 +219,11 @@ func HttpStaticNotFoundTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(404, resp.StatusCode)
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEmpty(resp.Header.Get("Cache-Control"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
@@ -164,9 +237,11 @@ func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(405, resp.StatusCode)
-       // TODO: need to be fixed in http code
-       //s.AssertNotEqual("", resp.Header.Get("Allow"))
+       s.AssertNotEqual("", resp.Header.Get("Allow"), "server MUST generate an Allow header")
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpCliBadRequestTest(s *NoTopoSuite) {
@@ -180,7 +255,10 @@ func HttpCliBadRequestTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(400, resp.StatusCode)
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
@@ -194,6 +272,7 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
@@ -203,6 +282,7 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
        s.AssertNotContains(string(data), "build_by")
        s.AssertNotContains(string(data), "build_host")
        s.AssertNotContains(string(data), "build_dir")
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
@@ -216,6 +296,7 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
@@ -225,6 +306,7 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
        s.AssertContains(string(data), "build_by")
        s.AssertContains(string(data), "build_host")
        s.AssertContains(string(data), "build_dir")
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
@@ -238,11 +320,13 @@ func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
        s.AssertContains(string(data), "interface_list")
        s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
@@ -256,12 +340,14 @@ func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
        s.AssertContains(string(data), "interface_stats")
        s.AssertContains(string(data), "local0")
        s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func validatePostInterfaceStats(s *NoTopoSuite, data string) {
@@ -284,10 +370,12 @@ func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
        validatePostInterfaceStats(s, string(data))
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func HttpStaticMacTimeTest(s *NoTopoSuite) {
@@ -302,12 +390,14 @@ func HttpStaticMacTimeTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
        s.AssertContains(string(data), "mactime")
        s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Ip4AddressString())
        s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
+       s.AssertContains(resp.Header.Get("Content-Type"), "json")
 }
 
 func HttpInvalidRequestLineTest(s *NoTopoSuite) {
@@ -444,7 +534,10 @@ func HttpMethodNotImplementedTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(501, resp.StatusCode)
+       s.AssertEmpty(resp.Header.Get("Content-Type"))
+       s.AssertEqual(int64(0), resp.ContentLength)
 }
 
 func HttpVersionNotSupportedTest(s *NoTopoSuite) {
@@ -468,12 +561,13 @@ func HttpUriDecodeTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
        s.AssertEqual(200, resp.StatusCode)
        data, err := io.ReadAll(resp.Body)
        s.AssertNil(err, fmt.Sprint(err))
-       s.Log(string(data))
        s.AssertNotContains(string(data), "unknown input")
        s.AssertContains(string(data), "Compiler")
+       s.AssertContains(resp.Header.Get("Content-Type"), "html")
 }
 
 func HttpHeadersTest(s *NoTopoSuite) {
@@ -539,5 +633,8 @@ func HeaderServerTest(s *NoTopoSuite) {
        resp, err := client.Do(req)
        s.AssertNil(err, fmt.Sprint(err))
        defer resp.Body.Close()
+       s.Log(DumpHttpResp(resp, true))
+       s.AssertEqual(200, resp.StatusCode)
        s.AssertEqual("http_cli_server", resp.Header.Get("Server"))
+       s.AssertContains(resp.Header.Get("Content-Type"), "html")
 }
index 9619efb..05b7b36 100644 (file)
@@ -5,6 +5,7 @@ import (
        "io"
        "net"
        "net/http"
+       "net/http/httputil"
        "os"
        "strings"
        "time"
@@ -96,6 +97,14 @@ func NewHttpClient() *http.Client {
        return client
 }
 
+func DumpHttpResp(resp *http.Response, body bool) string {
+       dump, err := httputil.DumpResponse(resp, body)
+       if err != nil {
+               return ""
+       }
+       return string(dump)
+}
+
 func TcpSendReceive(address, data string) (string, error) {
        conn, err := net.DialTimeout("tcp", address, time.Second*30)
        if err != nil {
index 4970da7..dfe50c5 100644 (file)
@@ -17,6 +17,8 @@
 #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>
 
 #define HCS_DEBUG 0
 
@@ -43,6 +45,7 @@ typedef struct
   u8 *tx_buf;
   u32 tx_offset;
   u32 vpp_session_index;
+  http_header_t *resp_headers;
 } hcs_session_t;
 
 typedef struct
@@ -148,24 +151,45 @@ hcs_cli_output (uword arg, u8 *buffer, uword buffer_bytes)
 }
 
 static void
-start_send_data (hcs_session_t *hs, http_status_code_t status,
-                http_content_type_t type)
+start_send_data (hcs_session_t *hs, http_status_code_t status)
 {
   http_msg_t msg;
   session_t *ts;
+  u8 *headers_buf = 0;
   int rv;
 
+  if (vec_len (hs->resp_headers))
+    {
+      headers_buf = http_serialize_headers (hs->resp_headers);
+      vec_free (hs->resp_headers);
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = vec_len (headers_buf);
+    }
+  else
+    {
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = 0;
+    }
+
   msg.type = HTTP_MSG_REPLY;
   msg.code = status;
-  msg.content_type = type;
   msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.len = vec_len (hs->tx_buf);
+  msg.data.body_len = vec_len (hs->tx_buf);
+  msg.data.body_offset = msg.data.headers_len;
+  msg.data.len = msg.data.body_len + msg.data.headers_len;
 
   ts = session_get (hs->vpp_session_index, hs->thread_index);
   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
   ASSERT (rv == sizeof (msg));
 
-  if (!msg.data.len)
+  if (msg.data.headers_len)
+    {
+      rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf);
+      ASSERT (rv == msg.data.headers_len);
+      vec_free (headers_buf);
+    }
+
+  if (!msg.data.body_len)
     goto done;
 
   rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (hs->tx_buf), hs->tx_buf);
@@ -203,7 +227,12 @@ send_data_to_http (void *rpc_args)
   hs->tx_buf = args->buf;
   if (args->plain_text)
     type = HTTP_CONTENT_TEXT_PLAIN;
-  start_send_data (hs, HTTP_STATUS_OK, type);
+
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                  http_content_type_token (type));
+
+  start_send_data (hs, HTTP_STATUS_OK);
 
 cleanup:
 
@@ -325,6 +354,7 @@ hcs_ts_rx_callback (session_t *ts)
 
   hs = hcs_session_get (ts->thread_index, ts->opaque);
   hs->tx_buf = 0;
+  hs->resp_headers = 0;
 
   /* Read the http message header */
   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
@@ -332,16 +362,17 @@ hcs_ts_rx_callback (session_t *ts)
 
   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
     {
-      start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED,
-                      HTTP_CONTENT_TEXT_HTML);
+      http_add_header (&hs->resp_headers,
+                      http_header_name_token (HTTP_HEADER_ALLOW),
+                      http_token_lit ("GET"));
+      start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
       goto done;
     }
 
   if (msg.data.target_path_len == 0 ||
       msg.data.target_form != HTTP_TARGET_ORIGIN_FORM)
     {
-      hs->tx_buf = 0;
-      start_send_data (hs, HTTP_STATUS_BAD_REQUEST, HTTP_CONTENT_TEXT_HTML);
+      start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
       goto done;
     }
 
@@ -353,7 +384,7 @@ hcs_ts_rx_callback (session_t *ts)
   HCS_DBG ("%v", args.buf);
   if (http_validate_abs_path_syntax (args.buf, &is_encoded))
     {
-      start_send_data (hs, HTTP_STATUS_BAD_REQUEST, HTTP_CONTENT_TEXT_HTML);
+      start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
       vec_free (args.buf);
       goto done;
     }
@@ -374,13 +405,13 @@ hcs_ts_rx_callback (session_t *ts)
       ASSERT (rv == msg.data.headers_len);
       if (http_parse_headers (headers, &ht))
        {
-         start_send_data (hs, HTTP_STATUS_BAD_REQUEST,
-                          HTTP_CONTENT_TEXT_HTML);
+         start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
          vec_free (args.buf);
          vec_free (headers);
          goto done;
        }
-      const char *accept_value = http_get_header (ht, HTTP_HEADER_ACCEPT);
+      const char *accept_value =
+       http_get_header (ht, http_header_name_str (HTTP_HEADER_ACCEPT));
       if (accept_value)
        {
          HCS_DBG ("client accept: %s", accept_value);
index 3a08650..9cc592c 100644 (file)
@@ -17,6 +17,8 @@
 #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>
 
 typedef struct
 {
@@ -34,6 +36,7 @@ typedef struct
     u32 close_rate;
   };
   u8 *uri;
+  http_header_t *resp_headers;
 } hts_session_t;
 
 typedef struct hts_listen_cfg_
@@ -223,19 +226,41 @@ hts_start_send_data (hts_session_t *hs, http_status_code_t status)
 {
   http_msg_t msg;
   session_t *ts;
+  u8 *headers_buf = 0;
   int rv;
 
+  if (vec_len (hs->resp_headers))
+    {
+      headers_buf = http_serialize_headers (hs->resp_headers);
+      vec_free (hs->resp_headers);
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = vec_len (headers_buf);
+    }
+  else
+    {
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = 0;
+    }
+
   msg.type = HTTP_MSG_REPLY;
   msg.code = status;
-  msg.content_type = HTTP_CONTENT_APP_OCTET_STREAM;
   msg.data.type = HTTP_MSG_DATA_INLINE;
-  msg.data.len = hs->data_len;
+  msg.data.body_len = hs->data_len;
+  msg.data.body_offset = msg.data.headers_len;
+  msg.data.len = msg.data.body_len + msg.data.headers_len;
 
   ts = session_get (hs->vpp_session_index, hs->thread_index);
   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
   ASSERT (rv == sizeof (msg));
 
-  if (!msg.data.len)
+  if (msg.data.headers_len)
+    {
+      rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf);
+      ASSERT (rv == msg.data.headers_len);
+      vec_free (headers_buf);
+    }
+
+  if (!msg.data.body_len)
     {
       if (svm_fifo_set_event (ts->tx_fifo))
        session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
@@ -286,6 +311,10 @@ try_test_file (hts_session_t *hs, u8 *target)
        }
     }
 
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                  http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM));
+
   hts_start_send_data (hs, HTTP_STATUS_OK);
 
 done:
@@ -304,6 +333,8 @@ hts_ts_rx_callback (session_t *ts)
   int rv;
 
   hs = hts_session_get (ts->thread_index, ts->opaque);
+  hs->data_len = 0;
+  hs->resp_headers = 0;
 
   /* Read the http message header */
   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
@@ -311,6 +342,9 @@ hts_ts_rx_callback (session_t *ts)
 
   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
     {
+      http_add_header (&hs->resp_headers,
+                      http_header_name_token (HTTP_HEADER_ALLOW),
+                      http_token_lit ("GET"));
       hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
       goto done;
     }
index c5e2cc1..db10153 100644 (file)
@@ -36,12 +36,6 @@ const char *http_status_code_str[] = {
 #undef _
 };
 
-const char *http_content_type_str[] = {
-#define _(s, ext, str) str,
-  foreach_http_content_type
-#undef _
-};
-
 const http_buffer_type_t msg_to_buf_type[] = {
   [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO,
   [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR,
@@ -374,22 +368,17 @@ http_ts_reset_callback (session_t *ts)
  */
 static const char *http_error_template = "HTTP/1.1 %s\r\n"
                                         "Date: %U GMT\r\n"
-                                        "Content-Type: text/html\r\n"
                                         "Connection: close\r\n"
-                                        "Pragma: no-cache\r\n"
                                         "Content-Length: 0\r\n\r\n";
 
-static const char *http_redirect_template = "HTTP/1.1 %s\r\n";
-
 /**
  * http response boilerplate
  */
 static const char *http_response_template = "HTTP/1.1 %s\r\n"
                                            "Date: %U GMT\r\n"
-                                           "Expires: %U GMT\r\n"
                                            "Server: %v\r\n"
-                                           "Content-Type: %s\r\n"
-                                           "Content-Length: %lu\r\n\r\n";
+                                           "Content-Length: %u\r\n"
+                                           "%s";
 
 static const char *http_request_template = "GET %s HTTP/1.1\r\n"
                                           "User-Agent: %v\r\n"
@@ -1004,49 +993,54 @@ http_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp)
       goto error;
     }
 
-  http_buffer_init (&hc->tx_buf, msg_to_buf_type[msg.data.type], as->tx_fifo,
-                   msg.data.len);
+  if (msg.code >= HTTP_N_STATUS)
+    {
+      clib_warning ("unsupported status code: %d", msg.code);
+      return HTTP_SM_ERROR;
+    }
 
   /*
-   * Add headers. For now:
+   * Add "protocol layer" headers:
    * - current time
-   * - expiration time
    * - server name
-   * - content type
    * - data length
    */
   now = clib_timebase_now (&hm->timebase);
-
-  switch (msg.code)
-    {
-    case HTTP_STATUS_NOT_FOUND:
-    case HTTP_STATUS_METHOD_NOT_ALLOWED:
-    case HTTP_STATUS_BAD_REQUEST:
-    case HTTP_STATUS_INTERNAL_ERROR:
-    case HTTP_STATUS_FORBIDDEN:
-    case HTTP_STATUS_OK:
-      header =
-       format (0, http_response_template, http_status_code_str[msg.code],
-               /* Date */
-               format_clib_timebase_time, now,
-               /* Expires */
-               format_clib_timebase_time, now + 600.0,
-               /* Server */
-               hc->app_name,
-               /* Content type */
-               http_content_type_str[msg.content_type],
-               /* Length */
-               msg.data.len);
-      break;
-    case HTTP_STATUS_MOVED:
-      header =
-       format (0, http_redirect_template, http_status_code_str[msg.code]);
-      /* Location: http(s)://new-place already queued up as data */
-      break;
-    default:
-      clib_warning ("unsupported status code: %d", msg.code);
-      return HTTP_SM_ERROR;
+  header = format (0, http_response_template, http_status_code_str[msg.code],
+                  /* Date */
+                  format_clib_timebase_time, now,
+                  /* Server */
+                  hc->app_name,
+                  /* Length */
+                  msg.data.body_len,
+                  /* Any headers from app? */
+                  msg.data.headers_len ? "" : "\r\n");
+
+  /* Add headers from app (if any) */
+  if (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 (header, uword_to_pointer (app_headers_ptr, u8 *));
+       }
+      else
+       {
+         u32 orig_len = vec_len (header);
+         vec_resize (header, msg.data.headers_len);
+         u8 *p = header + orig_len;
+         rv = svm_fifo_dequeue (as->tx_fifo, msg.data.headers_len, p);
+         ASSERT (rv == msg.data.headers_len);
+       }
     }
+  HTTP_DBG (0, "%v", header);
+
+  http_buffer_init (&hc->tx_buf, msg_to_buf_type[msg.data.type], as->tx_fifo,
+                   msg.data.body_len);
 
   offset = http_send_data (hc, header, vec_len (header), 0);
   if (offset != vec_len (header))
index debdebc..07d5472 100644 (file)
@@ -51,6 +51,14 @@ typedef struct http_conn_id_
 STATIC_ASSERT (sizeof (http_conn_id_t) <= TRANSPORT_CONN_ID_LEN,
               "ctx id must be less than TRANSPORT_CONN_ID_LEN");
 
+typedef struct
+{
+  char *base;
+  uword len;
+} http_token_t;
+
+#define http_token_lit(s) (s), sizeof (s) - 1
+
 typedef enum http_conn_state_
 {
   HTTP_CONN_STATE_LISTEN,
@@ -235,50 +243,100 @@ typedef enum http_status_code_
     HTTP_N_STATUS
 } http_status_code_t;
 
-#define HTTP_HEADER_ACCEPT                   "Accept"
-#define HTTP_HEADER_ACCEPT_CHARSET           "Accept-Charset"
-#define HTTP_HEADER_ACCEPT_ENCODING          "Accept-Encoding"
-#define HTTP_HEADER_ACCEPT_LANGUAGE          "Accept-Language"
-#define HTTP_HEADER_ACCEPT_RANGES            "Accept-Ranges"
-#define HTTP_HEADER_ALLOW                    "Allow"
-#define HTTP_HEADER_AUTHENTICATION_INFO              "Authentication-Info"
-#define HTTP_HEADER_AUTHORIZATION            "Authorization"
-#define HTTP_HEADER_CLOSE                    "Close"
-#define HTTP_HEADER_CONNECTION               "Connection"
-#define HTTP_HEADER_CONTENT_ENCODING         "Content-Encoding"
-#define HTTP_HEADER_CONTENT_LANGUAGE         "Content-Language"
-#define HTTP_HEADER_CONTENT_LENGTH           "Content-Length"
-#define HTTP_HEADER_CONTENT_LOCATION         "Content-Location"
-#define HTTP_HEADER_CONTENT_RANGE            "Content-Range"
-#define HTTP_HEADER_CONTENT_TYPE             "Content-Type"
-#define HTTP_HEADER_DATE                     "Date"
-#define HTTP_HEADER_ETAG                     "ETag"
-#define HTTP_HEADER_EXPECT                   "Expect"
-#define HTTP_HEADER_FROM                     "From"
-#define HTTP_HEADER_HOST                     "Host"
-#define HTTP_HEADER_IF_MATCH                 "If-Match"
-#define HTTP_HEADER_IF_MODIFIED_SINCE        "If-Modified-Since"
-#define HTTP_HEADER_IF_NONE_MATCH            "If-None-Match"
-#define HTTP_HEADER_IF_RANGE                 "If-Range"
-#define HTTP_HEADER_IF_UNMODIFIED_SINCE              "If-Unmodified-Since"
-#define HTTP_HEADER_LAST_MODIFIED            "Last-Modified"
-#define HTTP_HEADER_LOCATION                 "Location"
-#define HTTP_HEADER_MAX_FORWARDS             "Max-Forwards"
-#define HTTP_HEADER_PROXY_AUTHENTICATE       "Proxy-Authenticate"
-#define HTTP_HEADER_PROXY_AUTHENTICATION_INFO "Proxy-Authentication-Info"
-#define HTTP_HEADER_PROXY_AUTHORIZATION              "Proxy-Authorization"
-#define HTTP_HEADER_RANGE                    "Range"
-#define HTTP_HEADER_REFERER                  "Referer"
-#define HTTP_HEADER_RETRY_AFTER                      "Retry-After"
-#define HTTP_HEADER_SERVER                   "Server"
-#define HTTP_HEADER_TE                       "TE"
-#define HTTP_HEADER_TRAILER                  "Trailer"
-#define HTTP_HEADER_TRANSFER_ENCODING        "Transfer-Encoding"
-#define HTTP_HEADER_UPGRADE                  "Upgrade"
-#define HTTP_HEADER_USER_AGENT               "User-Agent"
-#define HTTP_HEADER_VARY                     "Vary"
-#define HTTP_HEADER_VIA                              "Via"
-#define HTTP_HEADER_WWW_AUTHENTICATE         "WWW-Authenticate"
+#define foreach_http_header_name                                              \
+  _ (ACCEPT, "Accept")                                                        \
+  _ (ACCEPT_CHARSET, "Accept-Charset")                                        \
+  _ (ACCEPT_ENCODING, "Accept-Encoding")                                      \
+  _ (ACCEPT_LANGUAGE, "Accept-Language")                                      \
+  _ (ACCEPT_RANGES, "Accept-Ranges")                                          \
+  _ (ACCESS_CONTROL_ALLOW_CREDENTIALS, "Access-Control-Allow-Credentials")    \
+  _ (ACCESS_CONTROL_ALLOW_HEADERS, "Access-Control-Allow-Headers")            \
+  _ (ACCESS_CONTROL_ALLOW_METHODS, "Access-Control-Allow-Methods")            \
+  _ (ACCESS_CONTROL_ALLOW_ORIGIN, "Access-Control-Allow-Origin")              \
+  _ (ACCESS_CONTROL_EXPOSE_HEADERS, "Access-Control-Expose-Headers")          \
+  _ (ACCESS_CONTROL_MAX_AGE, "Access-Control-Max-Age")                        \
+  _ (ACCESS_CONTROL_REQUEST_HEADERS, "Access-Control-Request-Headers")        \
+  _ (ACCESS_CONTROL_REQUEST_METHOD, "Access-Control-Request-Method")          \
+  _ (AGE, "Age")                                                              \
+  _ (ALLOW, "Allow")                                                          \
+  _ (ALPN, "ALPN")                                                            \
+  _ (ALT_SVC, "Alt-Svc")                                                      \
+  _ (ALT_USED, "Alt-Used")                                                    \
+  _ (ALTERNATES, "Alternates")                                                \
+  _ (AUTHENTICATION_CONTROL, "Authentication-Control")                        \
+  _ (AUTHENTICATION_INFO, "Authentication-Info")                              \
+  _ (AUTHORIZATION, "Authorization")                                          \
+  _ (CACHE_CONTROL, "Cache-Control")                                          \
+  _ (CACHE_STATUS, "Cache-Status")                                            \
+  _ (CAPSULE_PROTOCOL, "Capsule-Protocol")                                    \
+  _ (CDN_CACHE_CONTROL, "CDN-Cache-Control")                                  \
+  _ (CDN_LOOP, "CDN-Loop")                                                    \
+  _ (CLIENT_CERT, "Client-Cert")                                              \
+  _ (CLIENT_CERT_CHAIN, "Client-Cert-Chain")                                  \
+  _ (CLOSE, "Close")                                                          \
+  _ (CONNECTION, "Connection")                                                \
+  _ (CONTENT_DIGEST, "Content-Digest")                                        \
+  _ (CONTENT_DISPOSITION, "Content-Disposition")                              \
+  _ (CONTENT_ENCODING, "Content-Encoding")                                    \
+  _ (CONTENT_LANGUAGE, "Content-Language")                                    \
+  _ (CONTENT_LENGTH, "Content-Length")                                        \
+  _ (CONTENT_LOCATION, "Content-Location")                                    \
+  _ (CONTENT_RANGE, "Content-Range")                                          \
+  _ (CONTENT_TYPE, "Content-Type")                                            \
+  _ (COOKIE, "Cookie")                                                        \
+  _ (DATE, "Date")                                                            \
+  _ (DIGEST, "Digest")                                                        \
+  _ (DPOP, "DPoP")                                                            \
+  _ (DPOP_NONCE, "DPoP-Nonce")                                                \
+  _ (EARLY_DATA, "Early-Data")                                                \
+  _ (ETAG, "ETag")                                                            \
+  _ (EXPECT, "Expect")                                                        \
+  _ (EXPIRES, "Expires")                                                      \
+  _ (FORWARDED, "Forwarded")                                                  \
+  _ (FROM, "From")                                                            \
+  _ (HOST, "Host")                                                            \
+  _ (IF_MATCH, "If-Match")                                                    \
+  _ (IF_MODIFIED_SINCE, "If-Modified-Since")                                  \
+  _ (IF_NONE_MATCH, "If-None-Match")                                          \
+  _ (IF_RANGE, "If-Range")                                                    \
+  _ (IF_UNMODIFIED_SINCE, "If-Unmodified-Since")                              \
+  _ (KEEP_ALIVE, "Keep-Alive")                                                \
+  _ (LAST_MODIFIED, "Last-Modified")                                          \
+  _ (LINK, "Link")                                                            \
+  _ (LOCATION, "Location")                                                    \
+  _ (MAX_FORWARDS, "Max-Forwards")                                            \
+  _ (ORIGIN, "Origin")                                                        \
+  _ (PRIORITY, "Priority")                                                    \
+  _ (PROXY_AUTHENTICATE, "Proxy-Authenticate")                                \
+  _ (PROXY_AUTHENTICATION_INFO, "Proxy-Authentication-Info")                  \
+  _ (PROXY_AUTHORIZATION, "Proxy-Authorization")                              \
+  _ (PROXY_STATUS, "Proxy-Status")                                            \
+  _ (RANGE, "Range")                                                          \
+  _ (REFERER, "Referer")                                                      \
+  _ (REPR_DIGEST, "Repr-Digest")                                              \
+  _ (SET_COOKIE, "Set-Cookie")                                                \
+  _ (SIGNATURE, "Signature")                                                  \
+  _ (SIGNATURE_INPUT, "Signature-Input")                                      \
+  _ (STRICT_TRANSPORT_SECURITY, "Strict-Transport-Security")                  \
+  _ (RETRY_AFTER, "Retry-After")                                              \
+  _ (SERVER, "Server")                                                        \
+  _ (TE, "TE")                                                                \
+  _ (TRAILER, "Trailer")                                                      \
+  _ (TRANSFER_ENCODING, "Transfer-Encoding")                                  \
+  _ (UPGRADE, "Upgrade")                                                      \
+  _ (USER_AGENT, "User-Agent")                                                \
+  _ (VARY, "Vary")                                                            \
+  _ (VIA, "Via")                                                              \
+  _ (WANT_CONTENT_DIGEST, "Want-Content-Digest")                              \
+  _ (WANT_REPR_DIGEST, "Want-Repr-Digest")                                    \
+  _ (WWW_AUTHENTICATE, "WWW-Authenticate")
+
+typedef enum http_header_name_
+{
+#define _(sym, str) HTTP_HEADER_##sym,
+  foreach_http_header_name
+#undef _
+} http_header_name_t;
 
 typedef enum http_msg_data_type_
 {
@@ -669,11 +727,17 @@ typedef struct
 {
   u8 *name;
   u8 *value;
+} http_header_ht_t;
+
+typedef struct
+{
+  http_token_t name;
+  http_token_t value;
 } http_header_t;
 
 typedef struct
 {
-  http_header_t *headers;
+  http_header_ht_t *headers;
   uword *value_by_name;
 } http_header_table_t;
 
@@ -685,7 +749,7 @@ typedef struct
 always_inline void
 http_free_header_table (http_header_table_t *ht)
 {
-  http_header_t *header;
+  http_header_ht_t *header;
   vec_foreach (header, ht->headers)
     {
       vec_free (header->name);
@@ -713,7 +777,7 @@ http_parse_headers (u8 *headers, http_header_table_t **header_table)
   u8 *pos, *end, *name_start, *value_start, *name;
   u32 name_len, value_len;
   int rv;
-  http_header_t *header;
+  http_header_ht_t *header;
   http_header_table_t *ht;
   uword *p;
 
@@ -779,7 +843,7 @@ always_inline const char *
 http_get_header (http_header_table_t *header_table, const char *name)
 {
   uword *p;
-  http_header_t *header;
+  http_header_ht_t *header;
 
   p = hash_get_mem (header_table->value_by_name, name);
   if (p)
@@ -791,6 +855,67 @@ http_get_header (http_header_table_t *header_table, const char *name)
   return 0;
 }
 
+/**
+ * Add header to the list.
+ *
+ * @param headers Header list.
+ * @param name Pointer to header's name buffer.
+ * @param name_len Length of the name.
+ * @param value Pointer to header's value buffer.
+ * @param value_len Length of the value.
+ *
+ * @note Headers added at protocol layer: Date, Server, Content-Length
+ */
+always_inline void
+http_add_header (http_header_t **headers, const char *name, uword name_len,
+                const char *value, uword value_len)
+{
+  http_header_t *header;
+  vec_add2 (*headers, header, 1);
+  header->name.base = (char *) name;
+  header->name.len = name_len;
+  header->value.base = (char *) value;
+  header->value.len = value_len;
+}
+
+/**
+ * Serialize the header list.
+ *
+ * @param headers Header list to serialize.
+ *
+ * @return New vector with serialized headers.
+ *
+ * The caller is always responsible to free the returned vector.
+ */
+always_inline u8 *
+http_serialize_headers (http_header_t *headers)
+{
+  u8 *headers_buf = 0, *dst;
+  u32 headers_buf_len = 2;
+  http_header_t *header;
+
+  vec_foreach (header, headers)
+    headers_buf_len += header->name.len + header->value.len + 4;
+
+  vec_validate (headers_buf, headers_buf_len - 1);
+  dst = headers_buf;
+
+  vec_foreach (header, headers)
+    {
+      clib_memcpy (dst, header->name.base, header->name.len);
+      dst += header->name.len;
+      *dst++ = ':';
+      *dst++ = ' ';
+      clib_memcpy (dst, header->value.base, header->value.len);
+      dst += header->value.len;
+      *dst++ = '\r';
+      *dst++ = '\n';
+    }
+  *dst++ = '\r';
+  *dst = '\n';
+  return headers_buf;
+}
+
 #endif /* SRC_PLUGINS_HTTP_HTTP_H_ */
 
 /*
diff --git a/src/plugins/http/http_content_types.h b/src/plugins/http/http_content_types.h
new file mode 100644 (file)
index 0000000..ddc0256
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP_CONTENT_TYPES_H_
+#define SRC_PLUGINS_HTTP_HTTP_CONTENT_TYPES_H_
+
+#include <http/http.h>
+
+static http_token_t http_content_types[] = {
+#define _(s, ext, str) { http_token_lit (str) },
+  foreach_http_content_type
+#undef _
+};
+
+#define http_content_type_token(e)                                            \
+  http_content_types[e].base, http_content_types[e].len
+
+#endif /* SRC_PLUGINS_HTTP_HTTP_CONTENT_TYPES_H_ */
diff --git a/src/plugins/http/http_header_names.h b/src/plugins/http/http_header_names.h
new file mode 100644 (file)
index 0000000..99acac7
--- /dev/null
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP_HEADER_NAMES_H_
+#define SRC_PLUGINS_HTTP_HTTP_HEADER_NAMES_H_
+
+#include <http/http.h>
+
+static http_token_t http_header_names[] = {
+#define _(sym, str) { http_token_lit (str) },
+  foreach_http_header_name
+#undef _
+};
+
+#define http_header_name_token(e)                                             \
+  http_header_names[e].base, http_header_names[e].len
+
+#define http_header_name_str(e) http_header_names[e].base
+
+#endif /* SRC_PLUGINS_HTTP_HTTP_HEADER_NAMES_H_ */
index c4c4d2c..2f7a58e 100644 (file)
@@ -121,6 +121,7 @@ Following example shows how to parse headers:
 
 .. code-block:: C
 
+  #include <http/http_header_names.h>
   if (msg.data.headers_len)
     {
       u8 *headers = 0;
@@ -134,7 +135,7 @@ Following example shows how to parse headers:
           /* your error handling */
         }
       /* get Accept header */
-      const char *accept_value = http_get_header (ht, HTTP_HEADER_ACCEPT);
+      const char *accept_value = http_get_header (ht, http_header_name_str (HTTP_HEADER_ACCEPT));
       if (accept_value)
         {
           /* do something interesting */
@@ -154,3 +155,97 @@ Finally application reads body:
       rv = svm_fifo_peek (ts->rx_fifo, msg.data.body_offset, msg.data.body_len, body);
       ASSERT (rv == msg.data.body_len);
     }
+
+Sending data
+""""""""""""""
+
+When server application sends response back to HTTP layer it starts with message metadata, followed by optional serialized headers and finally body (if any).
+
+Application should set following items:
+
+* Status code
+* target form
+* header section offset and length
+* body offset and length
+
+Application could pass headers back to HTTP layer. Header list is created dynamically as vector of ``http_header_t``,
+where we store only pointers to buffers (zero copy).
+Well known header names are predefined.
+The list is serialized just before you send buffer to HTTP layer.
+
+.. note::
+    Following headers are added at protocol layer and **MUST NOT** be set by application: Date, Server, Content-Length
+
+Following example shows how to create headers section:
+
+.. code-block:: C
+
+  #include <http/http.h>
+  #include <http/http_header_names.h>
+  #include <http/http_content_types.h>
+  http_header_t *resp_headers = 0;
+  u8 *headers_buf = 0;
+  http_add_header (resp_headers,
+                  http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                  http_content_type_token (HTTP_CONTENT_TEXT_HTML));
+  http_add_header (resp_headers,
+                  http_header_name_token (HTTP_HEADER_CACHE_CONTROL),
+                  http_token_lit ("max-age=600"));
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_LOCATION),
+                  (const char *) redirect, vec_len (redirect));
+  headers_buf = http_serialize_headers (hs->resp_headers);
+
+The example below show how to create and send response HTTP message metadata:
+
+.. code-block:: C
+
+  http_msg_t msg;
+  msg.type = HTTP_MSG_REPLY;
+  msg.code = HTTP_STATUS_MOVED
+  msg.data.headers_offset = 0;
+  msg.data.headers_len = vec_len (headers_buf);
+  msg.data.type = HTTP_MSG_DATA_INLINE;
+  msg.data.body_len = vec_len (tx_buf);
+  msg.data.body_offset = msg.data.headers_len;
+  msg.data.len = msg.data.body_len + msg.data.headers_len;
+  ts = session_get (hs->vpp_session_index, hs->thread_index);
+  rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
+  ASSERT (rv == sizeof (msg));
+
+Next you will send your serialized headers:
+
+.. code-block:: C
+
+  rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf);
+  ASSERT (rv == msg.data.headers_len);
+  vec_free (headers_buf);
+
+Finally application sends response body:
+
+.. code-block:: C
+
+  rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (tx_buf), tx_buf);
+  if (rv != vec_len (hs->tx_buf))
+    {
+      hs->tx_offset = rv;
+      svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
+    }
+  else
+    {
+      vec_free (tx_buf);
+    }
+  if (svm_fifo_set_event (ts->tx_fifo))
+    session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
+
+Example above shows how to send body data by copy, alternatively you could pass it as pointer:
+
+.. code-block:: C
+
+  msg.data.type = HTTP_MSG_DATA_PTR;
+  /* code omitted for brevity */
+  uword data = pointer_to_uword (tx_buf);
+  rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
+  ASSERT (rv == sizeof (data));
+
+In this case you need to free data when you receive next request or when session is closed.
index 8578be1..19c5245 100644 (file)
@@ -45,6 +45,7 @@ handle_get_version (hss_url_handler_args_t *args)
 
   args->data = s;
   args->data_len = vec_len (s);
+  args->ct = HTTP_CONTENT_APP_JSON;
   args->free_vec_data = 1;
   return HSS_URL_HANDLER_OK;
 }
@@ -117,6 +118,7 @@ handle_get_interface_stats (hss_url_handler_args_t *args)
 out:
   args->data = s;
   args->data_len = vec_len (s);
+  args->ct = HTTP_CONTENT_APP_JSON;
   args->free_vec_data = 1;
   vec_free (sw_if_indices);
   vec_free (stats);
@@ -157,6 +159,7 @@ handle_get_interface_list (hss_url_handler_args_t *args)
 
   args->data = s;
   args->data_len = vec_len (s);
+  args->ct = HTTP_CONTENT_APP_JSON;
   args->free_vec_data = 1;
   return HSS_URL_HANDLER_OK;
 }
index 8b9751b..7a069da 100644 (file)
@@ -421,19 +421,19 @@ format_hss_cache (u8 *s, va_list *args)
     {
       s = format (s, "cache size %lld bytes, limit %lld bytes, evictions %lld",
                  hc->cache_size, hc->cache_limit, hc->cache_evictions);
-      return 0;
+      return s;
     }
 
   vm = vlib_get_main ();
   now = vlib_time_now (vm);
 
-  s = format (s, "%U", format_hss_cache_entry, 0 /* header */, now);
+  s = format (s, "%U\n", format_hss_cache_entry, 0 /* header */, now);
 
   for (index = hc->first_index; index != ~0;)
     {
       ce = pool_elt_at_index (hc->cache_pool, index);
       index = ce->next_index;
-      s = format (s, "%U", format_hss_cache_entry, ce, now);
+      s = format (s, "%U\n", format_hss_cache_entry, ce, now);
     }
 
   s = format (s, "%40s%12lld", "Total Size", hc->cache_size);
index 8f336e8..4c22e1d 100644 (file)
@@ -50,8 +50,10 @@ typedef struct
   int free_data;
   /** File cache pool index */
   u32 cache_pool_index;
-  /** Content type, e.g. text, text/javascript, etc. */
-  http_content_type_t content_type;
+  /** Response header list */
+  http_header_t *resp_headers;
+  /** Serialized headers to send */
+  u8 *headers_buf;
 } hss_session_t;
 
 typedef struct hss_session_handle_
@@ -91,6 +93,7 @@ typedef struct hss_url_handler_args_
       uword data_len;
       u8 free_vec_data;
       http_status_code_t sc;
+      http_content_type_t ct;
     };
   };
 } hss_url_handler_args_t;
index 2622498..bc18875 100644 (file)
@@ -19,6 +19,9 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <http/http_header_names.h>
+#include <http/http_content_types.h>
+
 /** @file static_server.c
  *  Static http server, sufficient to serve .html / .css / .js content.
  */
@@ -83,34 +86,65 @@ start_send_data (hss_session_t *hs, http_status_code_t status)
 {
   http_msg_t msg;
   session_t *ts;
+  u8 *headers_buf = 0;
   int rv;
 
   ts = session_get (hs->vpp_session_index, hs->thread_index);
 
+  if (vec_len (hs->resp_headers))
+    {
+      headers_buf = http_serialize_headers (hs->resp_headers);
+      vec_free (hs->resp_headers);
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = vec_len (headers_buf);
+    }
+  else
+    {
+      msg.data.headers_offset = 0;
+      msg.data.headers_len = 0;
+    }
+
   msg.type = HTTP_MSG_REPLY;
   msg.code = status;
-  msg.content_type = hs->content_type;
-  msg.data.len = hs->data_len;
+  msg.data.body_len = hs->data_len;
+  msg.data.len = msg.data.body_len + msg.data.headers_len;
 
-  if (hs->data_len > hss_main.use_ptr_thresh)
+  if (msg.data.len > hss_main.use_ptr_thresh)
     {
       msg.data.type = HTTP_MSG_DATA_PTR;
       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
       ASSERT (rv == sizeof (msg));
 
+      if (msg.data.headers_len)
+       {
+         hs->headers_buf = headers_buf;
+         uword headers = pointer_to_uword (hs->headers_buf);
+         rv =
+           svm_fifo_enqueue (ts->tx_fifo, sizeof (headers), (u8 *) &headers);
+         ASSERT (rv == sizeof (headers));
+       }
+
       uword data = pointer_to_uword (hs->data);
       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
-      ASSERT (rv == sizeof (sizeof (data)));
+      ASSERT (rv == sizeof (data));
 
       goto done;
     }
 
   msg.data.type = HTTP_MSG_DATA_INLINE;
+  msg.data.body_offset = msg.data.headers_len;
 
   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
   ASSERT (rv == sizeof (msg));
 
-  if (!msg.data.len)
+  if (msg.data.headers_len)
+    {
+      rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (headers_buf), headers_buf);
+      ASSERT (rv == msg.data.headers_len);
+      vec_free (headers_buf);
+    }
+
+  if (!msg.data.body_len)
     goto done;
 
   rv = svm_fifo_enqueue (ts->tx_fifo, hs->data_len, hs->data);
@@ -142,6 +176,15 @@ hss_session_send_data (hss_url_handler_args_t *args)
   hs->data = args->data;
   hs->data_len = args->data_len;
   hs->free_data = args->free_vec_data;
+
+  /* Set content type only if we have some response data */
+  if (hs->data_len)
+    {
+      http_add_header (&hs->resp_headers,
+                      http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                      http_content_type_token (args->ct));
+    }
+
   start_send_data (hs, args->sc);
 }
 
@@ -217,7 +260,6 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   http_status_code_t sc = HTTP_STATUS_OK;
   hss_url_handler_args_t args = {};
   uword *p, *url_table;
-  http_content_type_t type;
   int rv;
 
   if (!hsm->enable_url_handlers || !target_path)
@@ -229,8 +271,6 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
       target_path = format (target_path, "index.html");
     }
 
-  type = content_type_from_request (target_path);
-
   /* Look for built-in GET / POST handlers */
   url_table =
     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
@@ -263,17 +303,24 @@ try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
     {
       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
                    (rt == HTTP_REQ_GET) ? "GET" : "POST", target_path);
-      sc = HTTP_STATUS_NOT_FOUND;
+      sc = HTTP_STATUS_BAD_GATEWAY;
     }
 
   hs->data = args.data;
   hs->data_len = args.data_len;
   hs->free_data = args.free_vec_data;
-  hs->content_type = type;
+
+  /* Set content type only if we have some response data */
+  if (hs->data_len)
+    {
+      http_add_header (&hs->resp_headers,
+                      http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                      http_content_type_token (args.ct));
+    }
 
   start_send_data (hs, sc);
 
-  if (!hs->data)
+  if (!hs->data_len)
     hss_session_disconnect_transport (hs);
 
   return 0;
@@ -337,18 +384,20 @@ try_index_file (hss_main_t *hsm, hss_session_t *hs, u8 *path)
     }
 
   redirect =
-    format (0,
-           "Location: http%s://%U%s%s\r\n\r\n",
-           proto == TRANSPORT_PROTO_TLS ? "s" : "", format_ip46_address,
-           &endpt.ip, endpt.is_ip4, print_port ? port_str : (u8 *) "", path);
+    format (0, "http%s://%U%s%s", proto == TRANSPORT_PROTO_TLS ? "s" : "",
+           format_ip46_address, &endpt.ip, endpt.is_ip4,
+           print_port ? port_str : (u8 *) "", path);
 
   if (hsm->debug_level > 0)
     clib_warning ("redirect: %s", redirect);
 
   vec_free (port_str);
 
-  hs->data = redirect;
-  hs->data_len = vec_len (redirect);
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_LOCATION),
+                  (const char *) redirect, vec_len (redirect));
+  hs->data = redirect; /* TODO: find better way  */
+  hs->data_len = 0;
   hs->free_data = 1;
 
   return HTTP_STATUS_MOVED;
@@ -367,8 +416,6 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   if (!hsm->www_root)
     return -1;
 
-  type = content_type_from_request (target);
-
   /* Remove dot segments to prevent path traversal */
   sanitized_path = http_path_remove_dot_segments (target);
 
@@ -420,11 +467,23 @@ try_file_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
   hs->path = path;
   hs->cache_pool_index = ce_index;
 
+  /* Set following headers only for happy path:
+   * Content-Type
+   * Cache-Control max-age
+   */
+  type = content_type_from_request (target);
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+                  http_content_type_token (type));
+  /* TODO configurable max-age value */
+  http_add_header (&hs->resp_headers,
+                  http_header_name_token (HTTP_HEADER_CACHE_CONTROL),
+                  http_token_lit ("max-age=600"));
+
 done:
   vec_free (sanitized_path);
-  hs->content_type = type;
   start_send_data (hs, sc);
-  if (!hs->data)
+  if (!hs->data_len)
     hss_session_disconnect_transport (hs);
 
   return 0;
@@ -459,6 +518,8 @@ hss_ts_rx_callback (session_t *ts)
   if (hs->free_data)
     vec_free (hs->data);
   hs->data = 0;
+  hs->resp_headers = 0;
+  vec_free (hs->headers_buf);
 
   /* Read the http message header */
   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
@@ -467,6 +528,9 @@ hss_ts_rx_callback (session_t *ts)
   if (msg.type != HTTP_MSG_REQUEST ||
       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
     {
+      http_add_header (&hs->resp_headers,
+                      http_header_name_token (HTTP_HEADER_ALLOW),
+                      http_token_lit ("GET, POST"));
       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
       goto done;
     }
@@ -648,6 +712,7 @@ hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
   hs->data = 0;
   hs->data_offset = 0;
   hs->free_data = 0;
+  vec_free (hs->headers_buf);
   vec_free (hs->path);
 
   hss_session_free (hs);
index c487d03..f726d3c 100644 (file)
@@ -147,6 +147,7 @@ handle_get_mactime (hss_url_handler_args_t *args)
 
   args->data = s;
   args->data_len = vec_len (s);
+  args->ct = HTTP_CONTENT_APP_JSON;
   args->free_vec_data = 1;
   return HSS_URL_HANDLER_OK;
 }
index 934e848..76899a2 100644 (file)
@@ -191,6 +191,7 @@ send_data_to_hss (hss_session_handle_t sh)
   args.sh = sh;
   args.data = vec_dup (pm->stats);
   args.data_len = vec_len (pm->stats);
+  args.ct = HTTP_CONTENT_TEXT_PLAIN;
   args.sc = HTTP_STATUS_OK;
   args.free_vec_data = 1;