Linux Audio

Check our new training course

Loading...
/*
 * Copyright (c) 2017 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#if 1
#define SYS_LOG_DOMAIN "http-client"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif

#include <zephyr.h>
#include <errno.h>

#include <net/net_core.h>
#include <net/net_ip.h>

#include <net/http.h>

#include <net/net_app.h>

#include "config.h"

#define MAX_ITERATIONS	20
#define WAIT_TIME (APP_REQ_TIMEOUT * 2)

#define RESULT_BUF_SIZE 1024
static u8_t result[RESULT_BUF_SIZE];

/*
 * Note that the http_client_ctx is quite large so be careful if that is
 * allocated from stack.
 */
static struct http_client_ctx http_ctx;

#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
NET_PKT_TX_SLAB_DEFINE(http_cli_tx, 15);
NET_PKT_DATA_POOL_DEFINE(http_cli_data, 30);

static struct k_mem_slab *tx_slab(void)
{
	return &http_cli_tx;
}

static struct net_buf_pool *data_pool(void)
{
	return &http_cli_data;
}
#else
#if defined(CONFIG_NET_L2_BT)
#error "TCP connections over Bluetooth need CONFIG_NET_CONTEXT_NET_PKT_POOL "\
	"defined."
#endif /* CONFIG_NET_L2_BT */

#define tx_slab NULL
#define data_pool NULL
#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */

struct waiter {
	struct http_client_ctx *ctx;
	struct k_sem wait;
	size_t total_len;
	size_t header_len;
};

void panic(const char *msg)
{
	if (msg) {
		NET_ERR("%s", msg);
	}

	for (;;) {
		k_sleep(K_FOREVER);
	}
}

static int do_sync_http_req(struct http_client_ctx *ctx,
			    enum http_method method,
			    const char *url,
			    const char *content_type,
			    const char *payload)
{
	struct http_client_request req = {};
	int ret;

	req.method = method;
	req.url = url;
	req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;

	ret = http_client_send_req(ctx, &req, NULL, result, sizeof(result),
				   NULL, APP_REQ_TIMEOUT);
	if (ret < 0) {
		NET_ERR("Cannot send %s request (%d)", http_method_str(method),
			ret);
		goto out;
	}

	if (ctx->rsp.data_len > sizeof(result)) {
		NET_ERR("Result buffer overflow by %zd bytes",
		       ctx->rsp.data_len - sizeof(result));

		ret = -E2BIG;
	} else {
		NET_INFO("HTTP server response status: %s",
			 ctx->rsp.http_status);

		if (ctx->parser.http_errno) {
			if (method == HTTP_OPTIONS) {
				/* Ignore error if OPTIONS is not found */
				goto out;
			}

			NET_INFO("HTTP parser status: %s",
			       http_errno_description(ctx->parser.http_errno));
			ret = -EINVAL;
			goto out;
		}

		if (method != HTTP_HEAD) {
			if (ctx->rsp.body_found) {
				NET_INFO("HTTP body: %zd bytes, "
					 "expected: %zd bytes",
					 ctx->rsp.processed,
					 ctx->rsp.content_length);
			} else {
				NET_ERR("Error detected during HTTP msg "
					"processing");
			}
		}
	}

out:
	return ret;
}

void response(struct http_client_ctx *ctx,
	      u8_t *data, size_t buflen,
	      size_t datalen,
	      enum http_final_call data_end,
	      void *user_data)
{
	struct waiter *waiter = user_data;
	int ret;

	if (data_end == HTTP_DATA_MORE) {
		NET_INFO("Received %zd bytes piece of data", datalen);

		/* Do something with the data here. For this example
		 * we just ignore the received data.
		 */
		waiter->total_len += datalen;

		if (ctx->rsp.body_start) {
			/* This fragment contains the start of the body
			 * Note that the header length is not proper if
			 * the header is spanning over multiple recv
			 * fragments.
			 */
			waiter->header_len = ctx->rsp.body_start -
				ctx->rsp.response_buf;
		}

		return;
	}

	waiter->total_len += datalen;

	NET_INFO("HTTP server response status: %s", ctx->rsp.http_status);

	if (ctx->parser.http_errno) {
		if (ctx->req.method == HTTP_OPTIONS) {
			/* Ignore error if OPTIONS is not found */
			goto out;
		}

		NET_INFO("HTTP parser status: %s",
			 http_errno_description(ctx->parser.http_errno));
		ret = -EINVAL;
		goto out;
	}

	if (ctx->req.method != HTTP_HEAD && ctx->req.method != HTTP_OPTIONS) {
		if (ctx->rsp.body_found) {
			NET_INFO("HTTP body: %zd bytes, expected: %zd bytes",
				 ctx->rsp.processed, ctx->rsp.content_length);
		} else {
			NET_ERR("Error detected during HTTP msg processing");
		}

		if (waiter->total_len !=
		    waiter->header_len + ctx->rsp.content_length) {
			NET_ERR("Error while receiving data, "
				"received %zd expected %zd bytes",
				waiter->total_len, waiter->header_len +
				ctx->rsp.content_length);
		}
	}

out:
	k_sem_give(&waiter->wait);
}

