diff --git a/include/zephyr/net/http/server.h b/include/zephyr/net/http/server.h index ff67daeeb11..4cb40e52eb6 100644 --- a/include/zephyr/net/http/server.h +++ b/include/zephyr/net/http/server.h @@ -49,6 +49,14 @@ extern "C" { #define HTTP_SERVER_MAX_HEADER_LEN 0 #endif +#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) +#define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE +#define HTTP_SERVER_CAPTURE_HEADER_COUNT CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT +#else +#define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE 0 +#define HTTP_SERVER_CAPTURE_HEADER_COUNT 0 +#endif + #define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /** @endcond */ @@ -162,20 +170,36 @@ enum http_data_status { HTTP_SERVER_DATA_FINAL = 1, }; +/** @brief Status of captured request headers */ +enum http_header_status { + HTTP_HEADER_STATUS_OK, /**< All available headers were successfully captured. */ + HTTP_HEADER_STATUS_DROPPED, /**< One or more headers were dropped due to lack of space. */ + HTTP_HEADER_STATUS_NONE, /**< No header status is available. */ +}; + /** @brief HTTP header representation */ struct http_header { const char *name; /**< Pointer to header name NULL-terminated string. */ const char *value; /**< Pointer to header value NULL-terminated string. */ }; +/** @brief HTTP request context */ +struct http_request_ctx { + uint8_t *data; /**< HTTP request data */ + size_t data_len; /**< Length of HTTP request data */ + struct http_header *headers; /**< Array of HTTP request headers */ + size_t header_count; /**< Array length of HTTP request headers */ + enum http_header_status headers_status; /**< Status of HTTP request headers */ +}; + /** @brief HTTP response context */ struct http_response_ctx { - enum http_status status; /** HTTP status code to include in response */ - const struct http_header *headers; /** Array of HTTP headers */ - size_t header_count; /** Length of headers array */ - const uint8_t *body; /** Pointer to body data */ - size_t body_len; /** Length of body data */ - bool final_chunk; /** Flag set to true when the application has no more data to send */ + enum http_status status; /**< HTTP status code to include in response */ + const struct http_header *headers; /**< Array of HTTP headers */ + size_t header_count; /**< Length of headers array */ + const uint8_t *body; /**< Pointer to body data */ + size_t body_len; /**< Length of body data */ + bool final_chunk; /**< Flag set to true when the application has no more data to send */ }; /** @@ -185,20 +209,16 @@ struct http_response_ctx { * * @param client HTTP context information for this client connection. * @param status HTTP data status, indicate whether more data is expected or not. - * @param data_buffer Data received. - * @param data_len Amount of data received. - * @param response_ctx + * @param request_ctx Request context structure containing HTTP request data that was received. + * @param response_ctx Response context structure for application to populate with response data. * @param user_data User specified data. * - * @return >0 amount of data to be sent to client, let server to call this - * function again when new data is received. - * 0 nothing to sent to client, close the connection + * @return 0 success, server can send any response data provided in the response_ctx. * <0 error, close the connection. */ typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client, enum http_data_status status, - uint8_t *data_buffer, - size_t data_len, + const struct http_request_ctx *request_ctx, struct http_response_ctx *response_ctx, void *user_data); @@ -341,20 +361,14 @@ struct http2_frame { uint8_t padding_len; /**< Frame padding length. */ }; -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) -/** @brief Status of captured headers */ -enum http_header_status { - HTTP_HEADER_STATUS_OK, /**< All available headers were successfully captured. */ - HTTP_HEADER_STATUS_DROPPED, /**< One or more headers were dropped due to lack of space. */ -}; - +/** @cond INTERNAL_HIDDEN */ /** @brief Context for capturing HTTP headers */ struct http_header_capture_ctx { /** Buffer for HTTP headers captured for application use */ - unsigned char buffer[CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE]; + unsigned char buffer[HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE]; /** Descriptor of each captured HTTP header */ - struct http_header headers[CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT]; + struct http_header headers[HTTP_SERVER_CAPTURE_HEADER_COUNT]; /** Status of captured headers */ enum http_header_status status; @@ -365,15 +379,18 @@ struct http_header_capture_ctx { /** Current position in buffer */ size_t cursor; + /** The HTTP2 stream associated with the current headers */ + struct http2_stream_ctx *current_stream; + /** The next HTTP header value should be stored */ bool store_next_value; }; +/** @endcond */ /** @brief HTTP header name representation */ struct http_header_name { const char *name; /**< Pointer to header name NULL-terminated string. */ }; -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ /** * @brief Representation of an HTTP client connected to the server. @@ -418,10 +435,8 @@ struct http_client_ctx { /** HTTP/1 parser context. */ struct http_parser parser; -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) /** Header capture context */ struct http_header_capture_ctx header_capture_ctx; -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ /** Request URL. */ unsigned char url_buffer[HTTP_SERVER_MAX_URL_LENGTH]; @@ -478,7 +493,6 @@ struct http_client_ctx { bool expect_continuation : 1; }; -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) /** * @brief Register an HTTP request header to be captured by the server * @@ -493,7 +507,6 @@ struct http_client_ctx { static const STRUCT_SECTION_ITERABLE(http_header_name, _id) = { \ .name = _id##_str, \ } -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ /** @brief Start the HTTP2 server. * diff --git a/samples/net/prometheus/src/main.c b/samples/net/prometheus/src/main.c index 8e947de9029..5497da2e078 100644 --- a/samples/net/prometheus/src/main.c +++ b/samples/net/prometheus/src/main.c @@ -43,8 +43,8 @@ HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_htt 10, NULL); static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, - void *user_data) + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { int ret; static uint8_t prom_buffer[256]; diff --git a/samples/net/prometheus/src/stats.c b/samples/net/prometheus/src/stats.c index 9a5a444d4bf..ffc47a952da 100644 --- a/samples/net/prometheus/src/stats.c +++ b/samples/net/prometheus/src/stats.c @@ -33,8 +33,8 @@ static struct prometheus_collector *stats_collector; static struct prometheus_collector_walk_context walk_ctx; static int stats_handler(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, - void *user_data) + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { int ret; static uint8_t prom_buffer[1024]; diff --git a/samples/net/sockets/http_server/src/main.c b/samples/net/sockets/http_server/src/main.c index 0d7c07ff1b2..23ab975cd6c 100644 --- a/samples/net/sockets/http_server/src/main.c +++ b/samples/net/sockets/http_server/src/main.c @@ -68,8 +68,8 @@ static struct http_resource_detail_static main_js_gz_resource_detail = { }; static int echo_handler(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, - void *user_data) + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { #define MAX_TEMP_PRINT_LEN 32 static char print_str[MAX_TEMP_PRINT_LEN]; @@ -84,11 +84,11 @@ static int echo_handler(struct http_client_ctx *client, enum http_data_status st __ASSERT_NO_MSG(buffer != NULL); - processed += len; + processed += request_ctx->data_len; - snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", - http_method_str(method), len); - LOG_HEXDUMP_DBG(buffer, len, print_str); + snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", http_method_str(method), + request_ctx->data_len); + LOG_HEXDUMP_DBG(request_ctx->data, request_ctx->data_len, print_str); if (status == HTTP_SERVER_DATA_FINAL) { LOG_DBG("All data received (%zd bytes).", processed); @@ -96,8 +96,8 @@ static int echo_handler(struct http_client_ctx *client, enum http_data_status st } /* Echo data back to client */ - response_ctx->body = buffer; - response_ctx->body_len = len; + response_ctx->body = request_ctx->data; + response_ctx->body_len = request_ctx->data_len; response_ctx->final_chunk = (status == HTTP_SERVER_DATA_FINAL); return 0; @@ -113,8 +113,8 @@ static struct http_resource_detail_dynamic echo_resource_detail = { }; static int uptime_handler(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, - void *user_data) + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { int ret; static uint8_t uptime_buf[sizeof(STRINGIFY(INT64_MAX))]; @@ -172,20 +172,20 @@ static void parse_led_post(uint8_t *buf, size_t len) } static int led_handler(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, - void *user_data) + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { static uint8_t post_payload_buf[32]; static size_t cursor; - LOG_DBG("LED handler status %d, size %zu", status, len); + LOG_DBG("LED handler status %d, size %zu", status, request_ctx->data_len); if (status == HTTP_SERVER_DATA_ABORTED) { cursor = 0; return 0; } - if (len + cursor > sizeof(post_payload_buf)) { + if (request_ctx->data_len + cursor > sizeof(post_payload_buf)) { cursor = 0; return -ENOMEM; } @@ -194,8 +194,8 @@ static int led_handler(struct http_client_ctx *client, enum http_data_status sta * chunks (e.g. if the header size was such that the whole HTTP request exceeds the size of * the client buffer). */ - memcpy(post_payload_buf + cursor, buffer, len); - cursor += len; + memcpy(post_payload_buf + cursor, request_ctx->data, request_ctx->data_len); + cursor += request_ctx->data_len; if (status == HTTP_SERVER_DATA_FINAL) { parse_led_post(post_payload_buf, cursor); diff --git a/subsys/net/lib/http/headers/server_internal.h b/subsys/net/lib/http/headers/server_internal.h index 3fd041a4ad3..5048a8543fb 100644 --- a/subsys/net/lib/http/headers/server_internal.h +++ b/subsys/net/lib/http/headers/server_internal.h @@ -51,4 +51,7 @@ int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffe size_t buflen); const char *get_frame_type_name(enum http2_frame_type type); +void populate_request_ctx(struct http_request_ctx *req_ctx, uint8_t *data, size_t len, + struct http_header_capture_ctx *header_ctx); + #endif /* HTTP_SERVER_INTERNAL_H_ */ diff --git a/subsys/net/lib/http/http_server_core.c b/subsys/net/lib/http/http_server_core.c index f090254ff44..61fae3b5862 100644 --- a/subsys/net/lib/http/http_server_core.c +++ b/subsys/net/lib/http/http_server_core.c @@ -305,6 +305,7 @@ static void client_release_resources(struct http_client_ctx *client) { struct http_resource_detail *detail; struct http_resource_detail_dynamic *dynamic_detail; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; HTTP_SERVICE_FOREACH(service) { @@ -331,8 +332,10 @@ static void client_release_resources(struct http_client_ctx *client) continue; } - dynamic_detail->cb(client, HTTP_SERVER_DATA_ABORTED, NULL, 0, &response_ctx, - dynamic_detail->user_data); + populate_request_ctx(&request_ctx, NULL, 0, NULL); + + dynamic_detail->cb(client, HTTP_SERVER_DATA_ABORTED, &request_ctx, + &response_ctx, dynamic_detail->user_data); } } } @@ -421,11 +424,11 @@ static int handle_http_preface(struct http_client_ctx *client) return -EAGAIN; } -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - client->header_capture_ctx.count = 0; - client->header_capture_ctx.cursor = 0; - client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK; -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + client->header_capture_ctx.count = 0; + client->header_capture_ctx.cursor = 0; + client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK; + } if (strncmp(client->cursor, HTTP2_PREFACE, sizeof(HTTP2_PREFACE) - 1) != 0) { return enter_http1_request(client); @@ -829,6 +832,23 @@ bool http_response_is_provided(struct http_response_ctx *rsp) return false; } +void populate_request_ctx(struct http_request_ctx *req_ctx, uint8_t *data, size_t len, + struct http_header_capture_ctx *header_ctx) +{ + req_ctx->data = data; + req_ctx->data_len = len; + + if (NULL == header_ctx || header_ctx->status == HTTP_HEADER_STATUS_NONE) { + req_ctx->headers = NULL; + req_ctx->header_count = 0; + req_ctx->headers_status = HTTP_HEADER_STATUS_NONE; + } else { + req_ctx->headers = header_ctx->headers; + req_ctx->header_count = header_ctx->count; + req_ctx->headers_status = header_ctx->status; + } +} + int http_server_start(void) { if (server_running) { diff --git a/subsys/net/lib/http/http_server_http1.c b/subsys/net/lib/http/http_server_http1.c index 6ac3e3c1329..5b41c6249e1 100644 --- a/subsys/net/lib/http/http_server_http1.c +++ b/subsys/net/lib/http/http_server_http1.c @@ -260,6 +260,7 @@ static int dynamic_get_req(struct http_resource_detail_dynamic *dynamic_detail, int ret, len; char *ptr; enum http_data_status status; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; /* Start of GET params */ @@ -269,8 +270,9 @@ static int dynamic_get_req(struct http_resource_detail_dynamic *dynamic_detail, do { memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx); - ret = dynamic_detail->cb(client, status, ptr, len, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; @@ -302,6 +304,7 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail, int ret; char *ptr = client->cursor; enum http_data_status status; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; if (ptr == NULL) { @@ -315,13 +318,19 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail, } memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, client->data_len, &client->header_capture_ctx); - ret = dynamic_detail->cb(client, status, ptr, client->data_len, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } + /* Only send request headers in first callback to application. This is not strictly + * necessary for http1, but is done for consistency with the http2 behaviour. + */ + client->header_capture_ctx.status = HTTP_HEADER_STATUS_NONE; + /* For POST the application might not send a response until all data has been received. * Don't send a default response until the application has had a chance to respond. */ @@ -335,8 +344,9 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail, /* Once all data is transferred to application, repeat cb until response is complete */ while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) { memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, 0, &client->header_capture_ctx); - ret = dynamic_detail->cb(client, status, ptr, 0, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; @@ -544,7 +554,6 @@ not_supported: return 0; } -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) static void check_user_request_headers(struct http_header_capture_ctx *ctx, const char *buf) { size_t header_len; @@ -579,7 +588,6 @@ static void check_user_request_headers(struct http_header_capture_ctx *ctx, cons } } } -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ static int on_header_field(struct http_parser *parser, const char *at, size_t length) @@ -602,9 +610,10 @@ static int on_header_field(struct http_parser *parser, const char *at, /* This means that the header field is fully parsed, * and we can use it directly. */ -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - check_user_request_headers(&ctx->header_capture_ctx, ctx->header_buffer); -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + check_user_request_headers(&ctx->header_capture_ctx, + ctx->header_buffer); + } if (strcasecmp(ctx->header_buffer, "Upgrade") == 0) { ctx->has_upgrade_header = true; @@ -621,7 +630,6 @@ static int on_header_field(struct http_parser *parser, const char *at, return 0; } -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) static void populate_user_request_header(struct http_header_capture_ctx *ctx, const char *buf) { char *dest; @@ -650,7 +658,6 @@ static void populate_user_request_header(struct http_header_capture_ctx *ctx, co ctx->headers[ctx->count].value = dest; ctx->count++; } -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ static int on_header_value(struct http_parser *parser, const char *at, size_t length) @@ -664,21 +671,22 @@ static int on_header_value(struct http_parser *parser, LOG_DBG("Header %s too long (by %zu bytes)", "value", offset + length - sizeof(ctx->header_buffer) - 1U); ctx->header_buffer[0] = '\0'; -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - if (ctx->header_capture_ctx.store_next_value) { + + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) && + ctx->header_capture_ctx.store_next_value) { ctx->header_capture_ctx.store_next_value = false; ctx->header_capture_ctx.status = HTTP_HEADER_STATUS_DROPPED; } -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ } else { memcpy(ctx->header_buffer + offset, at, length); offset += length; ctx->header_buffer[offset] = '\0'; if (parser->state == s_header_almost_done) { -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - populate_user_request_header(&ctx->header_capture_ctx, ctx->header_buffer); -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + populate_user_request_header(&ctx->header_capture_ctx, + ctx->header_buffer); + } if (ctx->has_upgrade_header) { if (strcasecmp(ctx->header_buffer, "h2c") == 0) { @@ -777,9 +785,9 @@ int enter_http1_request(struct http_client_ctx *client) client->parser_state = HTTP1_INIT_HEADER_STATE; client->http1_headers_sent = false; -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - client->header_capture_ctx.store_next_value = false; -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + client->header_capture_ctx.store_next_value = false; + } memset(client->header_buffer, 0, sizeof(client->header_buffer)); memset(client->url_buffer, 0, sizeof(client->url_buffer)); diff --git a/subsys/net/lib/http/http_server_http2.c b/subsys/net/lib/http/http_server_http2.c index cd3f4ef6fd5..6ced44d0365 100644 --- a/subsys/net/lib/http/http_server_http2.c +++ b/subsys/net/lib/http/http_server_http2.c @@ -563,6 +563,7 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai char *ptr; struct http2_frame *frame = &client->current_frame; enum http_data_status status; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; if (client->current_stream == NULL) { @@ -576,8 +577,9 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai do { memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx); - ret = dynamic_detail->cb(client, status, ptr, len, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; @@ -607,14 +609,17 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai } static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_detail, - struct http_client_ctx *client) + struct http_client_ctx *client, bool headers_only) { int ret = 0; char *ptr = client->cursor; size_t data_len; enum http_data_status status; struct http2_frame *frame = &client->current_frame; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; + struct http_header_capture_ctx *request_headers_ctx = + headers_only ? &client->header_capture_ctx : NULL; if (dynamic_detail == NULL) { return -ENOENT; @@ -624,20 +629,26 @@ static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_deta return -ENOENT; } - data_len = MIN(frame->length, client->data_len); - frame->length -= data_len; - client->cursor += data_len; - client->data_len -= data_len; + if (headers_only) { + data_len = 0; + } else { + data_len = MIN(frame->length, client->data_len); + frame->length -= data_len; + client->cursor += data_len; + client->data_len -= data_len; + } - if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) { + if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM) && + !headers_only) { status = HTTP_SERVER_DATA_FINAL; } else { status = HTTP_SERVER_DATA_MORE; } memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, data_len, request_headers_ctx); - ret = dynamic_detail->cb(client, status, ptr, data_len, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; @@ -656,8 +667,9 @@ static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_deta /* Once all data is transferred to application, repeat cb until response is complete */ while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) { memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, ptr, 0, request_headers_ctx); - ret = dynamic_detail->cb(client, status, ptr, 0, &response_ctx, + ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; @@ -736,6 +748,17 @@ static int handle_http2_dynamic_resource( if (user_method & BIT(HTTP_POST)) { client->current_stream->current_detail = (struct http_resource_detail *)dynamic_detail; + + /* If there are any header fields to pass to the application, call the + * dynamic handler now with the header data so that this may be cleared to + * re-use for any other concurrent streams + */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + ret = dynamic_post_req_v2(dynamic_detail, client, true); + if (ret < 0) { + return ret; + } + } break; } @@ -840,6 +863,14 @@ static int enter_http_frame_headers_state(struct http_client_ctx *client) client->expect_continuation = false; } + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + /* Reset header capture state for new headers frame */ + client->header_capture_ctx.count = 0; + client->header_capture_ctx.cursor = 0; + client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK; + client->header_capture_ctx.current_stream = stream; + } + client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; return 0; @@ -1022,8 +1053,8 @@ int handle_http1_to_http2_upgrade(struct http_client_ctx *client) if (client->method == HTTP_POST) { ret = dynamic_post_req_v2( - (struct http_resource_detail_dynamic *)detail, - client); + (struct http_resource_detail_dynamic *)detail, client, + false); if (ret < 0) { goto error; } @@ -1131,7 +1162,7 @@ int handle_http_frame_data(struct http_client_ctx *client) ret = dynamic_post_req_v2( (struct http_resource_detail_dynamic *)client->current_stream->current_detail, - client); + client, false); if (ret < 0 && ret == -ENOENT) { ret = send_http2_404(client, frame); } @@ -1176,7 +1207,6 @@ int handle_http_frame_data(struct http_client_ctx *client) return 0; } -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx, struct http_hpack_header_buf *hdr_buf) { @@ -1223,14 +1253,13 @@ static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx } } } -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ static int process_header(struct http_client_ctx *client, struct http_hpack_header_buf *header) { -#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) - check_user_request_headers_http2(&client->header_capture_ctx, header); -#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ + if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { + check_user_request_headers_http2(&client->header_capture_ctx, header); + } if (header->name_len == (sizeof(":method") - 1) && memcmp(header->name, ":method", header->name_len) == 0) { @@ -1344,6 +1373,7 @@ static int handle_incomplete_http_header(struct http_client_ctx *client) static int handle_http_frame_headers_end_stream(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; + struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; int ret = 0; @@ -1361,9 +1391,10 @@ static int handle_http_frame_headers_end_stream(struct http_client_ctx *client) client->current_stream->current_detail; memset(&response_ctx, 0, sizeof(response_ctx)); + populate_request_ctx(&request_ctx, NULL, 0, NULL); - ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, NULL, 0, &response_ctx, - dynamic_detail->user_data); + ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, &request_ctx, + &response_ctx, dynamic_detail->user_data); if (ret < 0) { dynamic_detail->holder = NULL; goto out; diff --git a/tests/net/lib/http_server/core/src/main.c b/tests/net/lib/http_server/core/src/main.c index c5344166d64..b9b74a16a26 100644 --- a/tests/net/lib/http_server/core/src/main.c +++ b/tests/net/lib/http_server/core/src/main.c @@ -44,7 +44,18 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE, "long_payload should be longer than client buffer to test payload being sent to " "application across multiple calls to dynamic resource callback"); -/* Individual HTTP2 frames, used to compose requests. */ +/* Individual HTTP2 frames, used to compose requests. + * + * Headers and data frames can be composed based on a "real" request by copying the frame from a + * wireshark capture (Copy --> ...as a hex stream) and formatting into a C array initializer using + * xxd: + * + * echo "" | xxd -r -p | xxd -i + * + * For example: + * $ echo "01234567" | xxd -r -p | xxd -i + * 0x01, 0x23, 0x45, 0x67 + */ #define TEST_HTTP2_MAGIC \ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, \ 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a @@ -106,6 +117,22 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE, 0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b, 0x40, 0x88, 0x49, \ 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, 0x82, 0x88, 0x49, 0x50, 0x98, 0xbb, \ 0x8e, 0x8b, 0x4a, 0x2f +#define TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE_WITH_TESTHEADER_STREAM_1 \ + 0x00, 0x00, 0x4b, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ + 0x83, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \ + 0xb0, 0xbf, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, \ + 0x7a, 0x88, 0x25, 0xb6, 0x50, 0xc3, 0xab, 0xbc, 0x15, 0xc1, 0x53, 0x03, \ + 0x2a, 0x2f, 0x2a, 0x40, 0x88, 0x49, 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, \ + 0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b, 0x5f, 0x8b, 0x1d, \ + 0x75, 0xd0, 0x62, 0x0d, 0x26, 0x3d, 0x4c, 0x74, 0x41, 0xea, 0x0f, 0x0d, \ + 0x02, 0x31, 0x30 +#define TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE2_NO_TESTHEADER_STREAM_2 \ + 0x00, 0x00, 0x39, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_2, \ + 0x83, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \ + 0xb0, 0xa2, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, \ + 0x7a, 0x88, 0x25, 0xb6, 0x50, 0xc3, 0xab, 0xbc, 0x15, 0xc1, 0x53, 0x03, \ + 0x2a, 0x2f, 0x2a, 0x5f, 0x8b, 0x1d, 0x75, 0xd0, 0x62, 0x0d, 0x26, 0x3d, \ + 0x4c, 0x74, 0x41, 0xea, 0x0f, 0x0d, 0x02, 0x31, 0x30 #define TEST_HTTP2_HEADERS_GET_RESPONSE_HEADERS_STREAM_1 \ 0x00, 0x00, 0x28, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x82, 0x04, 0x8c, 0x62, 0xc2, 0xa2, 0xb3, 0xd4, 0x82, 0xc5, 0x39, 0x47, \ @@ -163,6 +190,12 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, \ 0x20, 0x50, 0x4f, 0x53, 0x54 +#define TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_1 \ + 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ + 0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x31, 0x7d +#define TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_2 \ + 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, TEST_STREAM_ID_2, \ + 0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x31, 0x7d #define TEST_HTTP2_TRAILING_HEADER_STREAM_1 \ 0x00, 0x00, 0x0c, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x40, 0x84, 0x92, 0xda, 0x69, 0xf5, 0x85, 0x9c, 0xa3, 0x90, 0xb6, 0x7f @@ -190,8 +223,9 @@ HTTP_RESOURCE_DEFINE(static_resource, test_http_service, "/", static uint8_t dynamic_payload[32]; static size_t dynamic_payload_len = sizeof(dynamic_payload); -static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status, uint8_t *buffer, - size_t len, struct http_response_ctx *response_ctx, void *user_data) +static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status, + const struct http_request_ctx *request_ctx, + struct http_response_ctx *response_ctx, void *user_data) { static size_t offset; @@ -207,13 +241,13 @@ static int dynamic_cb(struct http_client_ctx *client, enum http_data_status stat response_ctx->final_chunk = true; break; case HTTP_POST: - if (len + offset > sizeof(dynamic_payload)) { + if (request_ctx->data_len + offset > sizeof(dynamic_payload)) { return -ENOMEM; } - if (len > 0) { - memcpy(dynamic_payload + offset, buffer, len); - offset += len; + if (request_ctx->data_len > 0) { + memcpy(dynamic_payload + offset, request_ctx->data, request_ctx->data_len); + offset += request_ctx->data_len; } if (status == HTTP_SERVER_DATA_FINAL) { @@ -244,36 +278,43 @@ struct http_resource_detail_dynamic dynamic_detail = { HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic", &dynamic_detail); -static struct http_header_capture_ctx header_capture_ctx_clone; +struct test_headers_clone { + uint8_t buffer[CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE]; + struct http_header headers[CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT]; + size_t count; + enum http_header_status status; +}; static int dynamic_request_headers_cb(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, + const struct http_request_ctx *request_ctx, struct http_response_ctx *response_ctx, void *user_data) { ptrdiff_t offset; struct http_header *hdrs_src; struct http_header *hdrs_dst; + struct test_headers_clone *clone = (struct test_headers_clone *)user_data; - if (status == HTTP_SERVER_DATA_FINAL) { + if (request_ctx->header_count != 0) { /* Copy the captured header info to static buffer for later assertions in testcase. * Don't assume that the buffer inside client context remains valid after return - * from the callback - this is currently the case at the time of writing but could - * change. Also need to update pointers within structure with an offset to point at - * new buffer. + * from the callback. Also need to update pointers within structure with an offset + * to point at new buffer. */ - memcpy(&header_capture_ctx_clone, &client->header_capture_ctx, - sizeof(header_capture_ctx_clone)); + memcpy(clone->buffer, &client->header_capture_ctx, sizeof(clone->buffer)); - hdrs_src = client->header_capture_ctx.headers; - hdrs_dst = header_capture_ctx_clone.headers; - offset = header_capture_ctx_clone.buffer - client->header_capture_ctx.buffer; + clone->count = request_ctx->header_count; + clone->status = request_ctx->headers_status; - for (int i = 0; i < CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT; i++) { + hdrs_src = request_ctx->headers; + hdrs_dst = clone->headers; + offset = clone->buffer - client->header_capture_ctx.buffer; + + for (int i = 0; i < request_ctx->header_count; i++) { if (hdrs_src[i].name != NULL) { hdrs_dst[i].name = hdrs_src[i].name + offset; } - if (hdrs_dst[i].value != NULL) { + if (hdrs_src[i].value != NULL) { hdrs_dst[i].value = hdrs_src[i].value + offset; } } @@ -282,6 +323,10 @@ static int dynamic_request_headers_cb(struct http_client_ctx *client, enum http_ return 0; } +/* Define two resources for testing header capture, so that we can check concurrent streams */ +static struct test_headers_clone request_headers_clone; +static struct test_headers_clone request_headers_clone2; + struct http_resource_detail_dynamic dynamic_request_headers_detail = { .common = { .type = HTTP_RESOURCE_TYPE_DYNAMIC, @@ -289,12 +334,25 @@ struct http_resource_detail_dynamic dynamic_request_headers_detail = { .content_type = "text/plain", }, .cb = dynamic_request_headers_cb, - .user_data = NULL + .user_data = &request_headers_clone, +}; + +struct http_resource_detail_dynamic dynamic_request_headers_detail2 = { + .common = { + .type = HTTP_RESOURCE_TYPE_DYNAMIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET) | BIT(HTTP_POST), + .content_type = "text/plain", + }, + .cb = dynamic_request_headers_cb, + .user_data = &request_headers_clone2, }; HTTP_RESOURCE_DEFINE(dynamic_request_headers_resource, test_http_service, "/header_capture", &dynamic_request_headers_detail); +HTTP_RESOURCE_DEFINE(dynamic_request_headers_resource2, test_http_service, "/header_capture2", + &dynamic_request_headers_detail2); + HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_user_agent, "User-Agent"); HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header, "Test-Header"); HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header2, "Test-Header2"); @@ -326,7 +384,7 @@ static uint8_t dynamic_response_headers_variant; static uint8_t dynamic_response_headers_buffer[sizeof(long_payload)]; static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http_data_status status, - uint8_t *buffer, size_t len, + const struct http_request_ctx *request_ctx, struct http_response_ctx *response_ctx, void *user_data) { static bool request_continuation; @@ -340,6 +398,14 @@ static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http {.name = "Content-Type", .value = "application/json"}, }; + if (status != HTTP_SERVER_DATA_FINAL && + dynamic_response_headers_variant != DYNAMIC_RESPONSE_HEADERS_VARIANT_BODY_LONG) { + /* Long body variant is the only one which needs to take some action before final + * data has been received from server + */ + return 0; + } + switch (dynamic_response_headers_variant) { case DYNAMIC_RESPONSE_HEADERS_VARIANT_NONE: break; @@ -400,10 +466,12 @@ static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http } } else if (client->method == HTTP_POST) { /* Copy POST payload into buffer for later comparison */ - zassert(offset + len <= sizeof(dynamic_response_headers_buffer), + zassert(offset + request_ctx->data_len <= + sizeof(dynamic_response_headers_buffer), "POST data too long for buffer"); - memcpy(dynamic_response_headers_buffer + offset, buffer, len); - offset += len; + memcpy(dynamic_response_headers_buffer + offset, request_ctx->data, + request_ctx->data_len); + offset += request_ctx->data_len; if (status == HTTP_SERVER_DATA_FINAL) { offset = 0; @@ -544,10 +612,12 @@ static void expect_http2_headers_frame(size_t *offset, int stream_id, uint8_t fl test_get_frame_header(offset, &frame); - zassert_equal(frame.type, HTTP2_HEADERS_FRAME, "Expected headers frame"); + zassert_equal(frame.type, HTTP2_HEADERS_FRAME, "Expected headers frame, got frame type %u", + frame.type); zassert_equal(frame.stream_identifier, stream_id, "Invalid headers frame stream ID"); - zassert_equal(frame.flags, flags, "Unexpected flags received"); + zassert_equal(frame.flags, flags, "Unexpected flags received (expected %x got %x)", flags, + frame.flags); /* Consume headers payload */ test_read_data(offset, frame.length); @@ -588,7 +658,8 @@ static void expect_http2_window_update_frame(size_t *offset, int stream_id) zassert_equal(frame.type, HTTP2_WINDOW_UPDATE_FRAME, "Expected window update frame"); zassert_equal(frame.stream_identifier, stream_id, - "Invalid window update frame stream ID"); + "Invalid window update frame stream ID (expected %d got %d)", stream_id, + frame.stream_identifier); zassert_equal(frame.flags, 0, "Unexpected flags received"); zassert_equal(frame.length, sizeof(uint32_t), "Unexpected window update frame length"); @@ -1173,14 +1244,14 @@ ZTEST(server_function_tests, test_http1_header_capture) "Accept: */*\r\n" "Accept-Encoding: deflate, gzip, br\r\n" "\r\n"; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; test_http1_header_capture_common(request); - zassert_equal(header_capture_ctx_clone.count, 2, + zassert_equal(request_headers_clone.count, 2, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_OK, "Header capture status was not OK"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); @@ -1207,14 +1278,14 @@ ZTEST(server_function_tests, test_http1_header_too_long) "Accept: */*\r\n" "Accept-Encoding: deflate, gzip, br\r\n" "\r\n"; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; test_http1_header_capture_common(request); - zassert_equal(header_capture_ctx_clone.count, 1, + zassert_equal(request_headers_clone.count, 1, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED, "Header capture status was OK, but should not have been"); /* First header too long should not stop second header being captured into first slot */ @@ -1236,14 +1307,14 @@ ZTEST(server_function_tests, test_http1_header_too_many) "Accept: */*\r\n" "Accept-Encoding: deflate, gzip, br\r\n" "\r\n"; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; test_http1_header_capture_common(request); - zassert_equal(header_capture_ctx_clone.count, 2, + zassert_equal(request_headers_clone.count, 2, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED, "Header capture status OK, but should not have been"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); @@ -1290,14 +1361,14 @@ ZTEST(server_function_tests, test_http2_header_capture) TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE1_STREAM_1, TEST_HTTP2_GOAWAY, }; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; common_verify_http2_get_header_capture_request(request, sizeof(request)); - zassert_equal(header_capture_ctx_clone.count, 2, + zassert_equal(request_headers_clone.count, 2, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_OK, "Header capture status was not OK"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); @@ -1324,14 +1395,14 @@ ZTEST(server_function_tests, test_http2_header_too_long) TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE2_STREAM_1, TEST_HTTP2_GOAWAY, }; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; common_verify_http2_get_header_capture_request(request, sizeof(request)); - zassert_equal(header_capture_ctx_clone.count, 1, + zassert_equal(request_headers_clone.count, 1, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED, "Header capture status was OK, but should not have been"); /* First header too long should not stop second header being captured into first slot */ @@ -1353,14 +1424,14 @@ ZTEST(server_function_tests, test_http2_header_too_many) TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE3_STREAM_1, TEST_HTTP2_GOAWAY, }; - struct http_header *hdrs = header_capture_ctx_clone.headers; + struct http_header *hdrs = request_headers_clone.headers; int ret; common_verify_http2_get_header_capture_request(request, sizeof(request)); - zassert_equal(header_capture_ctx_clone.count, 2, + zassert_equal(request_headers_clone.count, 2, "Didn't capture the expected number of headers"); - zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, + zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED, "Header capture status OK, but should not have been"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); @@ -1378,6 +1449,73 @@ ZTEST(server_function_tests, test_http2_header_too_many) zassert_equal(0, ret, "Header strings did not match"); } +ZTEST(server_function_tests, test_http2_header_concurrent) +{ + /* Two POST requests which are concurrent, ie. headers1, headers2, data1, data2 */ + static const uint8_t request[] = { + TEST_HTTP2_MAGIC, + TEST_HTTP2_SETTINGS, + TEST_HTTP2_SETTINGS_ACK, + TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE_WITH_TESTHEADER_STREAM_1, + TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE2_NO_TESTHEADER_STREAM_2, + TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_1, + TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_2, + TEST_HTTP2_GOAWAY, + }; + + struct http_header *hdrs = request_headers_clone.headers; + struct http_header *hdrs2 = request_headers_clone2.headers; + int ret; + size_t offset = 0; + + ret = zsock_send(client_fd, request, sizeof(request), 0); + zassert_not_equal(ret, -1, "send() failed (%d)", errno); + + /* Wait for response on both resources before checking captured headers */ + expect_http2_settings_frame(&offset, false); + expect_http2_settings_frame(&offset, true); + expect_http2_headers_frame(&offset, TEST_STREAM_ID_1, + HTTP2_FLAG_END_HEADERS | HTTP2_FLAG_END_STREAM, NULL, 0); + expect_http2_window_update_frame(&offset, TEST_STREAM_ID_1); + expect_http2_window_update_frame(&offset, 0); + expect_http2_headers_frame(&offset, TEST_STREAM_ID_2, + HTTP2_FLAG_END_HEADERS | HTTP2_FLAG_END_STREAM, NULL, 0); + + /* Headers captured on /header_capture path should have two headers including the + * Test-Header + */ + zassert_equal(request_headers_clone.count, 2, + "Didn't capture the expected number of headers"); + + zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); + zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL"); + zassert_not_equal(hdrs[1].name, NULL, "Second header name is NULL"); + zassert_not_equal(hdrs[1].value, NULL, "Second header value is NULL"); + + ret = strcmp(hdrs[0].name, "User-Agent"); + zassert_equal(0, ret, "Header strings did not match"); + ret = strcmp(hdrs[0].value, "curl/7.81.0"); + zassert_equal(0, ret, "Header strings did not match"); + ret = strcmp(hdrs[1].name, "Test-Header"); + zassert_equal(0, ret, "Header strings did not match"); + ret = strcmp(hdrs[1].value, "test_value"); + zassert_equal(0, ret, "Header strings did not match"); + + /* Headers captured on the /header_capture2 path should have only one header, not including + * the Test-Header + */ + zassert_equal(request_headers_clone2.count, 1, + "Didn't capture the expected number of headers"); + + zassert_not_equal(hdrs2[0].name, NULL, "First header name is NULL"); + zassert_not_equal(hdrs2[0].value, NULL, "First header value is NULL"); + + ret = strcmp(hdrs2[0].name, "User-Agent"); + zassert_equal(0, ret, "Header strings did not match"); + ret = strcmp(hdrs2[0].value, "curl/7.81.0"); + zassert_equal(0, ret, "Header strings did not match"); +} + static void test_http1_dynamic_response_headers(const char *request, const char *expected_response) { int ret; @@ -1982,6 +2120,8 @@ static void http_server_tests_before(void *fixture) memset(dynamic_payload, 0, sizeof(dynamic_payload)); memset(dynamic_response_headers_buffer, 0, sizeof(dynamic_response_headers_buffer)); + memset(&request_headers_clone, 0, sizeof(request_headers_clone)); + memset(&request_headers_clone2, 0, sizeof(request_headers_clone2)); dynamic_payload_len = 0; ret = http_server_start();