Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

839 lines
20 KiB

/** @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 <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_http_client, CONFIG_NET_HTTP_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/http/client.h>
#include <zephyr/net/http/status.h>
#include "net_private.h"
#define HTTP_CONTENT_LEN_SIZE 11
#define MAX_SEND_BUF_LEN 192
static int sendall(int sock, const void *buf, size_t len,
const k_timepoint_t req_end_timepoint)
{
while (len) {
ssize_t out_len = zsock_send(sock, buf, len, 0);
if ((out_len == 0) || (out_len < 0 && errno == EAGAIN)) {
struct zsock_pollfd pfd;
int pollres;
k_ticks_t req_timeout_ticks =
sys_timepoint_timeout(req_end_timepoint).ticks;
int req_timeout_ms = k_ticks_to_ms_floor32(req_timeout_ticks);
pfd.fd = sock;
pfd.events = ZSOCK_POLLOUT;
pollres = zsock_poll(&pfd, 1, req_timeout_ms);
if (pollres == 0) {
return -ETIMEDOUT;
} else if (pollres > 0) {
continue;
} else {
return -errno;
}
} else 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 k_timepoint_t req_end_timepoint,
...)
{
const char *data;
va_list va;
int ret, end_of_send = *send_buf_pos;
int end_of_data, remaining_len;
int sent = 0;
va_start(va, req_end_timepoint);
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, req_end_timepoint);
if (ret < 0) {
NET_DBG("Cannot send %d bytes (%d)",
end_of_send, ret);
goto err;
}
sent += end_of_send;
end_of_send = 0;
continue;
} else {
memcpy(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 sent;
err:
va_end(va);
return ret;
}
static int http_flush_data(int sock, const char *send_buf, size_t send_buf_len,
const k_timepoint_t req_end_timepoint)
{
int ret;
LOG_HEXDUMP_DBG(send_buf, send_buf_len, "Data to send");
ret = sendall(sock, send_buf, send_buf_len, req_end_timepoint);
if (ret < 0) {
return ret;
}
return (int)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, 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);
uint16_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;
req->internal.response.http_status_code =
(uint16_t)parser->status_code;
NET_DBG("HTTP response status %d %s", parser->status_code,
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);
static const char content_len[] = "Content-Length";
static const char content_range[] = "Content-Range";
uint16_t content_len_len = sizeof(content_len) - 1;
uint16_t content_range_len = sizeof(content_range) - 1;
if (length >= content_len_len && strncasecmp(at, content_len, content_len_len) == 0) {
req->internal.response.cl_present = true;
} else if (length >= content_range_len &&
strncasecmp(at, content_range, content_range_len) == 0) {
req->internal.response.cr_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.cr_present) {
req->internal.response.content_range.start = parser->content_range.start;
req->internal.response.content_range.end = parser->content_range.end;
req->internal.response.content_range.total = parser->content_range.total;
req->internal.response.cr_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);
}
/* Reset the body_frag_start pointer for each fragment. */
if (!req->internal.response.body_frag_start) {
req->internal.response.body_frag_start = (uint8_t *)at;
}
/* Calculate the length of the body contained in the recv_buf */
req->internal.response.body_frag_len = req->internal.response.data_len -
(req->internal.response.body_frag_start - req->internal.response.recv_buf);
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 == HTTP_101_SWITCHING_PROTOCOLS) {
NET_DBG("Switching protocols, skipping body");
return 1;
}
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;
}
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;
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;
}
/* Report a NULL HTTP response to the caller.
* A NULL response is when the HTTP server intentionally closes the TLS socket (using FINACK)
* without sending any HTTP payload.
*/
static void http_report_null(struct http_request *req)
{
if (req->internal.response.cb) {
NET_DBG("Calling callback for Final Data"
"(NULL HTTP response)");
/* Status code 0 representing a null response */
req->internal.response.http_status_code = 0;
/* Zero out related response metrics */
req->internal.response.processed = 0;
req->internal.response.data_len = 0;
req->internal.response.content_length = 0;
req->internal.response.body_frag_start = NULL;
memset(req->internal.response.http_status, 0, HTTP_STATUS_STR_SIZE);
req->internal.response.cb(&req->internal.response, HTTP_DATA_FINAL,
req->internal.user_data);
}
}
/* Report a completed HTTP transaction (with no error) to the caller */
static void http_report_complete(struct http_request *req)
{
if (req->internal.response.cb) {
NET_DBG("Calling callback for %zd len data", req->internal.response.data_len);
(void)req->internal.response.cb(&req->internal.response,
HTTP_DATA_FINAL,
req->internal.user_data);
}
}
/* Report that some data has been received, but the HTTP transaction is still ongoing. */
static int http_report_progress(struct http_request *req)
{
if (req->internal.response.cb) {
NET_DBG("Calling callback for partitioned %zd len data",
req->internal.response.data_len);
return req->internal.response.cb(&req->internal.response,
HTTP_DATA_MORE,
req->internal.user_data);
}
return 0;
}
static int http_wait_data(int sock, struct http_request *req, const k_timepoint_t req_end_timepoint)
{
int total_received = 0;
size_t offset = 0, processed = 0;
int received, ret;
struct zsock_pollfd fds[1];
int nfds = 1;
fds[0].fd = sock;
fds[0].events = ZSOCK_POLLIN;
do {
k_ticks_t req_timeout_ticks =
sys_timepoint_timeout(req_end_timepoint).ticks;
int req_timeout_ms = k_ticks_to_ms_floor32(req_timeout_ticks);
ret = zsock_poll(fds, nfds, req_timeout_ms);
if (ret == 0) {
LOG_DBG("Timeout");
ret = -ETIMEDOUT;
goto error;
} else if (ret < 0) {
ret = -errno;
goto error;
}
if (fds[0].revents & ZSOCK_POLLERR) {
int sock_err;
socklen_t optlen = sizeof(sock_err);
(void)zsock_getsockopt(sock, SOL_SOCKET, SO_ERROR, &sock_err, &optlen);
ret = -sock_err;
goto error;
} else if (fds[0].revents & ZSOCK_POLLNVAL) {
ret = -EBADF;
goto error;
} else if (fds[0].revents & ZSOCK_POLLIN) {
received = zsock_recv(sock, req->internal.response.recv_buf + offset,
req->internal.response.recv_buf_len - offset, 0);
if (received == 0 && total_received == 0) {
/* Connection closed, no data received */
goto closed;
} else if (received < 0) {
ret = -errno;
goto error;
}
total_received += received;
offset += received;
/* Initialize the data length with the received data length. */
req->internal.response.data_len = offset;
/* In case of EOF on a socket, indicate this by passing
* 0 length to the parser.
*/
processed = http_parser_execute(
&req->internal.parser, &req->internal.parser_settings,
req->internal.response.recv_buf, received > 0 ? offset : 0);
if (processed > offset) {
LOG_ERR("HTTP parser error, too much data consumed");
ret = -EBADMSG;
goto error;
}
if (req->internal.parser.http_errno != HPE_OK) {
LOG_ERR("HTTP parsing error, %d",
req->internal.parser.http_errno);
ret = -EBADMSG;
goto error;
}
/* Update the response data length with the actually
* processed bytes.
*/
req->internal.response.data_len = processed;
offset -= processed;
if (offset >= req->internal.response.recv_buf_len) {
/* This means the parser did not consume any data
* and we can't fit any more in the buffer.
*/
LOG_ERR("HTTP RX buffer full, cannot proceed");
ret = -ENOMEM;
goto error;
}
if (req->internal.response.message_complete) {
http_report_complete(req);
} else {
ret = http_report_progress(req);
if (ret < 0) {
LOG_DBG("Connection aborted by the application (%d)",
ret);
return -ECONNABORTED;
}
/* Re-use the result buffer and start to fill it again */
req->internal.response.data_len = 0;
req->internal.response.body_frag_start = NULL;
req->internal.response.body_frag_len = 0;
}
if (offset > 0) {
/* In case there are any unprocessed data left,
* move them to the front of the buffer.
*/
memmove(req->internal.response.recv_buf,
req->internal.response.recv_buf + processed,
offset);
}
} else if (fds[0].revents & ZSOCK_POLLHUP) {
/* Connection closed */
goto closed;
}
} while (!req->internal.response.message_complete);
/* If there's still some data left in the buffer after HTTP processing,
* reflect this in data_len variable.
*/
req->data_len = offset;
return total_received;
closed:
LOG_DBG("Connection closed");
/* If connection was closed with no data sent, this is a NULL response, and is a special
* case valid response.
*/
if (total_received == 0) {
http_report_null(req);
return total_received;
}
/* Otherwise, connection was closed mid-way through response, and this should be
* considered an error.
*/
ret = -ECONNRESET;
error:
LOG_DBG("Connection error (%d)", ret);
return ret;
}
int http_client_req(int sock, struct http_request *req,
int32_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;
k_timeout_t req_timeout = (timeout == SYS_FOREVER_MS) ? K_FOREVER : K_MSEC(timeout);
k_timepoint_t req_end_timepoint = sys_timepoint_calc(req_timeout);
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.sock = sock;
method = http_method_str(req->method);
ret = http_send_data(sock, send_buf, send_buf_max_len, &send_buf_pos,
req_end_timepoint, method,
" ", req->url, " ", req->protocol,
HTTP_CRLF, NULL);
if (ret < 0) {
goto out;
}
total_sent += ret;
if (req->port) {
ret = http_send_data(sock, send_buf, send_buf_max_len,
&send_buf_pos, req_end_timepoint, "Host", ": ", req->host,
":", req->port, HTTP_CRLF, NULL);
if (ret < 0) {
goto out;
}
total_sent += ret;
} else {
ret = http_send_data(sock, send_buf, send_buf_max_len,
&send_buf_pos, req_end_timepoint, "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, req_end_timepoint);
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_end_timepoint,
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_end_timepoint, 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, req_end_timepoint, "Content-Type", ": ",
req->content_type_value, HTTP_CRLF, NULL);
if (ret < 0) {
goto out;
}
total_sent += ret;
}
if (req->payload || req->payload_cb) {
if (req->payload_len) {
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, req_end_timepoint,
"Content-Length", ": ",
content_len_str, HTTP_CRLF,
HTTP_CRLF, NULL);
} else {
ret = http_send_data(sock, send_buf, send_buf_max_len,
&send_buf_pos, req_end_timepoint, HTTP_CRLF, NULL);
}
if (ret < 0) {
goto out;
}
total_sent += ret;
ret = http_flush_data(sock, send_buf, send_buf_pos, req_end_timepoint);
if (ret < 0) {
goto out;
}
send_buf_pos = 0;
total_sent += ret;
if (req->payload_cb) {
ret = req->payload_cb(sock, req, user_data);
if (ret < 0) {
goto out;
}
total_sent += ret;
} else {
uint32_t length;
if (req->payload_len == 0) {
length = strlen(req->payload);
} else {
length = req->payload_len;
}
ret = sendall(sock, req->payload, length, req_end_timepoint);
if (ret < 0) {
goto out;
}
total_sent += length;
}
} else {
ret = http_send_data(sock, send_buf, send_buf_max_len,
&send_buf_pos, req_end_timepoint, HTTP_CRLF, NULL);
if (ret < 0) {
goto out;
}
total_sent += ret;
}
if (send_buf_pos > 0) {
ret = http_flush_data(sock, send_buf, send_buf_pos, req_end_timepoint);
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);
/* Request is sent, now wait data to be received */
total_recv = http_wait_data(sock, req, req_end_timepoint);
if (total_recv < 0) {
NET_DBG("Wait data failure (%d)", total_recv);
ret = total_recv;
goto out;
}
NET_DBG("Received %d bytes", total_recv);
return total_sent;
out:
return ret;
}