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:
Jukka Rissanen 2019-07-28 21:43:41 +03:00
commit a07045d8b2
8 changed files with 538 additions and 0 deletions

View 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
)

View 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.

View 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

View 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

View 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

View 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"
};

Binary file not shown.

View 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);
}