net: http: client: Initial version

Simple HTTP client API.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
diff --git a/include/net/http_client.h b/include/net/http_client.h
new file mode 100644
index 0000000..e310aaa
--- /dev/null
+++ b/include/net/http_client.h
@@ -0,0 +1,289 @@
+/** @file
+ * @brief HTTP client API
+ *
+ * An API for applications do HTTP requests
+ */
+
+/*
+ * Copyright (c) 2019 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_NET_HTTP_CLIENT_H_
+#define ZEPHYR_INCLUDE_NET_HTTP_CLIENT_H_
+
+/**
+ * @brief HTTP client API
+ * @defgroup http_client HTTP client API
+ * @ingroup networking
+ * @{
+ */
+
+#include <net/net_ip.h>
+#include <net/http_parser.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined(HTTP_CRLF)
+#define HTTP_CRLF "\r\n"
+#endif
+
+#if !defined(HTTP_STATUS_STR_SIZE)
+#define HTTP_STATUS_STR_SIZE	32
+#endif
+
+/* Is there more data to come */
+enum http_final_call {
+	HTTP_DATA_MORE = 0,
+	HTTP_DATA_FINAL = 1,
+};
+
+struct http_request;
+struct http_response;
+
+/**
+ * @typedef http_payload_cb_t
+ * @brief Callback used when data needs to be sent to the server.
+ *
+ * @param sock Socket id of the connection
+ * @param req HTTP request information
+ * @param user_data User specified data specified in http_client_req()
+ *
+ * @return >=0 amount of data sent, in this case http_client_req() should
+ *             continue sending data,
+ *         <0  if http_client_req() should return the error code to the
+ *             caller.
+ */
+typedef int (*http_payload_cb_t)(int sock,
+				 struct http_request *req,
+				 void *user_data);
+
+/**
+ * @typedef http_header_cb_t
+ * @brief Callback can be used if application wants to construct additional
+ * HTTP headers when the HTTP request is sent. Usage of this is optional.
+ *
+ * @param sock Socket id of the connection
+ * @param req HTTP request information
+ * @param user_data User specified data specified in http_client_req()
+ *
+ * @return >=0 amount of data sent, in this case http_client_req() should
+ *             continue sending data,
+ *         <0  if http_client_req() should return the error code to the
+ *             caller.
+ */
+typedef int (*http_header_cb_t)(int sock,
+				struct http_request *req,
+				void *user_data);
+
+/**
+ * @typedef http_response_cb_t
+ * @brief Callback used when data is received from the server.
+ *
+ * @param rsp HTTP response information
+ * @param final_data Does this data buffer contain all the data or
+ *        is there still more data to come.
+ * @param user_data User specified data specified in http_client_req()
+ */
+typedef void (*http_response_cb_t)(struct http_response *rsp,
+				   enum http_final_call final_data,
+				   void *user_data);
+
+/**
+ * HTTP response from the server.
+ */
+struct http_response {
+	/** HTTP parser settings for the application usage */
+	const struct http_parser_settings *http_cb;
+
+	/** User provided HTTP response callback which is
+	 * called when a response is received to a sent HTTP
+	 * request.
+	 */
+	http_response_cb_t cb;
+
+	/** Where the body starts */
+	u8_t *body_start;
+
+	/** Where the response is stored, this is to be
+	 * provided by the user.
+	 */
+	u8_t *recv_buf;
+
+	/** Response buffer maximum length */
+	size_t recv_buf_len;
+
+	/** Length of the data in the result buf. If the value
+	 * is larger than recv_buf_len, then it means that
+	 * the data is truncated and could not be fully copied
+	 * into recv_buf. This can only happen if the user
+	 * did not set the response callback. If the callback
+	 * is set, then the HTTP client API will call response
+	 * callback many times so that all the data is
+	 * delivered to the user.
+	 */
+	size_t data_len;
+
+	/** HTTP Content-Length field value */
+	size_t content_length;
+
+	/** Content length parsed. This should be the same as
+	 * the content_length field if parsing was ok.
+	 */
+	size_t processed;
+
+	/* https://tools.ietf.org/html/rfc7230#section-3.1.2
+	 * The status-code element is a 3-digit integer code
+	 *
+	 * The reason-phrase element exists for the sole
+	 * purpose of providing a textual description
+	 * associated with the numeric status code. A client
+	 * SHOULD ignore the reason-phrase content.
+	 */
+	char http_status[HTTP_STATUS_STR_SIZE];
+
+	u8_t cl_present : 1;
+	u8_t body_found : 1;
+	u8_t message_complete : 1;
+};
+
+/** HTTP client internal data that the application should not touch
+ */
+struct http_client_internal_data {
+	/** Work for handling timeout */
+	struct k_delayed_work work;
+
+	/** HTTP parser context */
+	struct http_parser parser;
+
+	/** HTTP parser settings */
+	struct http_parser_settings parser_settings;
+
+	/** HTTP response specific data (filled by http_client_req() when
+	 * data is received)
+	 */
+	struct http_response response;
+
+	/** User data */
+	void *user_data;
+
+	/** HTTP socket */
+	int sock;
+
+	/** Request timeout */
+	s32_t timeout;
+};
+
+/**
+ * HTTP client request. This contains all the data that is needed when doing
+ * a HTTP request.
+ */
+struct http_request {
+	/** HTTP client request internal data */
+	struct http_client_internal_data internal;
+
+	/* User should fill in following parameters */
+
+	/** The HTTP method: GET, HEAD, OPTIONS, POST, ... */
+	enum http_method method;
+
+	/** User supplied callback function to call when response is
+	 * received.
+	 */
+	http_response_cb_t response;
+
+	/** User supplied list of HTTP callback functions if the
+	 * calling application wants to know the parsing status or the HTTP
+	 * fields. This is optional and normally not needed.
+	 */
+	const struct http_parser_settings *http_cb;
+
+	/** User supplied buffer where received data is stored */
+	u8_t *recv_buf;
+
+	/** Length of the user supplied receive buffer */
+	size_t recv_buf_len;
+
+	/** The URL for this request, for example: /index.html */
+	const char *url;
+
+	/** The HTTP protocol, for example "HTTP/1.1" */
+	const char *protocol;
+
+	/** The HTTP header fields (application specific)
+	 * The Content-Type may be specified here or in the next field.
+	 * Depending on your application, the Content-Type may vary, however
+	 * some header fields may remain constant through the application's
+	 * life cycle. This is a NULL terminated list of header fields.
+	 */
+	const char **header_fields;
+
+	/** The value of the Content-Type header field, may be NULL */
+	const char *content_type_value;
+
+	/** Hostname to be used in the request */
+	const char *host;
+
+	/** User supplied callback function to call when payload
+	 * needs to be sent. This can be NULL in which case the payload field
+	 * in http_request is used. The idea of this payload callback is to
+	 * allow user to send more data that is practical to store in allocated
+	 * memory.
+	 */
+	http_payload_cb_t payload_cb;
+
+	/** Payload, may be NULL */
+	const char *payload;
+
+	/** Payload length, may be 0. Only used if payload field is not NULL */
+	size_t payload_len;
+
+	/** User supplied callback function to call when optional headers need
+	 * to be sent. This can be NULL, in which case the optional_headers
+	 * field in http_request is used. The idea of this optional_headers
+	 * callback is to allow user to send more HTTP header data that is
+	 * practical to store in allocated memory.
+	 */
+	http_header_cb_t optional_headers_cb;
+
+	/** A NULL terminated list of any optional headers that
+	 * should be added to the HTTP request. May be NULL.
+	 * If the optional_headers_cb is specified, then this field is ignored.
+	 * Note that there are two similar fields that contain headers,
+	 * the header_fields above and this optional_headers. This is done
+	 * like this to support Websocket use case where Websocket will use
+	 * header_fields variable and any optional application specific
+	 * headers will be placed into this field.
+	 */
+	const char **optional_headers;
+};
+
+/**
+ * @brief Do a HTTP request. The callback is called when data is received
+ * from the HTTP server. The caller must have created a connection to the
+ * server before calling this function so connect() call must have be done
+ * successfully for the socket.
+ *
+ * @param sock Socket id of the connection.
+ * @param req HTTP request information
+ * @param timeout Max timeout to wait for the data. The timeout value cannot be
+ *        0 as there would be no time to receive the data.
+ * @param user_data User specified data that is passed to the callback.
+ *
+ * @return <0 if error, >=0 amount of data sent to the server
+ */
+int http_client_req(int sock, struct http_request *req,
+		    s32_t timeout, void *user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* ZEPHYR_INCLUDE_NET_HTTP_CLIENT_H_ */
diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt
index b12a800..77fada0 100644
--- a/subsys/net/lib/CMakeLists.txt
+++ b/subsys/net/lib/CMakeLists.txt
@@ -17,8 +17,7 @@
   add_subdirectory(dns)
 endif()
 
-if(CONFIG_HTTP_PARSER_URL
-    OR CONFIG_HTTP_PARSER)
+if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER OR CONFIG_HTTP_CLIENT)
   add_subdirectory(http)
 endif()
 
