samples: net: http_client: HTTP client sample application
Simple HTTP client sample that connects to HTTP server and does GET and POST requests. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
eb40499627
commit
a07045d8b2
8 changed files with 538 additions and 0 deletions
16
samples/net/sockets/http_client/CMakeLists.txt
Normal file
16
samples/net/sockets/http_client/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||
project(http_client)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
||||
|
||||
set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/)
|
||||
|
||||
generate_inc_file_for_target(
|
||||
app
|
||||
src/https-cert.der
|
||||
${gen_dir}/https-cert.der.inc
|
||||
)
|
88
samples/net/sockets/http_client/README.rst
Normal file
88
samples/net/sockets/http_client/README.rst
Normal file
|
@ -0,0 +1,88 @@
|
|||
.. _sockets-http-client-sample:
|
||||
|
||||
Socket HTTP Client
|
||||
##################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample application implements an HTTP(S) client that will do an HTTP
|
||||
or HTTPS request and wait for the response from the HTTP server.
|
||||
|
||||
The source code for this sample application can be found at:
|
||||
:zephyr_file:`samples/net/sockets/http_client`.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
- :ref:`networking_with_host`
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
|
||||
You can use this application on a supported board, including
|
||||
running it inside QEMU as described in :ref:`networking_with_qemu`.
|
||||
|
||||
Build the http-client sample application like this:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/net/sockets/http_client
|
||||
:board: <board to use>
|
||||
:conf: <config file to use>
|
||||
:goals: build
|
||||
:compact:
|
||||
|
||||
Enabling TLS support
|
||||
====================
|
||||
|
||||
Enable TLS support in the sample by building the project with the
|
||||
``overlay-tls.conf`` overlay file enabled using these commands:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/net/sockets/http_client
|
||||
:board: qemu_x86
|
||||
:conf: "prj.conf overlay-tls.conf"
|
||||
:goals: build
|
||||
:compact:
|
||||
|
||||
An alternative way is to specify ``-DOVERLAY_CONFIG=overlay-tls.conf`` when
|
||||
running ``west build`` or ``cmake``.
|
||||
|
||||
The certificate and private key used by the sample can be found in the sample's
|
||||
:zephyr_file:`samples/net/sockets/http_client/src/` directory.
|
||||
The default certificates used by Socket HTTP Client and
|
||||
``https-server.py`` program found in the
|
||||
`net-tools <https://github.com/zephyrproject-rtos/net-tools>`_ project, enable
|
||||
establishing a secure connection between the samples.
|
||||
|
||||
|
||||
Running http-server in Linux Host
|
||||
=================================
|
||||
|
||||
You can run this ``http-client`` sample application in QEMU
|
||||
and run the ``http-server.py`` (from net-tools) on a Linux host.
|
||||
|
||||
To use QEMU for testing, follow the :ref:`networking_with_qemu` guide.
|
||||
|
||||
In a terminal window:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ./http-server.py
|
||||
|
||||
Run ``http-client`` application in QEMU:
|
||||
|
||||
.. zephyr-app-commands::
|
||||
:zephyr-app: samples/net/sockets/http_client
|
||||
:host-os: unix
|
||||
:board: qemu_x86
|
||||
:conf: prj.conf
|
||||
:goals: run
|
||||
:compact:
|
||||
|
||||
Note that ``http-server.py`` must be running in the Linux host terminal window
|
||||
before you start the http-client application in QEMU.
|
||||
Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
|
||||
|
||||
You can verify TLS communication with a Linux host as well. Just use the
|
||||
``https-server.py`` program in net-tools project.
|
13
samples/net/sockets/http_client/overlay-tls.conf
Normal file
13
samples/net/sockets/http_client/overlay-tls.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
CONFIG_MAIN_STACK_SIZE=3072
|
||||
CONFIG_NET_BUF_RX_COUNT=80
|
||||
CONFIG_NET_BUF_TX_COUNT=80
|
||||
|
||||
# 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
|
38
samples/net/sockets/http_client/prj.conf
Normal file
38
samples/net/sockets/http_client/prj.conf
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Networking config
|
||||
CONFIG_NETWORKING=y
|
||||
CONFIG_NET_IPV4=y
|
||||
CONFIG_NET_IPV6=y
|
||||
CONFIG_NET_TCP=y
|
||||
CONFIG_NET_SHELL=y
|
||||
|
||||
# Sockets
|
||||
CONFIG_NET_SOCKETS=y
|
||||
CONFIG_NET_SOCKETS_POSIX_NAMES=y
|
||||
CONFIG_NET_SOCKETS_POLL_MAX=4
|
||||
|
||||
# Network driver config
|
||||
CONFIG_TEST_RANDOM_GENERATOR=y
|
||||
|
||||
# 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_MY_IPV4_GW="192.0.2.2"
|
||||
# Address of HTTP IPv4 server
|
||||
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
|
||||
# Address of HTTP IPv6 server
|
||||
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
|
||||
|
||||
# HTTP
|
||||
CONFIG_HTTP_CLIENT=y
|
||||
|
||||
# Network debug config
|
||||
CONFIG_LOG=y
|
||||
CONFIG_LOG_IMMEDIATE=y
|
||||
CONFIG_NET_LOG=y
|
||||
CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n
|
||||
CONFIG_NET_HTTP_LOG_LEVEL_DBG=y
|
||||
|
||||
CONFIG_MAIN_STACK_SIZE=2048
|
11
samples/net/sockets/http_client/sample.yaml
Normal file
11
samples/net/sockets/http_client/sample.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
common:
|
||||
tags: net http http_client
|
||||
min_ram: 32
|
||||
# Blacklist qemu_x86_64 because of SSE compile error, see #19066 for details
|
||||
platform_exclude: qemu_x86_64
|
||||
sample:
|
||||
description: HTTP client sample
|
||||
name: http_client
|
||||
tests:
|
||||
sample.net.sockets.http_client:
|
||||
harness: net
|
15
samples/net/sockets/http_client/src/ca_certificate.h
Normal file
15
samples/net/sockets/http_client/src/ca_certificate.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define CA_CERTIFICATE_TAG 1
|
||||
|
||||
#define TLS_PEER_HOSTNAME "localhost"
|
||||
|
||||
/* This is the same cert as what is found in net-tools/https-cert.pem file
|
||||
*/
|
||||
static const unsigned char ca_certificate[] = {
|
||||
#include "https-cert.der.inc"
|
||||
};
|
BIN
samples/net/sockets/http_client/src/https-cert.der
Normal file
BIN
samples/net/sockets/http_client/src/https-cert.der
Normal file
Binary file not shown.
357
samples/net/sockets/http_client/src/main.c
Normal file
357
samples/net/sockets/http_client/src/main.c
Normal file
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(net_http_client_sample, LOG_LEVEL_DBG);
|
||||
|
||||
#include <net/net_ip.h>
|
||||
#include <net/socket.h>
|
||||
#include <net/tls_credentials.h>
|
||||
#include <net/http_client.h>
|
||||
|
||||
#include "ca_certificate.h"
|
||||
|
||||
#define HTTP_PORT 8000
|
||||
#define HTTPS_PORT 4443
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_PEER_IPV6_ADDR)
|
||||
#define SERVER_ADDR6 CONFIG_NET_CONFIG_PEER_IPV6_ADDR
|
||||
#else
|
||||
#define SERVER_ADDR6 ""
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_NET_CONFIG_PEER_IPV4_ADDR)
|
||||
#define SERVER_ADDR4 CONFIG_NET_CONFIG_PEER_IPV4_ADDR
|
||||
#else
|
||||
#define SERVER_ADDR4 ""
|
||||
#endif
|
||||
|
||||
#define MAX_RECV_BUF_LEN 512
|
||||
|
||||
static u8_t recv_buf_ipv4[MAX_RECV_BUF_LEN];
|
||||
static u8_t recv_buf_ipv6[MAX_RECV_BUF_LEN];
|
||||
|
||||
static int setup_socket(sa_family_t family, const char *server, int port,
|
||||
int *sock, struct sockaddr *addr, socklen_t addr_len)
|
||||
{
|
||||
const char *family_str = family == AF_INET ? "IPv4" : "IPv6";
|
||||
int ret = 0;
|
||||
|
||||
memset(addr, 0, addr_len);
|
||||
|
||||
if (family == AF_INET) {
|
||||
net_sin(addr)->sin_family = AF_INET;
|
||||
net_sin(addr)->sin_port = htons(port);
|
||||
inet_pton(family, server, &net_sin(addr)->sin_addr);
|
||||
} else {
|
||||
net_sin6(addr)->sin6_family = AF_INET6;
|
||||
net_sin6(addr)->sin6_port = htons(port);
|
||||
inet_pton(family, server, &net_sin6(addr)->sin6_addr);
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
|
||||
sec_tag_t sec_tag_list[] = {
|
||||
CA_CERTIFICATE_TAG,
|
||||
};
|
||||
|
||||
*sock = socket(family, SOCK_STREAM, IPPROTO_TLS_1_2);
|
||||
if (*sock >= 0) {
|
||||
ret = setsockopt(*sock, SOL_TLS, TLS_SEC_TAG_LIST,
|
||||
sec_tag_list, sizeof(sec_tag_list));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set %s secure option (%d)",
|
||||
family_str, -errno);
|
||||
ret = -errno;
|
||||
}
|
||||
|
||||
ret = setsockopt(*sock, SOL_TLS, TLS_HOSTNAME,
|
||||
TLS_PEER_HOSTNAME,
|
||||
sizeof(TLS_PEER_HOSTNAME));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to set %s TLS_HOSTNAME "
|
||||
"option (%d)", family_str, -errno);
|
||||
ret = -errno;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*sock = socket(family, SOCK_STREAM, IPPROTO_TCP);
|
||||
}
|
||||
|
||||
if (*sock < 0) {
|
||||
LOG_ERR("Failed to create %s HTTP socket (%d)", family_str,
|
||||
-errno);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int payload_cb(int sock, struct http_request *req, void *user_data)
|
||||
{
|
||||
const char *content[] = {
|
||||
"foobar",
|
||||
"chunked",
|
||||
"last"
|
||||
};
|
||||
char tmp[64];
|
||||
int i, pos = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(content); i++) {
|
||||
pos += snprintk(tmp + pos, sizeof(tmp) - pos,
|
||||
"%x\r\n%s\r\n",
|
||||
(unsigned int)strlen(content[i]),
|
||||
content[i]);
|
||||
}
|
||||
|
||||
pos += snprintk(tmp + pos, sizeof(tmp) - pos, "0\r\n\r\n");
|
||||
|
||||
(void)send(sock, tmp, pos, 0);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static void response_cb(struct http_response *rsp,
|
||||
enum http_final_call final_data,
|
||||
void *user_data)
|
||||
{
|
||||
if (final_data == HTTP_DATA_MORE) {
|
||||
LOG_INF("Partial data received (%zd bytes)", rsp->data_len);
|
||||
} else if (final_data == HTTP_DATA_FINAL) {
|
||||
LOG_INF("All the data received (%zd bytes)", rsp->data_len);
|
||||
}
|
||||
|
||||
LOG_INF("Response to %s", (const char *)user_data);
|
||||
LOG_INF("Response status %s", rsp->http_status);
|
||||
}
|
||||
|
||||
static int connect_socket(sa_family_t family, const char *server, int port,
|
||||
int *sock, struct sockaddr *addr, socklen_t addr_len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = setup_socket(family, server, port, sock, addr, addr_len);
|
||||
if (ret < 0 || *sock < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = connect(*sock, addr, addr_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Cannot connect to %s remote (%d)",
|
||||
family == AF_INET ? "IPv4" : "IPv6",
|
||||
-errno);
|
||||
ret = -errno;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
struct sockaddr_in6 addr6;
|
||||
struct sockaddr_in addr4;
|
||||
int sock4 = -1, sock6 = -1;
|
||||
s32_t timeout = K_SECONDS(3);
|
||||
int ret;
|
||||
int port = HTTP_PORT;
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
|
||||
ret = tls_credential_add(CA_CERTIFICATE_TAG,
|
||||
TLS_CREDENTIAL_CA_CERTIFICATE,
|
||||
ca_certificate,
|
||||
sizeof(ca_certificate));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to register public certificate: %d",
|
||||
ret);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
port = HTTPS_PORT;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
(void)connect_socket(AF_INET, SERVER_ADDR4, port,
|
||||
&sock4, (struct sockaddr *)&addr4,
|
||||
sizeof(addr4));
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
(void)connect_socket(AF_INET6, SERVER_ADDR6, port,
|
||||
&sock6, (struct sockaddr *)&addr6,
|
||||
sizeof(addr6));
|
||||
}
|
||||
|
||||
if (sock4 < 0 && sock6 < 0) {
|
||||
LOG_ERR("Cannot create HTTP connection.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (sock4 >= 0 && IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
struct http_request req;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_GET;
|
||||
req.url = "/";
|
||||
req.host = SERVER_ADDR4;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv4;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv4);
|
||||
|
||||
ret = http_client_req(sock4, &req, timeout, "IPv4 GET");
|
||||
|
||||
close(sock4);
|
||||
}
|
||||
|
||||
if (sock6 >= 0 && IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
struct http_request req;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_GET;
|
||||
req.url = "/";
|
||||
req.host = SERVER_ADDR6;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv6;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv6);
|
||||
|
||||
ret = http_client_req(sock6, &req, timeout, "IPv6 GET");
|
||||
|
||||
close(sock6);
|
||||
}
|
||||
|
||||
sock4 = -1;
|
||||
sock6 = -1;
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
(void)connect_socket(AF_INET, SERVER_ADDR4, port,
|
||||
&sock4, (struct sockaddr *)&addr4,
|
||||
sizeof(addr4));
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
(void)connect_socket(AF_INET6, SERVER_ADDR6, port,
|
||||
&sock6, (struct sockaddr *)&addr6,
|
||||
sizeof(addr6));
|
||||
}
|
||||
|
||||
if (sock4 < 0 && sock6 < 0) {
|
||||
LOG_ERR("Cannot create HTTP connection.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (sock4 >= 0 && IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
struct http_request req;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_POST;
|
||||
req.url = "/foobar";
|
||||
req.host = SERVER_ADDR4;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.payload = "foobar";
|
||||
req.payload_len = strlen(req.payload);
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv4;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv4);
|
||||
|
||||
ret = http_client_req(sock4, &req, timeout, "IPv4 POST");
|
||||
|
||||
close(sock4);
|
||||
}
|
||||
|
||||
if (sock6 >= 0 && IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
struct http_request req;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_POST;
|
||||
req.url = "/";
|
||||
req.host = SERVER_ADDR6;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.payload = "foobar";
|
||||
req.payload_len = strlen(req.payload);
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv6;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv6);
|
||||
|
||||
ret = http_client_req(sock6, &req, timeout, "IPv6 POST");
|
||||
|
||||
close(sock6);
|
||||
}
|
||||
|
||||
/* Do a chunked POST request */
|
||||
|
||||
sock4 = -1;
|
||||
sock6 = -1;
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
(void)connect_socket(AF_INET, SERVER_ADDR4, port,
|
||||
&sock4, (struct sockaddr *)&addr4,
|
||||
sizeof(addr4));
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
(void)connect_socket(AF_INET6, SERVER_ADDR6, port,
|
||||
&sock6, (struct sockaddr *)&addr6,
|
||||
sizeof(addr6));
|
||||
}
|
||||
|
||||
if (sock4 < 0 && sock6 < 0) {
|
||||
LOG_ERR("Cannot create HTTP connection.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (sock4 >= 0 && IS_ENABLED(CONFIG_NET_IPV4)) {
|
||||
struct http_request req;
|
||||
const char *headers[] = {
|
||||
"Transfer-Encoding: chunked\r\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_POST;
|
||||
req.url = "/chunked-test";
|
||||
req.host = SERVER_ADDR4;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.payload_cb = payload_cb;
|
||||
req.header_fields = headers;
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv4;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv4);
|
||||
|
||||
ret = http_client_req(sock4, &req, timeout, "IPv4 POST");
|
||||
|
||||
close(sock4);
|
||||
}
|
||||
|
||||
if (sock6 >= 0 && IS_ENABLED(CONFIG_NET_IPV6)) {
|
||||
struct http_request req;
|
||||
const char *headers[] = {
|
||||
"Transfer-Encoding: chunked\r\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
|
||||
req.method = HTTP_POST;
|
||||
req.url = "/chunked-test";
|
||||
req.host = SERVER_ADDR6;
|
||||
req.protocol = "HTTP/1.1";
|
||||
req.payload_cb = payload_cb;
|
||||
req.header_fields = headers;
|
||||
req.response = response_cb;
|
||||
req.recv_buf = recv_buf_ipv6;
|
||||
req.recv_buf_len = sizeof(recv_buf_ipv6);
|
||||
|
||||
ret = http_client_req(sock6, &req, timeout, "IPv6 POST");
|
||||
|
||||
close(sock6);
|
||||
}
|
||||
|
||||
k_sleep(K_FOREVER);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue