net: http: client: Initial version
Simple HTTP client API. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
4d59ef306b
commit
eb40499627
5 changed files with 965 additions and 2 deletions
289
include/net/http_client.h
Normal file
289
include/net/http_client.h
Normal file
|
@ -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_ */
|
|
@ -17,8 +17,7 @@ if (CONFIG_DNS_RESOLVER
|
||||||
add_subdirectory(dns)
|
add_subdirectory(dns)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CONFIG_HTTP_PARSER_URL
|
if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER OR CONFIG_HTTP_CLIENT)
|
||||||
OR CONFIG_HTTP_PARSER)
|
|
||||||
add_subdirectory(http)
|
add_subdirectory(http)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,8 @@ if(CONFIG_HTTP_PARSER_STRICT)
|
||||||
zephyr_library_compile_definitions(HTTP_PARSER_STRICT)
|
zephyr_library_compile_definitions(HTTP_PARSER_STRICT)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
zephyr_include_directories(${ZEPHYR_BASE}/subsys/net/ip)
|
||||||
|
|
||||||
zephyr_library_sources_if_kconfig(http_parser.c)
|
zephyr_library_sources_if_kconfig(http_parser.c)
|
||||||
zephyr_library_sources_if_kconfig(http_parser_url.c)
|
zephyr_library_sources_if_kconfig(http_parser_url.c)
|
||||||
|
zephyr_library_sources_if_kconfig(http_client.c)
|
||||||
|
|
|
@ -23,3 +23,16 @@ config HTTP_PARSER_STRICT
|
||||||
depends on (HTTP_PARSER || HTTP_PARSER_URL)
|
depends on (HTTP_PARSER || HTTP_PARSER_URL)
|
||||||
help
|
help
|
||||||
This option enables the strict parsing option
|
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"
|
||||||
|
|
659
subsys/net/lib/http/http_client.c
Normal file
659
subsys/net/lib/http/http_client.c
Normal file
|
@ -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;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue