http: connection upgrade mechanism
Handle "Connection" and "Upgrade" headers in http transport layer which are used to create a tunnel for some other protocol on the same connection. Type: improvement Change-Id: Icf5479f36fbcc7259b157eaad957211be5ea2aae Signed-off-by: Matus Fabian <matfabia@cisco.com>
This commit is contained in:
parent
c4b4cd5e77
commit
82b3cc1826
@ -36,7 +36,7 @@ func init() {
|
||||
HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest,
|
||||
HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest,
|
||||
HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest,
|
||||
HttpClientGetRepeat, HttpClientPostRepeat)
|
||||
HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest)
|
||||
RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
|
||||
PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
|
||||
PromConsecutiveConnectionsTest)
|
||||
@ -1227,12 +1227,12 @@ func HttpContentLengthTest(s *NoTopoSuite) {
|
||||
validatePostInterfaceStats(s, resp)
|
||||
|
||||
resp, err = TcpSendReceive(serverAddress+":80",
|
||||
"POST /interface_stats.json HTTP/1.1\r\n Content-Length: 4 \r\n\r\n"+ifName)
|
||||
"POST /interface_stats.json HTTP/1.1\r\nContent-Length: 4 \r\n\r\n"+ifName)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
validatePostInterfaceStats(s, resp)
|
||||
|
||||
resp, err = TcpSendReceive(serverAddress+":80",
|
||||
"POST /interface_stats.json HTTP/1.1\r\n\tContent-Length:\t\t4\r\n\r\n"+ifName)
|
||||
"POST /interface_stats.json HTTP/1.1\r\nContent-Length:\t\t4\r\n\r\n"+ifName)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
validatePostInterfaceStats(s, resp)
|
||||
}
|
||||
@ -1289,13 +1289,42 @@ func HttpHeadersTest(s *NoTopoSuite) {
|
||||
serverAddress := s.VppAddr()
|
||||
vpp.Vppctl("http cli server")
|
||||
|
||||
resp, err := TcpSendReceive(
|
||||
serverAddress+":80",
|
||||
"GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\nAccept:text/xml\r\nAccept:\ttext/plain\t \r\nAccept:text/html\r\n\r\n")
|
||||
transport := http.DefaultTransport
|
||||
transport.(*http.Transport).Proxy = nil
|
||||
transport.(*http.Transport).DisableKeepAlives = false
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
s.AssertContains(resp, "HTTP/1.1 200 OK")
|
||||
s.AssertContains(resp, "Content-Type: text/plain")
|
||||
s.AssertNotContains(resp, "<html>", "html content received instead of plain text")
|
||||
req.Header.Add("Accept", "text/xml")
|
||||
req.Header.Add("Accept-Language", "*")
|
||||
req.Header.Add("Accept", "text/plain")
|
||||
req.Header.Add("Accept", "text/html")
|
||||
resp, err := client.Do(req)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
defer resp.Body.Close()
|
||||
s.Log(DumpHttpResp(resp, true))
|
||||
s.AssertHttpStatus(resp, 200)
|
||||
s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain")
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
s.AssertNotContains(string(data), "<html>", "html content received instead of plain text")
|
||||
|
||||
req2, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
req2.Header.Add("Accept", "text/html")
|
||||
resp2, err := client.Do(req2)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
defer resp2.Body.Close()
|
||||
s.Log(DumpHttpResp(resp2, true))
|
||||
s.AssertHttpStatus(resp2, 200)
|
||||
s.AssertHttpHeaderWithValue(resp2, "Content-Type", "text/html")
|
||||
data2, err := io.ReadAll(resp2.Body)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
s.AssertContains(string(data2), "<html>", "html content not received")
|
||||
}
|
||||
|
||||
func HttpInvalidHeadersTest(s *NoTopoSuite) {
|
||||
@ -1382,3 +1411,28 @@ func HttpConnTimeoutTest(s *NoTopoSuite) {
|
||||
_, err = conn.Read(reply)
|
||||
s.AssertMatchError(err, io.EOF, "connection not closed by server")
|
||||
}
|
||||
|
||||
func HttpIgnoreH2UpgradeTest(s *NoTopoSuite) {
|
||||
vpp := s.GetContainerByName("vpp").VppInstance
|
||||
serverAddress := s.VppAddr()
|
||||
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
|
||||
|
||||
transport := http.DefaultTransport
|
||||
transport.(*http.Transport).Proxy = nil
|
||||
transport.(*http.Transport).DisableKeepAlives = false
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json", nil)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
req.Header.Add("Connection", "Upgrade")
|
||||
req.Header.Add("Upgrade", "HTTP/2.0")
|
||||
resp, err := client.Do(req)
|
||||
s.AssertNil(err, fmt.Sprint(err))
|
||||
defer resp.Body.Close()
|
||||
s.Log(DumpHttpResp(resp, true))
|
||||
s.AssertHttpStatus(resp, 200)
|
||||
s.AssertHttpHeaderNotPresent(resp, "Upgrade")
|
||||
}
|
||||
|
@ -150,6 +150,7 @@ func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri strin
|
||||
s.AssertContains(writeOut, "GET response code: 200")
|
||||
s.AssertNotContains(log, "bytes remaining to read")
|
||||
s.AssertNotContains(log, "Operation timed out")
|
||||
s.AssertNotContains(log, "Upgrade:")
|
||||
}
|
||||
|
||||
func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) {
|
||||
@ -158,6 +159,7 @@ func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string)
|
||||
s.AssertContains(writeOut, "CONNECT response code: 200")
|
||||
s.AssertContains(writeOut, "PUT response code: 201")
|
||||
s.AssertNotContains(log, "Operation timed out")
|
||||
s.AssertNotContains(log, "Upgrade:")
|
||||
}
|
||||
|
||||
var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() {
|
||||
|
@ -51,6 +51,7 @@ typedef struct
|
||||
u8 *tx_buf;
|
||||
u32 tx_offset;
|
||||
u32 vpp_session_index;
|
||||
http_header_table_t req_headers;
|
||||
http_header_t *resp_headers;
|
||||
} hcs_session_t;
|
||||
|
||||
@ -177,7 +178,7 @@ start_send_data (hcs_session_t *hs, http_status_code_t status)
|
||||
if (vec_len (hs->resp_headers))
|
||||
{
|
||||
headers_buf = http_serialize_headers (hs->resp_headers);
|
||||
vec_free (hs->resp_headers);
|
||||
vec_reset_length (hs->resp_headers);
|
||||
msg.data.headers_offset = 0;
|
||||
msg.data.headers_len = vec_len (headers_buf);
|
||||
}
|
||||
@ -370,7 +371,8 @@ hcs_ts_rx_callback (session_t *ts)
|
||||
|
||||
hs = hcs_session_get (ts->thread_index, ts->opaque);
|
||||
hs->tx_buf = 0;
|
||||
hs->resp_headers = 0;
|
||||
vec_reset_length (hs->resp_headers);
|
||||
http_reset_header_table (&hs->req_headers);
|
||||
|
||||
/* Read the http message header */
|
||||
rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
|
||||
@ -413,30 +415,22 @@ hcs_ts_rx_callback (session_t *ts)
|
||||
|
||||
if (msg.data.headers_len)
|
||||
{
|
||||
u8 *headers = 0;
|
||||
http_header_table_t *ht;
|
||||
vec_validate (headers, msg.data.headers_len - 1);
|
||||
http_init_header_table_buf (&hs->req_headers, msg);
|
||||
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
|
||||
msg.data.headers_len, headers);
|
||||
msg.data.headers_len, hs->req_headers.buf);
|
||||
ASSERT (rv == msg.data.headers_len);
|
||||
if (http_parse_headers (headers, &ht))
|
||||
http_build_header_table (&hs->req_headers, msg);
|
||||
const http_header_t *accept = http_get_header (
|
||||
&hs->req_headers, http_header_name_token (HTTP_HEADER_ACCEPT));
|
||||
if (accept)
|
||||
{
|
||||
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_name_str (HTTP_HEADER_ACCEPT));
|
||||
if (accept_value)
|
||||
{
|
||||
HCS_DBG ("client accept: %s", accept_value);
|
||||
HCS_DBG ("client accept: %U", format_http_bytes, accept->value.base,
|
||||
accept->value.len);
|
||||
/* just for testing purpose, we don't care about precedence */
|
||||
if (strstr (accept_value, "text/plain"))
|
||||
if (http_token_contains (accept->value.base, accept->value.len,
|
||||
http_token_lit ("text/plain")))
|
||||
args.plain_text = 1;
|
||||
}
|
||||
http_free_header_table (ht);
|
||||
vec_free (headers);
|
||||
}
|
||||
|
||||
args.hs_index = hs->session_index;
|
||||
@ -547,6 +541,8 @@ hcs_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
|
||||
return;
|
||||
|
||||
vec_free (hs->tx_buf);
|
||||
vec_free (hs->resp_headers);
|
||||
http_free_header_table (&hs->req_headers);
|
||||
hcs_session_free (hs);
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,12 @@ typedef struct
|
||||
http_msg_t msg;
|
||||
} hc_worker_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 *name;
|
||||
u8 *value;
|
||||
} hc_http_header_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 app_index;
|
||||
@ -52,7 +58,7 @@ typedef struct
|
||||
u8 *resp_headers;
|
||||
u8 *http_response;
|
||||
u8 *response_status;
|
||||
http_header_ht_t *custom_header;
|
||||
hc_http_header_t *custom_header;
|
||||
u8 is_file;
|
||||
u8 use_ptr;
|
||||
u8 *filename;
|
||||
@ -181,7 +187,7 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index,
|
||||
hc_main_t *hcm = &hc_main;
|
||||
hc_worker_t *wrk;
|
||||
u32 new_hc_index;
|
||||
http_header_ht_t *header;
|
||||
hc_http_header_t *header;
|
||||
HTTP_DBG (1, "ho hc_index: %d", hc_session_index);
|
||||
|
||||
if (err)
|
||||
@ -344,7 +350,6 @@ hc_rx_callback (session_t *s)
|
||||
format (0, "%U", format_http_status_code, msg.code);
|
||||
svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset);
|
||||
|
||||
http_header_table_t *ht;
|
||||
vec_validate (hcm->resp_headers, msg.data.headers_len - 1);
|
||||
vec_set_len (hcm->resp_headers, msg.data.headers_len);
|
||||
rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len,
|
||||
@ -352,14 +357,6 @@ hc_rx_callback (session_t *s)
|
||||
|
||||
ASSERT (rv == msg.data.headers_len);
|
||||
HTTP_DBG (1, (char *) format (0, "%v", hcm->resp_headers));
|
||||
if (http_parse_headers (hcm->resp_headers, &ht))
|
||||
{
|
||||
clib_warning ("invalid headers received");
|
||||
vlib_process_signal_event_mt (
|
||||
wrk->vlib_main, hcm->cli_node_index, HC_GENERIC_ERR, 0);
|
||||
return -1;
|
||||
}
|
||||
http_free_header_table (ht);
|
||||
msg.data.body_offset -=
|
||||
msg.data.headers_len + msg.data.headers_offset;
|
||||
}
|
||||
@ -666,7 +663,7 @@ hc_cleanup ()
|
||||
HTTP_DBG (1, "cleanup");
|
||||
hc_main_t *hcm = &hc_main;
|
||||
hc_worker_t *wrk;
|
||||
http_header_ht_t *header;
|
||||
hc_http_header_t *header;
|
||||
|
||||
vec_foreach (wrk, hcm->wrk)
|
||||
hcc_worker_cleanup (wrk);
|
||||
@ -696,7 +693,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input,
|
||||
unformat_input_t _line_input, *line_input = &_line_input;
|
||||
u8 *path = 0;
|
||||
u8 *file_data;
|
||||
http_header_ht_t new_header;
|
||||
hc_http_header_t new_header;
|
||||
u8 *name;
|
||||
u8 *value;
|
||||
int rv;
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,11 @@ Usage
|
||||
-----
|
||||
|
||||
The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``,
|
||||
``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_parse_headers``, ``http_get_header``,
|
||||
``http_free_header_table``, ``http_add_header``, ``http_serialize_headers``, ``http_parse_authority_form_target``,
|
||||
``http_serialize_authority_form_target``, ``http_parse_absolute_form``, ``http_parse_masque_host_port``.
|
||||
``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_build_header_table``, ``http_get_header``,
|
||||
``http_reset_header_table``, ``http_free_header_table``, ``http_add_header``,
|
||||
``http_serialize_headers``, ``http_parse_authority_form_target``, ``http_serialize_authority_form_target``,
|
||||
``http_parse_absolute_form``, ``http_parse_masque_host_port``, ``http_decap_udp_payload_datagram``,
|
||||
``http_encap_udp_payload_datagram``. ``http_token_is``, ``http_token_is_case``, ``http_token_contains``
|
||||
|
||||
It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications.
|
||||
|
||||
@ -125,24 +127,60 @@ Following example shows how to parse headers:
|
||||
#include <http/http_header_names.h>
|
||||
if (msg.data.headers_len)
|
||||
{
|
||||
u8 *headers = 0;
|
||||
http_header_table_t *ht;
|
||||
vec_validate (headers, msg.data.headers_len - 1);
|
||||
http_header_table_t ht = HTTP_HEADER_TABLE_NULL;
|
||||
http_init_header_table_buf (&ht, msg);
|
||||
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
|
||||
msg.data.headers_len, headers);
|
||||
msg.data.headers_len, ht.buf);
|
||||
ASSERT (rv == msg.data.headers_len);
|
||||
if (http_parse_headers (headers, &ht))
|
||||
{
|
||||
/* your error handling */
|
||||
}
|
||||
http_build_header_table (&ht, msg);
|
||||
/* get Accept header */
|
||||
const char *accept_value = http_get_header (ht, http_header_name_str (HTTP_HEADER_ACCEPT));
|
||||
const http_header_t *accept = http_get_header (&ht, http_header_name_token (HTTP_HEADER_ACCEPT));
|
||||
if (accept_value)
|
||||
{
|
||||
/* do something interesting */
|
||||
}
|
||||
http_free_header_table (&ht);
|
||||
}
|
||||
|
||||
Allocated header table memory can be reused, you just need to reset it using ``http_reset_header_table`` before reuse.
|
||||
We will add following member to our session context structure:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* ... */
|
||||
http_header_table_t ht;
|
||||
} session_ctx_t;
|
||||
|
||||
Don't forget to zero allocated session context.
|
||||
|
||||
And in ``session_cleanup_callback`` we free header table memory:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
http_free_header_table (&ctx->ht);
|
||||
|
||||
Modified example above:
|
||||
|
||||
.. code-block:: C
|
||||
|
||||
#include <http/http_header_names.h>
|
||||
http_reset_header_table (&ctx->ht);
|
||||
/* ... */
|
||||
if (msg.data.headers_len)
|
||||
{
|
||||
http_init_header_table_buf (&ctx->ht, msg);
|
||||
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
|
||||
msg.data.headers_len, ctx->ht.buf);
|
||||
ASSERT (rv == msg.data.headers_len);
|
||||
http_build_header_table (&ctx->ht, msg);
|
||||
/* get Accept header */
|
||||
const http_header_t *accept = http_get_header (&ctx->ht, http_header_name_token (HTTP_HEADER_ACCEPT));
|
||||
if (accept_value)
|
||||
{
|
||||
/* do something interesting */
|
||||
}
|
||||
http_free_header_table (ht);
|
||||
vec_free (headers);
|
||||
}
|
||||
|
||||
Finally application reads body (if any), which might be received in multiple pieces (depends on size), so we might need some state machine in ``builtin_app_rx_callback``.
|
||||
@ -437,24 +475,19 @@ Following example shows how to parse headers:
|
||||
#include <http/http_header_names.h>
|
||||
if (msg.data.headers_len)
|
||||
{
|
||||
u8 *headers = 0;
|
||||
http_header_table_t *ht;
|
||||
vec_validate (headers, msg.data.headers_len - 1);
|
||||
http_header_table_t ht = HTTP_HEADER_TABLE_NULL;
|
||||
http_init_header_table_buf (&ht, msg);
|
||||
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
|
||||
msg.data.headers_len, headers);
|
||||
msg.data.headers_len, ht.buf);
|
||||
ASSERT (rv == msg.data.headers_len);
|
||||
if (http_parse_headers (headers, &ht))
|
||||
{
|
||||
/* your error handling */
|
||||
}
|
||||
http_build_header_table (&ht, msg);
|
||||
/* get Content-Type header */
|
||||
const char *content_type = http_get_header (ht, http_header_name_str (HTTP_HEADER_CONTENT_TYPE));
|
||||
const http_header_t *content_type = http_get_header (&ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE));
|
||||
if (content_type)
|
||||
{
|
||||
/* do something interesting */
|
||||
}
|
||||
http_free_header_table (ht);
|
||||
vec_free (headers);
|
||||
http_free_header_table (&ht);
|
||||
}
|
||||
|
||||
Finally application reads body, which might be received in multiple pieces (depends on size), so we might need some state machine in ``builtin_app_rx_callback``.
|
||||
|
Loading…
x
Reference in New Issue
Block a user