diff --git a/subsys/net/lib/http/CMakeLists.txt b/subsys/net/lib/http/CMakeLists.txt
index e178d62..7a2255d 100644
--- a/subsys/net/lib/http/CMakeLists.txt
+++ b/subsys/net/lib/http/CMakeLists.txt
@@ -6,5 +6,8 @@
 zephyr_library_compile_definitions(HTTP_PARSER_STRICT)
 endif()
 
+zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/ip)
+
 zephyr_library_sources_if_kconfig(http_parser.c)
 zephyr_library_sources_if_kconfig(http_parser_url.c)
+zephyr_library_sources_if_kconfig(http_client.c)
diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig
index 10d3d5c..0f76718 100644
--- a/subsys/net/lib/http/Kconfig
+++ b/subsys/net/lib/http/Kconfig
@@ -23,3 +23,16 @@
 	depends on (HTTP_PARSER || HTTP_PARSER_URL)
 	help
 	  This option enables the strict parsing option
+
+config HTTP_CLIENT
+	bool "HTTP client API [EXPERIMENTAL]"
+	select HTTP_PARSER
+	select HTTP_PARSER_URL
+	help
+	  HTTP client API
+
+module = NET_HTTP
+module-dep = NET_LOG
+module-str = Log level for HTTP client library
+module-help = Enables HTTP client code to output debug messages.
+source "subsys/net/Kconfig.template.log_config.net"
diff --git a/subsys/net/lib/http/http_client.c b/subsys/net/lib/http/http_client.c
new file mode 100644
index 0000000..9f34075
--- /dev/null
+++ b/subsys/net/lib/http/http_client.c
@@ -0,0 +1,659 @@
+/** @file
+ * @brief HTTP client API
+ *
+ * An API for applications to send HTTP requests
+ */
+
+/*
+ * Copyright (c) 2019 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(net_http, CONFIG_NET_HTTP_LOG_LEVEL);
+
+#include <kernel.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <net/net_ip.h>
+#include <net/socket.h>
+#include <net/http_client.h>
+
+#include "net_private.h"
+
+#define HTTP_CONTENT_LEN_SIZE 6
+#define MAX_SEND_BUF_LEN 192
+
+static ssize_t sendall(int sock, const void *buf, size_t len)
+{
+	while (len) {
+		ssize_t out_len = send(sock, buf, len, 0);
+
+		if (out_len < 0) {
+			return -errno;
+		}
+
+		buf = (const char *)buf + out_len;
+		len -= out_len;
+	}
+
+	return 0;
+}
+
+static int http_send_data(int sock, char *send_buf,
+			  size_t send_buf_max_len, size_t *send_buf_pos,
+			  ...)
+{
+	const char *data;
+	va_list va;
+	int ret, end_of_send = *send_buf_pos;
+	int end_of_data, remaining_len;
+
+	va_start(va, send_buf_pos);
+
+	data = va_arg(va, const char *);
+
+	while (data) {
+		end_of_data = 0;
+
+		do {
+			int to_be_copied;
+
+			remaining_len = strlen(data + end_of_data);
+			to_be_copied = send_buf_max_len - end_of_send;
+
+			if (remaining_len > to_be_copied) {
+				strncpy(send_buf + end_of_send,
+					data + end_of_data,
+					to_be_copied);
+
+				end_of_send += to_be_copied;
+				end_of_data += to_be_copied;
+				remaining_len -= to_be_copied;
+
+				LOG_HEXDUMP_DBG(send_buf, end_of_send,
+						"Data to send");
+
+				ret = sendall(sock, send_buf, end_of_send);
+				if (ret < 0) {
+					NET_DBG("Cannot send %d bytes (%d)",
+						end_of_send, ret);
+					goto err;
+				}
+
+				end_of_send = 0;
+				continue;
+			} else {
+				strncpy(send_buf + end_of_send,
+					data + end_of_data,
+					remaining_len);
+				end_of_send += remaining_len;
+				remaining_len = 0;
+			}
+		} while (remaining_len > 0);
+
+		data = va_arg(va, const char *);
+	};
+
+	va_end(va);
+
+	if (end_of_send > (int)send_buf_max_len) {
+		NET_ERR("Sending overflow (%d > %zd)", end_of_send,
+			send_buf_max_len);
+		return -EMSGSIZE;
+	}
+
+	*send_buf_pos = end_of_send;
+
+	return end_of_send;
+
+err:
+	va_end(va);
+
+	return ret;
+}
+
+static int http_flush_data(int sock, const char *send_buf, size_t send_buf_len)
+{
+	LOG_HEXDUMP_DBG(send_buf, send_buf_len, "Data to send");
+
+	return sendall(sock, send_buf, send_buf_len);
+}
+
+static void print_header_field(size_t len, const char *str)
+{
+	if (IS_ENABLED(CONFIG_NET_HTTP_LOG_LEVEL_DBG)) {
+#define MAX_OUTPUT_LEN 128
+		char output[MAX_OUTPUT_LEN];
+
+		/* The value of len does not count \0 so we need to increase it
+		 * by one.
+		 */
+		if ((len + 1) > sizeof(output)) {
+			len = sizeof(output) - 1;
+		}
+
+		snprintk(output, len + 1, "%s", str);
+
+		NET_DBG("[%zd] %s", len, log_strdup(output));
+	}
+}
+
+static int on_url(struct http_parser *parser, const char *at, size_t length)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+	print_header_field(length, at);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_url) {
+		req->internal.response.http_cb->on_url(parser, at, length);
+	}
+
+	return 0;
+}
+
+static int on_status(struct http_parser *parser, const char *at, size_t length)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+	u16_t len;
+
+	len = MIN(length, sizeof(req->internal.response.http_status) - 1);
+	memcpy(req->internal.response.http_status, at, len);
+	req->internal.response.http_status[len] = 0;
+
+	NET_DBG("HTTP response status %d %s", parser->status_code,
+		log_strdup(req->internal.response.http_status));
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_status) {
+		req->internal.response.http_cb->on_status(parser, at, length);
+	}
+
+	return 0;
+}
+
+static int on_header_field(struct http_parser *parser, const char *at,
+			   size_t length)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+	const char *content_len = "Content-Length";
+	u16_t len;
+
+	len = strlen(content_len);
+	if (length >= len && strncasecmp(at, content_len, len) == 0) {
+		req->internal.response.cl_present = true;
+	}
+
+	print_header_field(length, at);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_header_field) {
+		req->internal.response.http_cb->on_header_field(parser, at,
+								length);
+	}
+
+	return 0;
+}
+
+#define MAX_NUM_DIGITS	16
+
+static int on_header_value(struct http_parser *parser, const char *at,
+			   size_t length)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+	char str[MAX_NUM_DIGITS];
+
+	if (req->internal.response.cl_present) {
+		if (length <= MAX_NUM_DIGITS - 1) {
+			long int num;
+
+			memcpy(str, at, length);
+			str[length] = 0;
+
+			num = strtol(str, NULL, 10);
+			if (num == LONG_MIN || num == LONG_MAX) {
+				return -EINVAL;
+			}
+
+			req->internal.response.content_length = num;
+		}
+
+		req->internal.response.cl_present = false;
+	}
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_header_value) {
+		req->internal.response.http_cb->on_header_value(parser, at,
+								length);
+	}
+
+	print_header_field(length, at);
+
+	return 0;
+}
+
+static int on_body(struct http_parser *parser, const char *at, size_t length)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	req->internal.response.body_found = 1;
+	req->internal.response.processed += length;
+
+	NET_DBG("Processed %zd length %zd", req->internal.response.processed,
+		length);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_body) {
+		req->internal.response.http_cb->on_body(parser, at, length);
+	}
+
+	if (!req->internal.response.body_start &&
+	    (u8_t *)at != (u8_t *)req->internal.response.recv_buf) {
+		req->internal.response.body_start = (u8_t *)at;
+	}
+
+	if (req->internal.response.cb) {
+		if (http_should_keep_alive(parser)) {
+			NET_DBG("Calling callback for partitioned %zd len data",
+				req->internal.response.data_len);
+
+			req->internal.response.cb(&req->internal.response,
+						  HTTP_DATA_MORE,
+						  req->internal.user_data);
+		} else {
+			NET_DBG("Calling callback for %zd len data",
+				req->internal.response.data_len);
+
+			req->internal.response.cb(&req->internal.response,
+						  HTTP_DATA_FINAL,
+						  req->internal.user_data);
+		}
+
+		/* Re-use the result buffer and start to fill it again */
+		req->internal.response.data_len = 0;
+		req->internal.response.body_start = NULL;
+	}
+
+	return 0;
+}
+
+static int on_headers_complete(struct http_parser *parser)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_headers_complete) {
+		req->internal.response.http_cb->on_headers_complete(parser);
+	}
+
+	if (parser->status_code >= 500 && parser->status_code < 600) {
+		NET_DBG("Status %d, skipping body", parser->status_code);
+		return 1;
+	}
+
+	if ((req->method == HTTP_HEAD || req->method == HTTP_OPTIONS) &&
+	    req->internal.response.content_length > 0) {
+		NET_DBG("No body expected");
+		return 1;
+	}
+
+	if ((req->method == HTTP_PUT || req->method == HTTP_POST) &&
+	    req->internal.response.content_length == 0) {
+		NET_DBG("No body expected");
+		return 1;
+	}
+
+	NET_DBG("Headers complete");
+
+	return 0;
+}
+
+static int on_message_begin(struct http_parser *parser)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_message_begin) {
+		req->internal.response.http_cb->on_message_begin(parser);
+	}
+
+	NET_DBG("-- HTTP %s response (headers) --",
+		http_method_str(req->method));
+
+	return 0;
+}
+
+static int on_message_complete(struct http_parser *parser)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_message_complete) {
+		req->internal.response.http_cb->on_message_complete(parser);
+	}
+
+	NET_DBG("-- HTTP %s response (complete) --",
+		http_method_str(req->method));
+
+	req->internal.response.message_complete = 1;
+
+	if (req->internal.response.cb) {
+		req->internal.response.cb(&req->internal.response,
+					  HTTP_DATA_FINAL,
+					  req->internal.user_data);
+	}
+
+	return 0;
+}
+
+static int on_chunk_header(struct http_parser *parser)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_chunk_header) {
+		req->internal.response.http_cb->on_chunk_header(parser);
+	}
+
+	return 0;
+}
+
+static int on_chunk_complete(struct http_parser *parser)
+{
+	struct http_request *req = CONTAINER_OF(parser,
+						struct http_request,
+						internal.parser);
+
+	if (req->internal.response.http_cb &&
+	    req->internal.response.http_cb->on_chunk_complete) {
+		req->internal.response.http_cb->on_chunk_complete(parser);
+	}
+
+	return 0;
+}
+
+static void http_client_init_parser(struct http_parser *parser,
+				    struct http_parser_settings *settings)
+{
+	http_parser_init(parser, HTTP_RESPONSE);
+
+	settings->on_body = on_body;
+	settings->on_chunk_complete = on_chunk_complete;
+	settings->on_chunk_header = on_chunk_header;
+	settings->on_headers_complete = on_headers_complete;
+	settings->on_header_field = on_header_field;
+	settings->on_header_value = on_header_value;
+	settings->on_message_begin = on_message_begin;
+	settings->on_message_complete = on_message_complete;
+	settings->on_status = on_status;
+	settings->on_url = on_url;
+}
+
+static int http_wait_data(int sock, struct http_request *req)
+{
+	int total_received = 0;
+	size_t offset = 0;
+	int received, ret;
+
+	do {
+		received = recv(sock, req->internal.response.recv_buf + offset,
+				req->internal.response.recv_buf_len - offset,
+				0);
+		if (received == 0) {
+			/* Connection closed */
+			LOG_DBG("Connection closed");
+			ret = total_received;
+			break;
+		} else if (received < 0) {
+			/* Socket error */
+			LOG_DBG("Connection error (%d)", errno);
+			ret = -errno;
+			break;
+		} else {
+			req->internal.response.data_len += received;
+
+			(void)http_parser_execute(
+				&req->internal.parser,
+				&req->internal.parser_settings,
+				req->internal.response.recv_buf + offset,
+				received);
+		}
+
+		total_received += received;
+		offset += received;
+
+		if (offset >= req->internal.response.recv_buf_len) {
+			offset = 0;
+		}
+
+		if (req->internal.response.message_complete) {
+			ret = total_received;
+			break;
+		}
+
+	} while (true);
+
+	return ret;
+}
+
+static void http_timeout(struct k_work *work)
+{
+	struct http_client_internal_data *data =
+		CONTAINER_OF(work, struct http_client_internal_data, work);
+
+	(void)close(data->sock);
+}
+
+int http_client_req(int sock, struct http_request *req,
+		    s32_t timeout, void *user_data)
+{
+	/* Utilize the network usage by sending data in bigger blocks */
+	char send_buf[MAX_SEND_BUF_LEN];
+	const size_t send_buf_max_len = sizeof(send_buf);
+	size_t send_buf_pos = 0;
+	int total_sent = 0;
+	int ret, total_recv, i;
+	const char *method;
+
+	if (sock < 0 || req == NULL || req->response == NULL ||
+	    req->recv_buf == NULL || req->recv_buf_len == 0) {
+		return -EINVAL;
+	}
+
+	memset(&req->internal.response, 0, sizeof(req->internal.response));
+
+	req->internal.response.http_cb = req->http_cb;
+	req->internal.response.cb = req->response;
+	req->internal.response.recv_buf = req->recv_buf;
+	req->internal.response.recv_buf_len = req->recv_buf_len;
+	req->internal.user_data = user_data;
+	req->internal.timeout = timeout;
+	req->internal.sock = sock;
+
+	method = http_method_str(req->method);
+
+	ret = http_send_data(sock, send_buf, send_buf_max_len, &send_buf_pos,
+			     method, " ", req->url, " ", req->protocol,
+			     HTTP_CRLF, NULL);
+	if (ret < 0) {
+		goto out;
+	}
+
+	total_sent += ret;
+
+	ret = http_send_data(sock, send_buf, send_buf_max_len, &send_buf_pos,
+			     "Host", ": ", req->host, HTTP_CRLF, NULL);
+	if (ret < 0) {
+		goto out;
+	}
+
+	total_sent += ret;
+
+	if (req->optional_headers_cb) {
+		ret = http_flush_data(sock, send_buf, send_buf_pos);
+		if (ret < 0) {
+			goto out;
+		}
+
+		send_buf_pos = 0;
+		total_sent += ret;
+
+		ret = req->optional_headers_cb(sock, req, user_data);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	} else {
+		for (i = 0; req->optional_headers && req->optional_headers[i];
+		     i++) {
+			ret = http_send_data(sock, send_buf, send_buf_max_len,
+					     &send_buf_pos,
+					     req->optional_headers[i], NULL);
+			if (ret < 0) {
+				goto out;
+			}
+
+			total_sent += ret;
+		}
+	}
+
+	for (i = 0; req->header_fields && req->header_fields[i]; i++) {
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, req->header_fields[i],
+				     NULL);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	}
+
+	if (req->content_type_value) {
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, "Content-Type", ": ",
+				     req->content_type_value, HTTP_CRLF, NULL);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	}
+
+	if (req->payload_cb) {
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, HTTP_CRLF, NULL);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+
+		ret = http_flush_data(sock, send_buf, send_buf_pos);
+		if (ret < 0) {
+			goto out;
+		}
+
+		send_buf_pos = 0;
+		total_sent += ret;
+
+		ret = req->payload_cb(sock, req, user_data);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	} else if (req->payload) {
+		char content_len_str[HTTP_CONTENT_LEN_SIZE];
+
+		ret = snprintk(content_len_str, HTTP_CONTENT_LEN_SIZE,
+			       "%zd", req->payload_len);
+		if (ret <= 0 || ret >= HTTP_CONTENT_LEN_SIZE) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, "Content-Length", ": ",
+				     content_len_str, HTTP_CRLF,
+				     HTTP_CRLF, NULL);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, req->payload, NULL);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	} else {
+		ret = http_send_data(sock, send_buf, send_buf_max_len,
+				     &send_buf_pos, HTTP_CRLF, NULL);
+		if (ret < 0) {
+			goto out;
+		}
+	}
+
+	if (send_buf_pos > 0) {
+		ret = http_flush_data(sock, send_buf, send_buf_pos);
+		if (ret < 0) {
+			goto out;
+		}
+
+		total_sent += ret;
+	}
+
+	NET_DBG("Sent %d bytes", total_sent);
+
+	http_client_init_parser(&req->internal.parser,
+				&req->internal.parser_settings);
+
+	if (timeout != K_FOREVER && timeout != K_NO_WAIT) {
+		k_delayed_work_init(&req->internal.work, http_timeout);
+		(void)k_delayed_work_submit(&req->internal.work, timeout);
+	}
+
+	/* Request is sent, now wait data to be received */
+	total_recv = http_wait_data(sock, req);
+	if (total_recv < 0) {
+		NET_DBG("Wait data failure (%d)", total_recv);
+	} else {
+		NET_DBG("Received %d bytes", total_recv);
+	}
+
+	if (timeout != K_FOREVER && timeout != K_NO_WAIT) {
+		(void)k_delayed_work_cancel(&req->internal.work);
+	}
+
+	return total_sent;
+
+out:
+	return ret;
+}