samples: net: http_server: Add HTTP server sample application

A simple HTTP server sample application.

Signed-off-by: Emna Rekik <emna.rekik007@gmail.com>
Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2023-11-09 20:15:12 +02:00 committed by Carles Cufí
parent 87f45d153f
commit 660149dbf7
16 changed files with 573 additions and 0 deletions

View file

@ -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()

View file

@ -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"

View file

@ -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 <https://docs.zephyrproject.org/latest/connectivity/networking/qemu_setup.html#networking-with-qemu>`_
Building and running the server
-------------------------------
To build and run the application:
.. code-block:: bash
$ west build -p auto -b <board_to_use> -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 <pid_of_server>
``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 <pid_of_server> -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.

View file

@ -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

View file

@ -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: {}

View file

@ -0,0 +1,4 @@
#include <zephyr/linker/iterable_sections.h>
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)

Binary file not shown.

View file

@ -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__ */

View file

@ -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__ */

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Zephyr HTTP Server</title>
</head>
<body>
<h1>Welcome to Zephyr HTTP Server!</h1>
<p>This is a simple HTML file.</p>
</body>
</html>

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2023, Emna Rekik
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/net/tls_credentials.h>
#include <zephyr/net/http/server.h>
#include <zephyr/net/http/service.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/socket.h>
#include <zephyr/logging/log.h>
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;
}

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
<p>The requested resource was not found.</p>
</body>
</html>

Binary file not shown.

Binary file not shown.