diff options
-rw-r--r-- | extras/hs-test/http_test.go | 87 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_cli.c | 4 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_client_cli.c | 102 | ||||
-rw-r--r-- | src/plugins/hs_apps/http_tps.c | 6 | ||||
-rw-r--r-- | src/plugins/http/http.c | 354 | ||||
-rw-r--r-- | src/plugins/http/http.h | 5 | ||||
-rw-r--r-- | src/plugins/http/http_plugin.rst | 202 | ||||
-rw-r--r-- | src/plugins/http/http_status_codes.h | 27 | ||||
-rw-r--r-- | src/plugins/http_static/static_server.c | 4 |
9 files changed, 626 insertions, 165 deletions
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index dcfe56199ed..75db60d43ae 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "github.com/onsi/gomega/ghttp" "github.com/onsi/gomega/gmeasure" "io" "net" @@ -18,6 +19,7 @@ import ( func init() { RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest) + RegisterSoloVethTests(HttpClientGetMemLeakTest) RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest, HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest, @@ -25,7 +27,7 @@ func init() { HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest, HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest, HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest, - HttpHeadersTest, HttpStaticFileHandler) + HttpHeadersTest, HttpStaticFileHandler, HttpClientTest, HttpClientErrRespTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest, PromConcurrentConnections, PromMemLeakTest) } @@ -169,6 +171,7 @@ func HttpCliTest(s *VethsSuite) { s.Log(o) s.AssertContains(o, "<html>", "<html> not found in the result!") + s.AssertContains(o, "</html>", "</html> not found in the result!") } func HttpCliConnectErrorTest(s *VethsSuite) { @@ -185,6 +188,51 @@ func HttpCliConnectErrorTest(s *VethsSuite) { s.AssertContains(o, "failed to connect") } +func HttpClientTest(s *NoTopoSuite) { + serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString() + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress+":80") + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/test"), + ghttp.VerifyHeader(http.Header{"User-Agent": []string{"http_cli_client"}}), + ghttp.VerifyHeader(http.Header{"Accept": []string{"text / html"}}), + ghttp.RespondWith(http.StatusOK, "<html><body><p>Hello</p></body></html>"), + )) + server.Start() + defer server.Close() + uri := "http://" + serverAddress + "/80" + vpp := s.GetContainerByName("vpp").VppInstance + o := vpp.Vppctl("http cli client uri " + uri + " query /test") + + s.Log(o) + s.AssertContains(o, "<html>", "<html> not found in the result!") + s.AssertContains(o, "</html>", "</html> not found in the result!") +} + +func HttpClientErrRespTest(s *NoTopoSuite) { + serverAddress := s.GetInterfaceByName(TapInterfaceName).Ip4AddressString() + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress+":80") + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/test"), + ghttp.RespondWith(http.StatusNotFound, "404: Not Found"), + )) + server.Start() + defer server.Close() + uri := "http://" + serverAddress + "/80" + vpp := s.GetContainerByName("vpp").VppInstance + o := vpp.Vppctl("http cli client uri " + uri + " query /test") + + s.Log(o) + s.AssertContains(o, "404: Not Found", "error not found in the result!") +} + func HttpStaticPromTest(s *NoTopoSuite) { query := "stats.prom" vpp := s.GetContainerByName("vpp").VppInstance @@ -276,9 +324,40 @@ func PromMemLeakTest(s *NoTopoSuite) { vpp.MemLeakCheck(traces1, traces2) } +func HttpClientGetMemLeakTest(s *VethsSuite) { + s.SkipUnlessLeakCheck() + + serverContainer := s.GetContainerByName("server-vpp").VppInstance + clientContainer := s.GetContainerByName("client-vpp").VppInstance + serverVeth := s.GetInterfaceByName(ServerInterfaceName) + + /* no goVPP less noise */ + clientContainer.Disconnect() + + serverContainer.Vppctl("http cli server") + + uri := "http://" + serverVeth.Ip4AddressString() + "/80" + + /* warmup request (FIB) */ + clientContainer.Vppctl("http cli client uri " + uri + " query /show/version") + + clientContainer.EnableMemoryTrace() + traces1, err := clientContainer.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + + clientContainer.Vppctl("http cli client uri " + uri + " query /show/vlib/graph") + + /* let's give it some time to clean up sessions */ + time.Sleep(time.Second * 12) + + traces2, err := clientContainer.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + clientContainer.MemLeakCheck(traces1, traces2) +} + func HttpStaticFileHandler(s *NoTopoSuite) { - content := "<http><body><p>Hello</p></body></http>" - content2 := "<http><body><p>Page</p></body></http>" + content := "<html><body><p>Hello</p></body></html>" + content2 := "<html><body><p>Page</p></body></html>" vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec("mkdir -p " + wwwRootPath) vpp.Container.CreateFile(wwwRootPath+"/index.html", content) @@ -357,7 +436,7 @@ func HttpStaticPathTraversalTest(s *NoTopoSuite) { func HttpStaticMovedTest(s *NoTopoSuite) { vpp := s.GetContainerByName("vpp").VppInstance vpp.Container.Exec("mkdir -p " + wwwRootPath + "/tmp.aaa") - vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<http><body><p>Hello</p></body></http>") + vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "<html><body><p>Hello</p></body></html>") serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString() s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index dfe50c58092..814f5f15fcf 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -207,7 +207,7 @@ start_send_data (hcs_session_t *hs, http_status_code_t status) done: if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); } static void @@ -469,7 +469,7 @@ hcs_ts_tx_callback (session_t *ts) } if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); return 0; } diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index a99169bafea..722499b24c3 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -16,6 +16,9 @@ #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> +#include <http/http_status_codes.h> #define HCC_DEBUG 0 @@ -34,12 +37,12 @@ typedef struct u32 vpp_session_index; u32 to_recv; u8 is_closed; + http_header_t *req_headers; } hcc_session_t; typedef struct { hcc_session_t *sessions; - u8 *rx_buf; u32 thread_index; } hcc_worker_t; @@ -95,13 +98,6 @@ hcc_session_get (u32 hs_index, u32 thread_index) return pool_elt_at_index (wrk->sessions, hs_index); } -static void -hcc_session_free (u32 thread_index, hcc_session_t *hs) -{ - hcc_worker_t *wrk = hcc_worker_get (thread_index); - pool_put (wrk->sessions, hs); -} - static int hcc_ts_accept_callback (session_t *ts) { @@ -128,6 +124,7 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, hcc_session_t *hs, *new_hs; hcc_worker_t *wrk; http_msg_t msg; + u8 *headers_buf; int rv; HCC_DBG ("hc_index: %d", hc_index); @@ -149,24 +146,42 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, hs->vpp_session_index = as->session_index; + http_add_header (&hs->req_headers, + http_header_name_token (HTTP_HEADER_ACCEPT), + http_content_type_token (HTTP_CONTENT_TEXT_HTML)); + headers_buf = http_serialize_headers (hs->req_headers); + vec_free (hs->req_headers); + msg.type = HTTP_MSG_REQUEST; msg.method_type = HTTP_REQ_GET; - msg.content_type = HTTP_CONTENT_TEXT_HTML; + /* request target */ + msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; + msg.data.target_path_offset = 0; + msg.data.target_path_len = vec_len (hcm->http_query); + /* custom headers */ + msg.data.headers_offset = msg.data.target_path_len; + msg.data.headers_len = vec_len (headers_buf); + /* request body */ + msg.data.body_len = 0; + /* data type and total length */ msg.data.type = HTTP_MSG_DATA_INLINE; - msg.data.len = vec_len (hcm->http_query); + msg.data.len = + msg.data.target_path_len + msg.data.headers_len + msg.data.body_len; - svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { hcm->http_query, vec_len (hcm->http_query) } }; + svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, + { hcm->http_query, vec_len (hcm->http_query) }, + { headers_buf, vec_len (headers_buf) } }; - rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 2, 0 /* allow partial */); - if (rv < 0 || rv != sizeof (msg) + vec_len (hcm->http_query)) + rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */); + vec_free (headers_buf); + if (rv < 0 || rv != sizeof (msg) + msg.data.len) { clib_warning ("failed app enqueue"); return -1; } if (svm_fifo_set_event (as->tx_fifo)) - session_send_io_evt_to_thread (as->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (as->handle, SESSION_IO_EVT_TX); return 0; } @@ -221,17 +236,26 @@ hcc_ts_rx_callback (session_t *ts) if (hs->to_recv == 0) { + /* read the http message header */ rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); - if (msg.type != HTTP_MSG_REPLY || msg.code != HTTP_STATUS_OK) + if (msg.type != HTTP_MSG_REPLY) { clib_warning ("unexpected msg type %d", msg.type); return 0; } - vec_validate (hcm->http_response, msg.data.len - 1); + /* drop everything up to body */ + svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.body_offset); + hs->to_recv = msg.data.body_len; + if (msg.code != HTTP_STATUS_OK && hs->to_recv == 0) + { + hcm->http_response = format (0, "request failed, response code: %U", + format_http_status_code, msg.code); + goto done; + } + vec_validate (hcm->http_response, msg.data.body_len - 1); vec_reset_length (hcm->http_response); - hs->to_recv = msg.data.len; } u32 max_deq = svm_fifo_max_dequeue (ts->rx_fifo); @@ -253,8 +277,10 @@ hcc_ts_rx_callback (session_t *ts) hs->to_recv -= rv; HCC_DBG ("app rcvd %d, remains %d", rv, hs->to_recv); +done: if (hs->to_recv == 0) { + HCC_DBG ("all data received, going to disconnect"); hcc_session_disconnect (ts); vlib_process_signal_event_mt (hcm->vlib_main, hcm->cli_node_index, HCC_REPLY_RECEIVED, 0); @@ -264,18 +290,6 @@ hcc_ts_rx_callback (session_t *ts) } static void -hcc_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf) -{ - hcc_session_t *hs; - - hs = hcc_session_get (s->thread_index, s->opaque); - if (!hs) - return; - - hcc_session_free (s->thread_index, hs); -} - -static void hcc_ts_transport_closed (session_t *s) { hcc_main_t *hcm = &hcc_main; @@ -293,7 +307,6 @@ static session_cb_vft_t hcc_session_cb_vft = { .builtin_app_rx_callback = hcc_ts_rx_callback, .builtin_app_tx_callback = hcc_ts_tx_callback, .session_reset_callback = hcc_ts_reset_callback, - .session_cleanup_callback = hcc_ts_cleanup_callback, .session_transport_closed_callback = hcc_ts_transport_closed, }; @@ -423,7 +436,6 @@ hcc_run (vlib_main_t *vm, int print_output) case HCC_REPLY_RECEIVED: if (print_output) vlib_cli_output (vm, "%v", hcm->http_response); - vec_free (hcm->http_response); break; case HCC_TRANSPORT_CLOSED: err = clib_error_return (0, "error, transport closed"); @@ -460,6 +472,28 @@ hcc_detach () return rv; } +static void +hcc_worker_cleanup (hcc_worker_t *wrk) +{ + pool_free (wrk->sessions); +} + +static void +hcc_cleanup () +{ + hcc_main_t *hcm = &hcc_main; + hcc_worker_t *wrk; + + vec_foreach (wrk, hcm->wrk) + hcc_worker_cleanup (wrk); + + vec_free (hcm->uri); + vec_free (hcm->http_query); + vec_free (hcm->http_response); + vec_free (hcm->appns_id); + vec_free (hcm->wrk); +} + static clib_error_t * hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -509,7 +543,6 @@ hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, } } - vec_free (hcm->appns_id); hcm->appns_id = appns_id; hcm->cli_node_index = vlib_get_current_process (vm)->node_runtime.node_index; @@ -540,8 +573,7 @@ hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, } done: - vec_free (hcm->uri); - vec_free (hcm->http_query); + hcc_cleanup (); unformat_free (line_input); return err; } diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c index 9cc592cd746..b37c447295a 100644 --- a/src/plugins/hs_apps/http_tps.c +++ b/src/plugins/hs_apps/http_tps.c @@ -154,7 +154,7 @@ hts_session_tx_zc (hts_session_t *hs, session_t *ts) svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); } static void @@ -201,7 +201,7 @@ hts_session_tx_no_zc (hts_session_t *hs, session_t *ts) svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); } static inline void @@ -263,7 +263,7 @@ hts_start_send_data (hts_session_t *hs, http_status_code_t status) 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); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); return; } diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 416ba8fe817..017fd50ebae 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -16,11 +16,11 @@ #include <http/http.h> #include <vnet/session/session.h> #include <http/http_timer.h> +#include <http/http_status_codes.h> static http_main_t http_main; #define HTTP_FIFO_THRESH (16 << 10) -#define CONTENT_LEN_STR "Content-Length: " /* HTTP state machine result */ typedef enum http_sm_result_t_ @@ -30,18 +30,12 @@ typedef enum http_sm_result_t_ HTTP_SM_ERROR = -1, } http_sm_result_t; -const char *http_status_code_str[] = { -#define _(c, s, str) str, - foreach_http_status_code -#undef _ -}; - const http_buffer_type_t msg_to_buf_type[] = { [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO, [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR, }; -u8 * +static u8 * format_http_state (u8 *s, va_list *va) { http_state_t state = va_arg (*va, http_state_t); @@ -388,9 +382,13 @@ static const char *http_response_template = "HTTP/1.1 %s\r\n" "Content-Length: %u\r\n" "%s"; +/** + * http request boilerplate + */ static const char *http_request_template = "GET %s HTTP/1.1\r\n" + "Host: %v\r\n" "User-Agent: %v\r\n" - "Accept: */*\r\n\r\n"; + "%s"; static u32 http_send_data (http_conn_t *hc, u8 *data, u32 length, u32 offset) @@ -409,7 +407,7 @@ http_send_data (http_conn_t *hc, u8 *data, u32 length, u32 offset) return offset; if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); return (offset + sent); } @@ -655,6 +653,111 @@ http_parse_request_line (http_conn_t *hc, http_status_code_t *ec) return 0; } +#define expect_char(c) \ + if (*p++ != c) \ + { \ + clib_warning ("unexpected character"); \ + return -1; \ + } + +#define parse_int(val, mul) \ + do \ + { \ + if (!isdigit (*p)) \ + { \ + clib_warning ("expected digit"); \ + return -1; \ + } \ + val += mul * (*p++ - '0'); \ + } \ + while (0) + +static int +http_parse_status_line (http_conn_t *hc) +{ + int i; + u32 next_line_offset; + u8 *p, *end; + u16 status_code = 0; + + i = v_find_index (hc->rx_buf, 0, 0, "\r\n"); + /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */ + if (i < 0) + { + clib_warning ("status line incomplete"); + return -1; + } + HTTP_DBG (0, "status line length: %d", i); + if (i < 12) + { + clib_warning ("status line too short (%d)", i); + return -1; + } + next_line_offset = i + 2; + p = hc->rx_buf; + end = hc->rx_buf + i; + + /* there should be at least one more CRLF */ + if (vec_len (hc->rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + return -1; + } + + /* parse version */ + expect_char ('H'); + expect_char ('T'); + expect_char ('T'); + expect_char ('P'); + expect_char ('/'); + expect_char ('1'); + expect_char ('.'); + if (!isdigit (*p++)) + { + clib_warning ("invalid HTTP minor version"); + return -1; + } + + /* skip space(s) */ + if (*p != ' ') + { + clib_warning ("no space after HTTP version"); + return -1; + } + do + { + p++; + if (p == end) + { + clib_warning ("no status code"); + return -1; + } + } + while (*p == ' '); + + /* parse status code */ + if ((end - p) < 3) + { + clib_warning ("not enough characters for status code"); + return -1; + } + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + clib_warning ("invalid status code %d", status_code); + return -1; + } + hc->status_code = status_code; + HTTP_DBG (0, "status code: %d", hc->status_code); + + /* set buffer offset to nex line start */ + hc->rx_buf_offset = next_line_offset; + + return 0; +} + static int http_identify_headers (http_conn_t *hc, http_status_code_t *ec) { @@ -692,6 +795,7 @@ http_identify_message_body (http_conn_t *hc, http_status_code_t *ec) unformat_input_t input; int i, len; u8 *line; + u32 body_len; hc->body_len = 0; @@ -733,13 +837,14 @@ http_identify_message_body (http_conn_t *hc, http_status_code_t *ec) HTTP_DBG (0, "%v", line); unformat_init_vector (&input, line); - if (!unformat (&input, "%lu", &hc->body_len)) + if (!unformat (&input, "%u", &body_len)) { clib_warning ("failed to unformat content length value"); *ec = HTTP_STATUS_BAD_REQUEST; return -1; } unformat_free (&input); + hc->body_len = body_len; hc->body_offset = hc->headers_offset + hc->headers_len + 2; HTTP_DBG (0, "body length: %u", hc->body_len); @@ -748,61 +853,16 @@ http_identify_message_body (http_conn_t *hc, http_status_code_t *ec) return 0; } -static int -http_parse_header (http_conn_t *hc, int *content_length) -{ - unformat_input_t input; - int i, len; - u8 *line; - - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, CONTENT_LEN_STR); - if (i < 0) - { - clib_warning ("cannot find '%s' in the header!", CONTENT_LEN_STR); - return -1; - } - - hc->rx_buf_offset = i; - - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "\n"); - if (i < 0) - { - clib_warning ("end of line missing; incomplete data"); - return -1; - } - - len = i - hc->rx_buf_offset; - line = vec_new (u8, len); - clib_memcpy (line, hc->rx_buf + hc->rx_buf_offset, len); - - unformat_init_vector (&input, line); - if (!unformat (&input, CONTENT_LEN_STR "%d", content_length)) - { - clib_warning ("failed to unformat content length!"); - return -1; - } - unformat_free (&input); - - /* skip rest of the header */ - hc->rx_buf_offset += len; - i = v_find_index (hc->rx_buf, hc->rx_buf_offset, 0, "<html>"); - if (i < 0) - { - clib_warning ("<html> tag not found"); - return -1; - } - hc->rx_buf_offset = i; - - return 0; -} - static http_sm_result_t http_state_wait_server_reply (http_conn_t *hc, transport_send_params_t *sp) { - int i, rv, content_length; + int rv; http_msg_t msg = {}; app_worker_t *app_wrk; session_t *as; + u32 len; + http_status_code_t ec; + http_main_t *hm = &http_main; rv = http_read_message (hc); @@ -813,57 +873,58 @@ http_state_wait_server_reply (http_conn_t *hc, transport_send_params_t *sp) return HTTP_SM_STOP; } + HTTP_DBG (0, "%v", hc->rx_buf); + if (vec_len (hc->rx_buf) < 8) { clib_warning ("response buffer too short"); goto error; } - if ((i = v_find_index (hc->rx_buf, 0, 0, "200 OK")) >= 0) - { - msg.type = HTTP_MSG_REPLY; - msg.content_type = HTTP_CONTENT_TEXT_HTML; - msg.code = HTTP_STATUS_OK; - msg.data.type = HTTP_MSG_DATA_INLINE; - msg.data.len = 0; + rv = http_parse_status_line (hc); + if (rv) + goto error; - rv = http_parse_header (hc, &content_length); - if (rv) - { - clib_warning ("failed to parse http reply"); - goto error; - } - msg.data.len = content_length; - u32 dlen = vec_len (hc->rx_buf) - hc->rx_buf_offset; - as = session_get_from_handle (hc->h_pa_session_handle); - svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { &hc->rx_buf[hc->rx_buf_offset], dlen } }; - - rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, - 0 /* allow partial */); - if (rv < 0) - { - clib_warning ("error enqueue"); - return HTTP_SM_ERROR; - } + rv = http_identify_headers (hc, &ec); + if (rv) + goto error; - hc->rx_buf_offset += dlen; - hc->to_recv = content_length - dlen; + rv = http_identify_message_body (hc, &ec); + if (rv) + goto error; - if (hc->rx_buf_offset == vec_len (hc->rx_buf)) - { - vec_reset_length (hc->rx_buf); - hc->rx_buf_offset = 0; - } + len = vec_len (hc->rx_buf); - if (hc->to_recv == 0) - { - hc->rx_buf_offset = 0; - vec_reset_length (hc->rx_buf); - http_state_change (hc, HTTP_STATE_WAIT_APP_METHOD); - } + msg.type = HTTP_MSG_REPLY; + msg.code = hm->sc_by_u16[hc->status_code]; + msg.data.headers_offset = hc->headers_offset; + msg.data.headers_len = hc->headers_len; + msg.data.body_offset = hc->body_offset; + msg.data.body_len = hc->body_len; + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = len; + + as = session_get_from_handle (hc->h_pa_session_handle); + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, + { hc->rx_buf, len } }; + + rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */); + if (rv < 0) + { + clib_warning ("error enqueue"); + return HTTP_SM_ERROR; + } + + vec_free (hc->rx_buf); + + if (hc->body_len <= (len - hc->body_offset)) + { + hc->to_recv = 0; + http_state_change (hc, HTTP_STATE_WAIT_APP_METHOD); + } else { + hc->to_recv = hc->body_len - (len - hc->body_offset); http_state_change (hc, HTTP_STATE_CLIENT_IO_MORE_DATA); } @@ -871,12 +932,6 @@ http_state_wait_server_reply (http_conn_t *hc, transport_send_params_t *sp) if (app_wrk) app_worker_rx_notify (app_wrk, as); return HTTP_SM_STOP; - } - else - { - clib_warning ("Unknown http method %v", hc->rx_buf); - goto error; - } error: session_transport_closing_notify (&hc->connection); @@ -925,7 +980,6 @@ http_state_wait_client_method (http_conn_t *hc, transport_send_params_t *sp) msg.type = HTTP_MSG_REQUEST; msg.method_type = hc->method; - msg.content_type = HTTP_CONTENT_TEXT_HTML; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = len; msg.data.target_form = hc->target_form; @@ -1080,7 +1134,7 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) { http_msg_t msg; session_t *as; - u8 *buf = 0, *request; + u8 *target = 0, *request; u32 offset; int rv; @@ -1107,12 +1161,44 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) clib_warning ("unsupported method %d", msg.method_type); goto error; } + if (msg.data.body_len != 0) + { + clib_warning ("GET request shouldn't include data"); + goto error; + } - vec_validate (buf, msg.data.len - 1); - rv = svm_fifo_dequeue (as->tx_fifo, msg.data.len, buf); - ASSERT (rv == msg.data.len); + /* read request target */ + vec_validate (target, msg.data.target_path_len - 1); + rv = svm_fifo_dequeue (as->tx_fifo, msg.data.target_path_len, target); + ASSERT (rv == msg.data.target_path_len); + + /* + * Add "protocol layer" headers: + * - host + * - user agent + */ + request = format (0, http_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent*/ + hc->app_name, + /* Any headers from app? */ + msg.data.headers_len ? "" : "\r\n"); + + /* Add headers from app (if any) */ + if (msg.data.headers_len) + { + HTTP_DBG (0, "get headers from app, len %d", msg.data.headers_len); + u32 orig_len = vec_len (request); + vec_resize (request, msg.data.headers_len); + u8 *p = request + orig_len; + rv = svm_fifo_dequeue (as->tx_fifo, msg.data.headers_len, p); + ASSERT (rv == msg.data.headers_len); + } + HTTP_DBG (0, "%v", request); - request = format (0, http_request_template, buf, hc->app_name); offset = http_send_data (hc, request, vec_len (request), 0); if (offset != vec_len (request)) { @@ -1122,7 +1208,7 @@ http_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) http_state_change (hc, HTTP_STATE_WAIT_SERVER_REPLY); - vec_free (buf); + vec_free (target); vec_free (request); return HTTP_SM_STOP; @@ -1232,7 +1318,7 @@ http_state_app_io_more_data (http_conn_t *hc, transport_send_params_t *sp) if (!http_buffer_is_drained (hb)) { if (sent && svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH) { @@ -1246,7 +1332,7 @@ http_state_app_io_more_data (http_conn_t *hc, transport_send_params_t *sp) else { if (sent && svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX_FLUSH); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH); /* Finished transaction, back to HTTP_STATE_WAIT_METHOD */ http_state_change (hc, HTTP_STATE_WAIT_CLIENT_METHOD); @@ -1346,6 +1432,7 @@ http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf) clib_warning ("no http connection for %u", ts->session_index); return; } + HTTP_DBG (1, "going to free session %x", ts->opaque); vec_free (hc->rx_buf); @@ -1353,6 +1440,12 @@ http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf) http_conn_timer_stop (hc); session_transport_delete_notify (&hc->connection); + + if (!hc->is_server) + { + vec_free (hc->app_name); + vec_free (hc->host); + } http_conn_free (hc); } @@ -1458,11 +1551,20 @@ http_transport_connect (transport_endpoint_cfg_t *tep) hc->state = HTTP_CONN_STATE_CONNECTING; cargs->api_context = hc_index; + hc->is_server = 0; + if (vec_len (app->name)) hc->app_name = vec_dup (app->name); else hc->app_name = format (0, "VPP HTTP client"); + if (sep->is_ip4) + hc->host = format (0, "%U:%d", format_ip4_address, &sep->ip.ip4, + clib_net_to_host_u16 (sep->port)); + else + hc->host = format (0, "%U:%d", format_ip6_address, &sep->ip.ip6, + clib_net_to_host_u16 (sep->port)); + HTTP_DBG (1, "hc ho_index %x", hc_index); if ((error = vnet_connect (cargs))) @@ -1515,6 +1617,8 @@ http_start_listen (u32 app_listener_index, transport_endpoint_cfg_t *tep) lhc->c_s_index = app_listener_index; lhc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; + lhc->is_server = 1; + if (vec_len (app->name)) lhc->app_name = vec_dup (app->name); else @@ -1762,6 +1866,7 @@ static clib_error_t * http_transport_init (vlib_main_t *vm) { http_main_t *hm = &http_main; + int i; transport_register_protocol (TRANSPORT_PROTO_HTTP, &http_proto, FIB_PROTOCOL_IP4, ~0); @@ -1773,7 +1878,26 @@ http_transport_init (vlib_main_t *vm) hm->first_seg_size = 32 << 20; hm->fifo_size = 512 << 10; - return 0; + /* Setup u16 to http_status_code_t map */ + /* Unrecognized status code is equivalent to the x00 status */ + vec_validate (hm->sc_by_u16, 599); + for (i = 100; i < 200; i++) + hm->sc_by_u16[i] = HTTP_STATUS_CONTINUE; + for (i = 200; i < 300; i++) + hm->sc_by_u16[i] = HTTP_STATUS_OK; + for (i = 300; i < 400; i++) + hm->sc_by_u16[i] = HTTP_STATUS_MULTIPLE_CHOICES; + for (i = 400; i < 500; i++) + hm->sc_by_u16[i] = HTTP_STATUS_BAD_REQUEST; + for (i = 500; i < 600; i++) + hm->sc_by_u16[i] = HTTP_STATUS_INTERNAL_ERROR; + + /* Registered status codes */ +#define _(c, s, str) hm->sc_by_u16[c] = HTTP_STATUS_##s; + foreach_http_status_code +#undef _ + + return 0; } VLIB_INIT_FUNCTION (http_transport_init); diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 07d5472346e..e3267dfc3b2 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -368,7 +368,6 @@ typedef struct http_msg_ http_req_method_t method_type; http_status_code_t code; }; - http_content_type_t content_type; http_msg_data_t data; } http_msg_t; @@ -388,6 +387,8 @@ typedef struct http_tc_ http_conn_state_t state; u32 timer_handle; u8 *app_name; + u8 *host; + u8 is_server; /* * Current request @@ -408,6 +409,7 @@ typedef struct http_tc_ u32 headers_len; u32 body_offset; u32 body_len; + u16 status_code; } http_conn_t; typedef struct http_worker_ @@ -423,6 +425,7 @@ typedef struct http_main_ clib_timebase_t timebase; + u16 *sc_by_u16; /* * Runtime config */ diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst index 2f7a58ef9bf..273812735f7 100644 --- a/src/plugins/http/http_plugin.rst +++ b/src/plugins/http/http_plugin.rst @@ -191,10 +191,10 @@ Following example shows how to create headers section: 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_add_header (resp_headers, http_header_name_token (HTTP_HEADER_LOCATION), (const char *) redirect, vec_len (redirect)); - headers_buf = http_serialize_headers (hs->resp_headers); + headers_buf = http_serialize_headers (resp_headers); The example below show how to create and send response HTTP message metadata: @@ -236,7 +236,7 @@ Finally application sends response body: 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); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); Example above shows how to send body data by copy, alternatively you could pass it as pointer: @@ -249,3 +249,199 @@ Example above shows how to send body data by copy, alternatively you could pass ASSERT (rv == sizeof (data)); In this case you need to free data when you receive next request or when session is closed. + + +Client application +^^^^^^^^^^^^^^^^^^ + +Client application opens connection with vnet URI where transport protocol is set to ``http``. + +Sending data +"""""""""""""" + +HTTP request is sent when connection is successfully established in ``session_connected_callback``. + +When client application sends message to HTTP layer it starts with message metadata, followed by request target, optional headers and body (if any) buffers. + +Application should set following items: + +* HTTP method +* target form, offset and length +* header section offset and length +* body offset and length + +Application could pass headers 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: Host, User-Agent + + +The example below 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 *req_headers = 0; + u8 *headers_buf = 0; + http_add_header (req_headers, + http_header_name_token (HTTP_HEADER_ACCEPT), + http_content_type_token (HTTP_CONTENT_TEXT_HTML)); + headers_buf = http_serialize_headers (req_headers); + vec_free (hs->req_headers); + +Following example shows how to set message metadata: + +.. code-block:: C + + http_msg_t msg; + msg.type = HTTP_MSG_REQUEST; + msg.method_type = HTTP_REQ_GET; + msg.data.headers_offset = 0; + /* request target */ + msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; + msg.data.target_path_offset = 0; + msg.data.target_path_len = vec_len (target); + /* custom headers */ + msg.data.headers_offset = msg.data.target_path_len; + msg.data.headers_len = vec_len (headers_buf); + /* no request body because we are doing GET request */ + msg.data.body_len = 0; + /* data type and total length */ + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = msg.data.target_path_len + msg.data.headers_len + msg.data.body_len; + +Finally application sends everything to HTTP layer: + +.. code-block:: C + + svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, /* message metadata */ + { target, vec_len (target) }, /* request target */ + { headers_buf, vec_len (headers_buf) } }; /* serialized headers */ + rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */); + vec_free (headers_buf); + if (rv < 0 || rv != sizeof (msg) + msg.data.len) + { + clib_warning ("failed app enqueue"); + return -1; + } + if (svm_fifo_set_event (as->tx_fifo)) + session_program_tx_io_evt (as->handle, SESSION_IO_EVT_TX); + +Receiving data +"""""""""""""" + +HTTP plugin sends message header with metadata for parsing, in form of offset and length, followed by all data bytes as received from transport. + +Application will get pre-parsed following items: + +* status code +* header section offset and length +* body offset and length + +The example below reads HTTP message header in ``builtin_app_rx_callback``, which is first step application should do: + +.. code-block:: C + + #include <http/http.h> + http_msg_t msg; + rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg); + ASSERT (rv == sizeof (msg)); + +As next step application might validate message type and status code: + +.. code-block:: C + + if (msg.type != HTTP_MSG_REPLY) + { + /* your error handling */ + } + if (msg.code != HTTP_STATUS_OK) + { + /* your error handling */ + /* of course you can continue with steps bellow */ + /* you might be interested in some headers or body content (if any) */ + } + +Headers are parsed using a generic algorithm, independent of the individual header names. +When header is repeated, its combined value consists of all values separated by comma, concatenated in order as received. +Following example shows how to parse headers: + +.. code-block:: C + + #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); + rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset, + msg.data.headers_len, headers); + ASSERT (rv == msg.data.headers_len); + if (http_parse_headers (headers, &ht)) + { + /* your error handling */ + } + /* get Content-Type header */ + const char *content_type = http_get_header (ht, http_header_name_str (HTTP_HEADER_CONTENT_TYPE)); + if (content_type) + { + /* do something interesting */ + } + http_free_header_table (ht); + vec_free (headers); + } + +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``. +We will add following members to our session context structure: + +.. code-block:: C + + typedef struct + { + /* ... */ + u32 to_recv; + u8 *resp_body; + } session_ctx_t; + +First we prepare vector for response body, do it only once when you are reading metadata: + +.. code-block:: C + + /* drop everything up to body */ + svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.body_offset); + ctx->to_recv = msg.data.body_len; + /* prepare vector for response body */ + vec_validate (ctx->resp_body, msg.data.body_len - 1); + vec_reset_length (ctx->resp_body); + +Now we can start reading body content, following block of code could be executed multiple times: + +.. code-block:: C + + /* dequeue */ + u32 max_deq = svm_fifo_max_dequeue (ts->rx_fifo); + u32 n_deq = clib_min (to_recv, max_deq); + /* current offset */ + u32 curr = vec_len (ctx->resp_body); + rv = svm_fifo_dequeue (ts->rx_fifo, n_deq, ctx->resp_body + curr); + if (rv < 0 || rv != n_deq) + { + /* your error handling */ + } + /* update length of the vector */ + vec_set_len (ctx->resp_body, curr + n_deq); + /* update number of remaining bytes to receive */ + ASSERT (to_recv >= rv); + ctx->to_recv -= rv; + /* check if all data received */ + if (ctx->to_recv == 0) + { + /* we are done */ + /* close the session if you don't want to send another request */ + /* and update state machine... */ + } diff --git a/src/plugins/http/http_status_codes.h b/src/plugins/http/http_status_codes.h new file mode 100644 index 00000000000..14b6b7db42d --- /dev/null +++ b/src/plugins/http/http_status_codes.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2024 Cisco Systems, Inc. + */ + +#ifndef SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_ +#define SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_ + +#include <http/http.h> + +const char *http_status_code_str[] = { +#define _(c, s, str) str, + foreach_http_status_code +#undef _ +}; + +static inline u8 * +format_http_status_code (u8 *s, va_list *va) +{ + http_status_code_t status_code = va_arg (*va, http_status_code_t); + if (status_code < HTTP_N_STATUS) + s = format (s, "%s", http_status_code_str[status_code]); + else + s = format (s, "invalid status code %d", status_code); + return s; +} + +#endif /* SRC_PLUGINS_HTTP_HTTP_STATUS_CODES_H_ */ diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index d987a3eb7fb..b47fc9c3f0b 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -158,7 +158,7 @@ start_send_data (hss_session_t *hs, http_status_code_t status) done: if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); } __clib_export void @@ -618,7 +618,7 @@ hss_ts_tx_callback (session_t *ts) } if (svm_fifo_set_event (ts->tx_fifo)) - session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX); + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); return 0; } |