diff --git a/include/net/http_client.h b/include/net/http_client.h new file mode 100644 index 00000000000..e310aaa5fb6 --- /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 +#include + +#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 b12a80000a5..77fada00c6e 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -17,8 +17,7 @@ if (CONFIG_DNS_RESOLVER 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 e178d62f4fa..7a2255db704 100644 --- a/subsys/net/lib/http/CMakeLists.txt +++ b/subsys/net/lib/http/CMakeLists.txt @@ -6,5 +6,8 @@ if(CONFIG_HTTP_PARSER_STRICT) 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 10d3d5c9264..0f76718a347 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -23,3 +23,16 @@ config HTTP_PARSER_STRICT 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 00000000000..9f34075c626 --- /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 +LOG_MODULE_REGISTER(net_http, CONFIG_NET_HTTP_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +}