diff --git a/samples/net/sockets/http_server/CMakeLists.txt b/samples/net/sockets/http_server/CMakeLists.txt new file mode 100644 index 00000000000000..5b27e5ee7ca96f --- /dev/null +++ b/samples/net/sockets/http_server/CMakeLists.txt @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +find_package(Python REQUIRED COMPONENTS Interpreter) + +project(http_server) + +if(CONFIG_NET_SOCKETS_SOCKOPT_TLS AND + CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED AND + (CONFIG_NET_SAMPLE_PSK_HEADER_FILE STREQUAL "dummy_psk.h")) + add_custom_target(development_psk + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + COMMAND ${CMAKE_COMMAND} -E echo "--- WARNING: Using dummy PSK! Only suitable for ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- development. Set NET_SAMPLE_PSK_HEADER_FILE to use ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- own pre-shared key. ---" + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + ) + add_dependencies(app development_psk) +endif() + +option(INCLUDE_HTML_CONTENT "Include the HTML content" ON) + +target_sources(app PRIVATE src/main.c) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +set(source_file_index src/index.html) +generate_inc_file_for_target(app ${source_file_index} ${gen_dir}/index.html.gz.inc --gzip) + +set(source_file_not_found src/not_found_page.html) +generate_inc_file_for_target(app ${source_file_not_found} ${gen_dir}/not_found_page.html.gz.inc --gzip) + +target_link_libraries(app PRIVATE zephyr_interface zephyr) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTPS_SERVICE NAME + http_resource_desc_test_https_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTP_SERVICE NAME + http_resource_desc_test_http_service + KVMA RAM_REGION GROUP RODATA_REGION + SUBALIGN Z_LINK_ITERABLE_SUBALIGN) + +foreach(inc_file + ca.der + server.der + server_privkey.der + https-server-cert.der + https-server-key.der + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() diff --git a/samples/net/sockets/http_server/Kconfig b/samples/net/sockets/http_server/Kconfig new file mode 100644 index 00000000000000..90104be01fa19a --- /dev/null +++ b/samples/net/sockets/http_server/Kconfig @@ -0,0 +1,41 @@ +# Config options for http2 server sample application + +# Copyright (c) 2023, Emna Rekik +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "HTTP2 server sample application" + +config NET_SAMPLE_HTTP_SERVICE + bool "Enable http service" + default y + +config NET_SAMPLE_HTTP_SERVER_SERVICE_PORT + int "Port number for http service" + default 80 + depends on NET_SAMPLE_HTTP_SERVICE + +config NET_SAMPLE_HTTPS_SERVICE + bool "Enable https service" + depends on NET_SOCKETS_SOCKOPT_TLS || TLS_CREDENTIALS + +config NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT + int "Port number for https service" + default 443 + depends on NET_SAMPLE_HTTPS_SERVICE + +config NET_SAMPLE_PSK_HEADER_FILE + string "Header file containing PSK" + default "dummy_psk.h" + depends on MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + help + Name of a header file containing a + pre-shared key. + +config NET_SAMPLE_CERTS_WITH_SC + bool "Signed certificates" + depends on NET_SOCKETS_SOCKOPT_TLS + help + Enable this flag, if you are interested to run this + application with signed certificates and keys. + +source "Kconfig.zephyr" diff --git a/samples/net/sockets/http_server/README.rst b/samples/net/sockets/http_server/README.rst new file mode 100644 index 00000000000000..cc4cd1c5482196 --- /dev/null +++ b/samples/net/sockets/http_server/README.rst @@ -0,0 +1,118 @@ +Zephyr HTTP Server +================== + +Overview +-------- + +This sample application demonstrates the use of the ``http_server`` library. +This library provides high-level functions to simplify and abstract server implementation. +The server supports the HTTP/1.1 protocol which can also be upgraded to HTTP/2, +it also support native HTTP/2 protocol without upgrading. + +Requirement +----------- + +`QEMU Networking `_ + +Building and running the server +------------------------------- + +To build and run the application: + +.. code-block:: bash + + $ west build -p auto -b -t run samples/net/sockets/http_server + +When the server is up, we can make requests to the server using either HTTP/1.1 or +HTTP/2 protocol from the host machine. + +**With HTTP/1.1:** + +- Using a browser: ``http://192.0.2.1/`` +- Using curl: ``curl -v --compressed http://192.0.2.1/`` +- Using ab (Apache Bench): ``ab -n10 http://192.0.2.1/`` + +**With HTTP/2:** + +- Using nghttp client: ``nghttp -v --no-dep http://192.0.2.1/`` +- Using curl: ``curl --http2 -v --compressed http://192.0.2.1/`` +- Using h2load: ``h2load -n10 http://192.0.2.1/`` + +Server Customization +--------------------- + +The server sample contains several parameters that can be customized based on +the requirements. These are the configurable parameters: + +- ``CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT``: Configures the service port. + +- ``CONFIG_HTTP_SERVER_MAX_CLIENTS``: Defines the maximum number of HTTP/2 + clients that the server can handle simultaneously. + +- ``CONFIG_HTTP_SERVER_MAX_STREAMS``: Specifies the maximum number of HTTP/2 + streams that can be established per client. + +- ``CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE``: Defines the buffer size allocated + for each client. This limits the maximum length of an individual HTTP header + supported. + +- ``CONFIG_HTTP_SERVER_MAX_URL_LENGTH``: Specifies the maximum length of an HTTP + URL that the server can process. + +To customize these options, we can run ``west build -t menuconfig``, which provides +us with an interactive configuration interface. Then we could navigate from the top-level +menu to: ``-> Subsystems and OS Services -> Networking -> Network Protocols``. + +Performance Analysis +-------------------- + +CPU Usage Profiling +******************* + +We can use ``perf`` to collect statistics about the CPU usage of our server +running in native_sim board with the ``stat`` command: + +.. code-block:: bash + + $ sudo perf stat -p + +``perf stat`` will then start monitoring our server. We can let it run while +sending requests to our server. Once we've collected enough data, we can +stop ``perf stat``, which will print a summary of the performance statistics. + +Hotspot Analysis +**************** + +``perf record`` and ``perf report`` can be used together to identify the +functions in our code that consume the most CPU time: + +.. code-block:: bash + + $ sudo perf record -g -p -o perf.data + +After running our server under load (For example, using ApacheBench tool), +we can stop the recording and analyze the data using: + +.. code-block:: bash + + $ sudo perf report -i perf.data + +After generating a file named ``perf.data`` which contains the profiling data, +we can visualize it using ``FlameGraph`` tool. It's particularly useful for +identifying the most expensive code-paths and inspect where our application is +spending the most time. + +To do this, we need to convert the ``perf.data`` to a format that ``FlameGraph`` +can understand: + +.. code-block:: bash + + $ sudo perf script | ~/FlameGraph/stackcollapse-perf.pl > out.perf-folded + +And, then, generate the ``FlameGraph``: + +.. code-block:: bash + + $ ~/FlameGraph/flamegraph.pl out.perf-folded > flamegraph.svg + +We can view flamegraph.svg using a web browser. diff --git a/samples/net/sockets/http_server/prj.conf b/samples/net/sockets/http_server/prj.conf new file mode 100644 index 00000000000000..9e56f3332e7f2b --- /dev/null +++ b/samples/net/sockets/http_server/prj.conf @@ -0,0 +1,71 @@ +# General config +CONFIG_MAIN_STACK_SIZE=3072 +CONFIG_SHELL=y +CONFIG_LOG=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_POSIX_MAX_FDS=32 +CONFIG_POSIX_API=y +CONFIG_FDTABLE=y +CONFIG_NET_SOCKETS_POLL_MAX=32 + +# Eventfd +CONFIG_EVENTFD=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_SHELL=y +CONFIG_NET_LOG=y + +# JSON +CONFIG_JSON_LIBRARY=y + +# HTTP parser +CONFIG_HTTP_PARSER_URL=y +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_SERVER=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IP address options +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_MAX_CONTEXTS=32 +CONFIG_NET_MAX_CONN=32 + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=6 +CONFIG_TLS_CREDENTIALS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 + +# Networking tweaks +# Required to handle large number of consecutive connections, +# e.g. when testing with ApacheBench. +CONFIG_NET_TCP_TIME_WAIT_DELAY=0 diff --git a/samples/net/sockets/http_server/sample.yaml b/samples/net/sockets/http_server/sample.yaml new file mode 100644 index 00000000000000..eeaca47d386eda --- /dev/null +++ b/samples/net/sockets/http_server/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: HTTP Server Sample + name: http_server_sample +common: + harness: net + min_ram: 192 + tags: + - http + - net + - server + - socket + platform_exclude: + - native_posix + - native_posix/native/64 +tests: + sample.net.sockets.http.server: {} diff --git a/samples/net/sockets/http_server/sections-rom.ld b/samples/net/sockets/http_server/sections-rom.ld new file mode 100644 index 00000000000000..d51cad087f3e90 --- /dev/null +++ b/samples/net/sockets/http_server/sections-rom.ld @@ -0,0 +1,4 @@ +#include + +ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_ROM(http_resource_desc_test_https_service, Z_LINK_ITERABLE_SUBALIGN) diff --git a/samples/net/sockets/http_server/src/ca.der b/samples/net/sockets/http_server/src/ca.der new file mode 100644 index 00000000000000..b1d3e097cadcea Binary files /dev/null and b/samples/net/sockets/http_server/src/ca.der differ diff --git a/samples/net/sockets/http_server/src/certificate.h b/samples/net/sockets/http_server/src/certificate.h new file mode 100644 index 00000000000000..52a3fa9c8ea18f --- /dev/null +++ b/samples/net/sockets/http_server/src/certificate.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CERTIFICATE_H__ +#define __CERTIFICATE_H__ + +enum tls_tag { + /** The Certificate Authority public key */ + HTTP_SERVER_CA_CERTIFICATE_TAG, + /** Used for both the public and private server keys */ + HTTP_SERVER_CERTIFICATE_TAG, + /** Used for both the public and private client keys */ + HTTP_SERVER_CLIENT_CERTIFICATE_TAG, + PSK_TAG, +}; + +#if !defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) +static const unsigned char server_certificate[] = { +#include "https-server-cert.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "https-server-key.der.inc" +}; + +#else + +static const unsigned char ca_certificate[] = { +#include "ca.der.inc" +}; + +static const unsigned char server_certificate[] = { +#include "server.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "server_privkey.der.inc" +}; +#endif + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) +#include CONFIG_NET_SAMPLE_PSK_HEADER_FILE +#endif + +#endif /* __CERTIFICATE_H__ */ diff --git a/samples/net/sockets/http_server/src/dummy_psk.h b/samples/net/sockets/http_server/src/dummy_psk.h new file mode 100644 index 00000000000000..e67107266fda45 --- /dev/null +++ b/samples/net/sockets/http_server/src/dummy_psk.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DUMMY_PSK_H__ +#define __DUMMY_PSK_H__ + +static const unsigned char psk[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, +0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; +static const char psk_id[] = "PSK_identity"; + +#endif /* __DUMMY_PSK_H__ */ diff --git a/samples/net/sockets/http_server/src/https-server-cert.der b/samples/net/sockets/http_server/src/https-server-cert.der new file mode 100644 index 00000000000000..bfcb335e31c8c3 Binary files /dev/null and b/samples/net/sockets/http_server/src/https-server-cert.der differ diff --git a/samples/net/sockets/http_server/src/https-server-key.der b/samples/net/sockets/http_server/src/https-server-key.der new file mode 100644 index 00000000000000..5a4d67372ea418 Binary files /dev/null and b/samples/net/sockets/http_server/src/https-server-key.der differ diff --git a/samples/net/sockets/http_server/src/index.html b/samples/net/sockets/http_server/src/index.html new file mode 100644 index 00000000000000..ad654e6a3051b5 --- /dev/null +++ b/samples/net/sockets/http_server/src/index.html @@ -0,0 +1,10 @@ + + + + Zephyr HTTP Server + + +

Welcome to Zephyr HTTP Server!

+

This is a simple HTML file.

+ + diff --git a/samples/net/sockets/http_server/src/main.c b/samples/net/sockets/http_server/src/main.c new file mode 100644 index 00000000000000..dac4499029d4db --- /dev/null +++ b/samples/net/sockets/http_server/src/main.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023, Emna Rekik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(net_http_server_sample, LOG_LEVEL_DBG); + +static uint8_t index_html_gz[] = { +#include "index.html.gz.inc" +}; + +#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE) +static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT; +HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port, 1, + 10, NULL); + +struct http_resource_detail_static index_html_gz_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + .content_encoding = "gzip", + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource, test_http_service, "/", + &index_html_gz_resource_detail); + +static uint8_t recv_buffer[1024]; + +static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, + uint8_t *buffer, size_t len, void *user_data) +{ +#define MAX_TEMP_PRINT_LEN 32 + static char print_str[MAX_TEMP_PRINT_LEN]; + enum http_method method = client->method; + static size_t processed; + + __ASSERT_NO_MSG(buffer != NULL); + + if (status == HTTP_SERVER_DATA_ABORTED) { + LOG_DBG("Transaction aborted after %zd bytes.", processed); + processed = 0; + return 0; + } + + processed += len; + + snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", + http_method_str(method), len); + LOG_HEXDUMP_DBG(buffer, len, print_str); + + if (status == HTTP_SERVER_DATA_FINAL) { + LOG_DBG("All data received (%zd bytes).", processed); + processed = 0; + } + + /* This will echo data back to client as the buffer and recv_buffer + * point to same area. + */ + return len; +} + +struct http_resource_detail_dynamic dyn_resource_detail = { + .common = { + .type = HTTP_RESOURCE_TYPE_DYNAMIC, + .bitmask_of_supported_http_methods = + BIT(HTTP_GET) | BIT(HTTP_POST), + }, + .cb = dyn_handler, + .data_buffer = recv_buffer, + .data_buffer_len = sizeof(recv_buffer), + .user_data = NULL, +}; + +HTTP_RESOURCE_DEFINE(dyn_resource, test_http_service, "/dynamic", + &dyn_resource_detail); + +#endif /* CONFIG_NET_SAMPLE_HTTP_SERVICE */ + +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#include "certificate.h" + +static const sec_tag_t sec_tag_list_verify_none[] = { + HTTP_SERVER_CERTIFICATE_TAG, +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + PSK_TAG, +#endif + }; + +static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT; +HTTPS_SERVICE_DEFINE(test_https_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, + &test_https_service_port, 1, 10, NULL, + sec_tag_list_verify_none, sizeof(sec_tag_list_verify_none)); + +static struct http_resource_detail_static index_html_gz_resource_detail_https = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + .content_encoding = "gzip", + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/", + &index_html_gz_resource_detail_https); + +#endif /* CONFIG_NET_SAMPLE_HTTPS_SERVICE */ + +static void setup_tls(void) +{ +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + int err; + +#if defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, + sizeof(ca_certificate)); + if (err < 0) { + LOG_ERR("Failed to register CA certificate: %d", err); + } +#endif /* defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) */ + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_SERVER_CERTIFICATE, + server_certificate, + sizeof(server_certificate)); + if (err < 0) { + LOG_ERR("Failed to register public certificate: %d", err); + } + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_PRIVATE_KEY, + private_key, sizeof(private_key)); + if (err < 0) { + LOG_ERR("Failed to register private key: %d", err); + } + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK, + psk, + sizeof(psk)); + if (err < 0) { + LOG_ERR("Failed to register PSK: %d", err); + } + + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK_ID, + psk_id, + sizeof(psk_id) - 1); + if (err < 0) { + LOG_ERR("Failed to register PSK ID: %d", err); + } +#endif /* defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) */ +#endif /* defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) */ +#endif /* defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) */ +} + +int main(void) +{ + setup_tls(); + http_server_start(); + return 0; +} diff --git a/samples/net/sockets/http_server/src/not_found_page.html b/samples/net/sockets/http_server/src/not_found_page.html new file mode 100644 index 00000000000000..c4bf66f08ee1e6 --- /dev/null +++ b/samples/net/sockets/http_server/src/not_found_page.html @@ -0,0 +1,10 @@ + + + + 404 Not Found + + +

404 Not Found

+

The requested resource was not found.

+ + diff --git a/samples/net/sockets/http_server/src/server.der b/samples/net/sockets/http_server/src/server.der new file mode 100644 index 00000000000000..2b664a4bdb2ce6 Binary files /dev/null and b/samples/net/sockets/http_server/src/server.der differ diff --git a/samples/net/sockets/http_server/src/server_privkey.der b/samples/net/sockets/http_server/src/server_privkey.der new file mode 100644 index 00000000000000..2269293fe790f2 Binary files /dev/null and b/samples/net/sockets/http_server/src/server_privkey.der differ