http: return more than data from server app

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 <matfabia@cisco.com>
This commit is contained in:
Matus Fabian
2024-06-20 17:08:26 +02:00
committed by Florin Coras
parent 1f870c9bdc
commit 8ca6ce6fe1
15 changed files with 655 additions and 157 deletions

View 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")
}

View 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 {

View 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);

View 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;
}

View 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,50 +993,55 @@ 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);
/*
* Add headers. For now:
* - current time
* - expiration time
* - server name
* - content type
* - data length
*/
now = clib_timebase_now (&hm->timebase);
switch (msg.code)
if (msg.code >= HTTP_N_STATUS)
{
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;
}
/*
* Add "protocol layer" headers:
* - current time
* - server name
* - data length
*/
now = clib_timebase_now (&hm->timebase);
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))
{

View 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_ */
/*

View File

@ -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_ */

View File

@ -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_ */

View 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.

View 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;
}

View 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);

View 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;

View 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);

View 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;
}

View 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;