summaryrefslogtreecommitdiffstats
path: root/src/plugins/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/http')
-rw-r--r--src/plugins/http/http.c354
-rw-r--r--src/plugins/http/http.h5
-rw-r--r--src/plugins/http/http_plugin.rst202
-rw-r--r--src/plugins/http/http_status_codes.h27
4 files changed, 469 insertions, 119 deletions
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_ */