1 /*
2 * Copyright (c) 2025 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/misc/lorem_ipsum.h>
8 #include <zephyr/net/http/client.h>
9 #include <zephyr/net/http/service.h>
10 #include <zephyr/ztest.h>
11
12 #define SERVER_IPV6_ADDR "::1"
13 #define SERVER_PORT 8080
14 #define TEST_BUF_SIZE 1200
15
16 static uint16_t test_http_service_port = SERVER_PORT;
17 HTTP_SERVICE_DEFINE(test_http_service, SERVER_IPV6_ADDR,
18 &test_http_service_port, 1, 10, NULL, NULL, NULL);
19
20 static const char static_resource_payload[] = LOREM_IPSUM_SHORT;
21 struct http_resource_detail_static static_resource_detail = {
22 .common = {
23 .type = HTTP_RESOURCE_TYPE_STATIC,
24 .bitmask_of_supported_http_methods = BIT(HTTP_GET),
25 },
26 .static_data = static_resource_payload,
27 .static_data_len = sizeof(static_resource_payload) - 1,
28 };
29 HTTP_RESOURCE_DEFINE(static_resource, test_http_service, "/static",
30 &static_resource_detail);
31
32 static uint8_t dynamic_buf[TEST_BUF_SIZE];
33 static size_t dynamic_len;
34
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)35 static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status,
36 const struct http_request_ctx *request_ctx,
37 struct http_response_ctx *response_ctx, void *user_data)
38 {
39 static size_t offset;
40
41 if (status == HTTP_SERVER_DATA_ABORTED) {
42 offset = 0;
43 return 0;
44 }
45
46 switch (client->method) {
47 case HTTP_GET:
48 response_ctx->body = dynamic_buf;
49 response_ctx->body_len = dynamic_len;
50 response_ctx->final_chunk = true;
51 break;
52 case HTTP_POST:
53 if (request_ctx->data_len + offset > sizeof(dynamic_buf)) {
54 return -ENOMEM;
55 }
56
57 if (request_ctx->data_len > 0) {
58 memcpy(dynamic_buf + offset, request_ctx->data,
59 request_ctx->data_len);
60 offset += request_ctx->data_len;
61 }
62
63 if (status == HTTP_SERVER_DATA_FINAL) {
64 /* All data received, reset progress. */
65 dynamic_len = offset;
66 offset = 0;
67 }
68
69 break;
70 default:
71 return -ENOTSUP;
72 }
73
74 return 0;
75 }
76
77 struct http_resource_detail_dynamic dynamic_resource_detail = {
78 .common = {
79 .type = HTTP_RESOURCE_TYPE_DYNAMIC,
80 .bitmask_of_supported_http_methods =
81 BIT(HTTP_GET) | BIT(HTTP_POST),
82 },
83 .cb = dynamic_cb,
84 .user_data = NULL,
85 };
86 HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic",
87 &dynamic_resource_detail);
88
89 struct test_ctx {
90 uint8_t *buf;
91 size_t buflen;
92 size_t offset;
93 uint16_t status;
94 bool abort;
95 bool final;
96 };
97
response_cb(struct http_response * rsp,enum http_final_call final_data,void * user_data)98 static int response_cb(struct http_response *rsp,
99 enum http_final_call final_data,
100 void *user_data)
101 {
102 struct test_ctx *ctx = user_data;
103
104 if (ctx == NULL) {
105 return 0;
106 }
107
108 if (ctx->abort) {
109 return -EBADMSG;
110 }
111
112 /* Final event should only be received once. */
113 zassert_false(ctx->final);
114
115 if (final_data == HTTP_DATA_FINAL) {
116 ctx->final = true;
117 ctx->status = rsp->http_status_code;
118 }
119
120 /* Copy response body */
121 if (ctx->buf != NULL && rsp->body_frag_start != NULL &&
122 rsp->body_frag_len > 0) {
123 zassert_true(ctx->offset + rsp->body_frag_len < ctx->buflen,
124 "Response too long");
125 memcpy(ctx->buf + ctx->offset, rsp->body_frag_start,
126 rsp->body_frag_len);
127 ctx->offset += rsp->body_frag_len;
128 }
129
130 return 0;
131 }
132
133 static int client_fd = -1;
134 static uint8_t recv_buf[64];
135 static uint8_t response_buf[TEST_BUF_SIZE];
136 static size_t resp_offset;
137
common_request_init(struct http_request * req)138 static void common_request_init(struct http_request *req)
139 {
140 req->host = SERVER_IPV6_ADDR;
141 req->protocol = "HTTP/1.1";
142 req->response = response_cb;
143 req->recv_buf = recv_buf;
144 req->recv_buf_len = sizeof(recv_buf);
145 }
146
ZTEST(http_client,test_http1_client_get)147 ZTEST(http_client, test_http1_client_get)
148 {
149 struct http_request req = { 0 };
150 struct test_ctx ctx = { 0 };
151 int ret;
152
153 common_request_init(&req);
154 req.method = HTTP_GET;
155 req.url = "/static";
156
157 ctx.buf = response_buf;
158 ctx.buflen = sizeof(response_buf);
159
160 ret = http_client_req(client_fd, &req, -1, &ctx);
161 zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
162 zassert_true(ctx.final, "No final event received");
163 zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
164 zassert_equal(ctx.offset, strlen(static_resource_payload),
165 "Invalid payload length");
166 zassert_mem_equal(response_buf, static_resource_payload, ctx.offset,
167 "Invalid payload");
168 }
169
test_http1_client_get_cb_common(struct http_parser_settings * http_cb)170 static void test_http1_client_get_cb_common(struct http_parser_settings *http_cb)
171 {
172 struct http_request req = { 0 };
173 int ret;
174
175 common_request_init(&req);
176 req.method = HTTP_GET;
177 req.url = "/static";
178 req.http_cb = http_cb;
179
180 ret = http_client_req(client_fd, &req, -1, NULL);
181 zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
182 }
183
test_common_cb(struct http_parser * parser,const char * at,size_t length)184 int test_common_cb(struct http_parser *parser, const char *at, size_t length)
185 {
186 zassert_true(resp_offset + length <= sizeof(response_buf),
187 "HTTP field too long");
188
189 memcpy(response_buf + resp_offset, at, length);
190 resp_offset += length;
191
192 return 0;
193 }
194
ZTEST(http_client,test_http1_client_get_status_cb)195 ZTEST(http_client, test_http1_client_get_status_cb)
196 {
197 struct http_parser_settings http_cb = {
198 .on_status = test_common_cb,
199 };
200
201 test_http1_client_get_cb_common(&http_cb);
202
203 zassert_str_equal(response_buf, "OK", "Wrong status");
204 }
205
ZTEST(http_client,test_http1_client_get_body_cb)206 ZTEST(http_client, test_http1_client_get_body_cb)
207 {
208 struct http_parser_settings http_cb = {
209 .on_body = test_common_cb,
210 };
211
212 test_http1_client_get_cb_common(&http_cb);
213
214 zassert_str_equal(response_buf, LOREM_IPSUM_SHORT, "Wrong body payload");
215 }
216
217 static bool test_on_header_field;
218
test_header_field_cb(struct http_parser * parser,const char * at,size_t length)219 int test_header_field_cb(struct http_parser *parser, const char *at, size_t length)
220 {
221 if (resp_offset > 0 && !test_on_header_field) {
222 response_buf[resp_offset++] = '\n';
223 }
224
225 zassert_true(resp_offset + length <= sizeof(response_buf),
226 "HTTP field too long");
227
228 memcpy(response_buf + resp_offset, at, length);
229 resp_offset += length;
230
231 test_on_header_field = true;
232
233 return 0;
234 }
235
test_header_value_cb(struct http_parser * parser,const char * at,size_t length)236 int test_header_value_cb(struct http_parser *parser, const char *at, size_t length)
237 {
238 if (test_on_header_field) {
239 response_buf[resp_offset++] = ':';
240 response_buf[resp_offset++] = ' ';
241
242 test_on_header_field = false;
243 }
244
245 zassert_true(resp_offset + length <= sizeof(response_buf),
246 "HTTP field too long");
247
248 memcpy(response_buf + resp_offset, at, length);
249 resp_offset += length;
250
251 return 0;
252 }
253
ZTEST(http_client,test_http1_client_get_headers_cb)254 ZTEST(http_client, test_http1_client_get_headers_cb)
255 {
256 struct http_parser_settings http_cb = {
257 .on_header_field = test_header_field_cb,
258 .on_header_value = test_header_value_cb,
259 };
260 uint8_t *header_field;
261
262 test_http1_client_get_cb_common(&http_cb);
263
264 header_field = strstr(response_buf, "Content-Type: text/html");
265 zassert_not_null(header_field, "Header field not found");
266 header_field = strstr(response_buf, "Content-Length: 445");
267 zassert_not_null(header_field, "Header field not found");
268 }
269
ZTEST(http_client,test_http1_client_get_abort)270 ZTEST(http_client, test_http1_client_get_abort)
271 {
272 struct http_request req = { 0 };
273 struct test_ctx ctx = { 0 };
274 int ret;
275
276 common_request_init(&req);
277 req.method = HTTP_GET;
278 req.url = "/static";
279
280 ctx.abort = true;
281
282 ret = http_client_req(client_fd, &req, -1, &ctx);
283 zassert_equal(ret, -ECONNABORTED,
284 "http_client_req() should've reported abort (%d)", ret);
285 }
286
ZTEST(http_client,test_http1_client_get_no_resource)287 ZTEST(http_client, test_http1_client_get_no_resource)
288 {
289 struct http_request req = { 0 };
290 struct test_ctx ctx = { 0 };
291 int ret;
292
293 common_request_init(&req);
294 req.method = HTTP_GET;
295 req.url = "/not_found";
296
297 ret = http_client_req(client_fd, &req, -1, &ctx);
298 zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
299 zassert_true(ctx.final, "No final event received");
300 zassert_equal(ctx.status, 404, "Unexpected HTTP status code");
301 }
302
ZTEST(http_client,test_http1_client_post)303 ZTEST(http_client, test_http1_client_post)
304 {
305 struct http_request req = { 0 };
306 struct test_ctx ctx = { 0 };
307 int ret;
308
309 common_request_init(&req);
310 req.method = HTTP_POST;
311 req.url = "/dynamic";
312 req.payload = LOREM_IPSUM;
313 req.payload_len = LOREM_IPSUM_STRLEN;
314
315 ret = http_client_req(client_fd, &req, -1, &ctx);
316 zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
317 zassert_true(ctx.final, "No final event received");
318 zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
319 zassert_equal(dynamic_len, LOREM_IPSUM_STRLEN,
320 "Invalid payload length uploaded");
321 zassert_mem_equal(dynamic_buf, LOREM_IPSUM, dynamic_len,
322 "Invalid payload uploaded");
323 }
324
test_payload_cb(int sock,struct http_request * req,void * user_data)325 static int test_payload_cb(int sock, struct http_request *req, void *user_data)
326 {
327 int ret;
328
329 ret = zsock_send(sock, LOREM_IPSUM_SHORT, LOREM_IPSUM_SHORT_STRLEN, 0);
330 zassert_equal(ret, LOREM_IPSUM_SHORT_STRLEN,
331 "Failed to send payload (%d)", ret);
332
333 return ret;
334 }
335
ZTEST(http_client,test_http1_client_post_payload_cb)336 ZTEST(http_client, test_http1_client_post_payload_cb)
337 {
338 static const char * const headers[] = {
339 "Content-Length: " STRINGIFY(LOREM_IPSUM_SHORT_STRLEN) "\r\n",
340 NULL
341 };
342 struct http_request req = { 0 };
343 struct test_ctx ctx = { 0 };
344 int ret;
345
346 common_request_init(&req);
347 req.method = HTTP_POST;
348 req.url = "/dynamic";
349 req.header_fields = headers;
350 req.payload_cb = test_payload_cb;
351
352 ret = http_client_req(client_fd, &req, -1, &ctx);
353 zassert_true(ret > 0, "http_client_req() failed (%d)", ret);
354 zassert_true(ctx.final, "No final event received");
355 zassert_equal(ctx.status, 200, "Unexpected HTTP status code");
356 zassert_equal(dynamic_len, LOREM_IPSUM_SHORT_STRLEN,
357 "Invalid payload length uploaded %d", dynamic_len);
358 zassert_mem_equal(dynamic_buf, LOREM_IPSUM_SHORT, dynamic_len,
359 "Invalid payload uploaded %d", dynamic_len);
360 }
361
client_tests_before(void * fixture)362 static void client_tests_before(void *fixture)
363 {
364 struct net_sockaddr_in6 sa;
365 int ret;
366
367 ARG_UNUSED(fixture);
368
369 dynamic_len = 0;
370 resp_offset = 0;
371 test_on_header_field = false;
372 memset(recv_buf, 0, sizeof(recv_buf));
373 memset(response_buf, 0, sizeof(response_buf));
374 memset(dynamic_buf, 0, sizeof(dynamic_buf));
375
376 ret = http_server_start();
377 if (ret < 0) {
378 printk("Failed to start the server\n");
379 return;
380 }
381
382 ret = zsock_socket(NET_AF_INET6, NET_SOCK_STREAM, NET_IPPROTO_TCP);
383 if (ret < 0) {
384 printk("Failed to create client socket (%d)\n", errno);
385 return;
386 }
387 client_fd = ret;
388
389 sa.sin6_family = NET_AF_INET6;
390 sa.sin6_port = net_htons(SERVER_PORT);
391
392 ret = zsock_inet_pton(NET_AF_INET6, SERVER_IPV6_ADDR, &sa.sin6_addr.s6_addr);
393 if (ret != 1) {
394 printk("inet_pton() failed to convert %s\n", SERVER_IPV6_ADDR);
395 return;
396 }
397
398 ret = zsock_connect(client_fd, (struct net_sockaddr *)&sa, sizeof(sa));
399 if (ret < 0) {
400 printk("Failed to connect (%d)\n", errno);
401 }
402 }
403
client_tests_after(void * fixture)404 static void client_tests_after(void *fixture)
405 {
406 ARG_UNUSED(fixture);
407
408 if (client_fd >= 0) {
409 (void)zsock_close(client_fd);
410 client_fd = -1;
411 }
412
413 (void)http_server_stop();
414
415 k_yield();
416 }
417
418 ZTEST_SUITE(http_client, NULL, NULL, client_tests_before, client_tests_after, NULL);
419