static int do_async_http_req(struct http_client_ctx *ctx,
			     enum http_method method,
			     const char *url,
			     const char *content_type,
			     const char *payload)
{
	struct http_client_request req = {};
	struct waiter waiter;
	int ret;

	req.method = method;
	req.url = url;
	req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;

	k_sem_init(&waiter.wait, 0, 1);

	waiter.total_len = 0;

	ret = http_client_send_req(ctx, &req, response, result, sizeof(result),
				   &waiter, APP_REQ_TIMEOUT);
	if (ret < 0 && ret != -EINPROGRESS) {
		NET_ERR("Cannot send %s request (%d)", http_method_str(method),
			ret);
		goto out;
	}

	if (k_sem_take(&waiter.wait, WAIT_TIME)) {
		NET_ERR("Timeout while waiting HTTP response");
		http_client_release(ctx);
		ret = -ETIMEDOUT;
		goto out;
	}

	ret = 0;

out:
	return ret;
}

static inline int do_sync_reqs(struct http_client_ctx *ctx, int count)
{
	int ret;

	/* These examples use the HTTP client API synchronously so they
	 * do not set the callback parameter.
	 */
	while (count--) {
		ret = do_sync_http_req(&http_ctx, HTTP_GET, "/index.html",
				       NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_sync_http_req(&http_ctx, HTTP_HEAD, "/",
				       NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_sync_http_req(&http_ctx, HTTP_OPTIONS, "/index.html",
				       NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_sync_http_req(&http_ctx, HTTP_POST, "/post_test.php",
				       POST_CONTENT_TYPE, POST_PAYLOAD);
		if (ret < 0) {
			goto out;
		}

		/* Note that we cannot receive data bigger than RESULT_BUF_SIZE
		 * if we wait the buffer synchronously. If you want to receive
		 * bigger data, then you need to set the callback when sending
		 * the HTTP request using http_client_send_req()
		 */
	}

out:
	return ret;
}

static inline int do_async_reqs(struct http_client_ctx *ctx, int count)
{
	int ret;

	/* These examples use the HTTP client API asynchronously so they
	 * do set the callback parameter.
	 */
	while (count--) {
		ret = do_async_http_req(&http_ctx, HTTP_GET, "/index.html",
					NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_async_http_req(&http_ctx, HTTP_HEAD, "/",
					NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_async_http_req(&http_ctx, HTTP_OPTIONS, "/index.html",
					NULL, NULL);
		if (ret < 0) {
			goto out;
		}

		ret = do_async_http_req(&http_ctx, HTTP_POST, "/post_test.php",
					POST_CONTENT_TYPE, POST_PAYLOAD);
		if (ret < 0) {
			goto out;
		}

		ret = do_async_http_req(&http_ctx, HTTP_GET, "/big-file.html",
					NULL, NULL);
		if (ret < 0) {
			goto out;
		}
	}

out:
	return ret;
}

void main(void)
{
	int ret;

	ret = http_client_init(&http_ctx, SERVER_ADDR, SERVER_PORT);
	if (ret < 0) {
		NET_ERR("HTTP init failed (%d)", ret);
		panic(NULL);
	}

	http_client_set_net_pkt_pool(&http_ctx, tx_slab, data_pool);

	ret = do_sync_reqs(&http_ctx, MAX_ITERATIONS);
	if (ret < 0) {
		goto out;
	}

	ret = do_async_reqs(&http_ctx, MAX_ITERATIONS);
	if (ret < 0) {
		goto out;
	}

out:
	http_client_release(&http_ctx);

	NET_INFO("Done!");
}