http: client: Create a HTTP library

Instead of separate sample application that does everything
related to HTTP client connectivity, create a HTTP client library
that hides nasty details that are related to sending HTTP methods.
After this the sample HTTP client application is very simple and
only shows how to use the client HTTP API.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2017-04-25 17:58:18 +03:00 committed by Anas Nashif
commit 70f334d9f2
21 changed files with 1570 additions and 1038 deletions

View file

@ -9,45 +9,392 @@
#if defined(CONFIG_HTTP_CLIENT)
#include <net/http_parser.h>
#include <net/net_context.h>
#define HTTP_CRLF "\r\n"
/* Is there more data to come */
enum http_final_call {
HTTP_DATA_MORE = 0,
HTTP_DATA_FINAL = 1,
};
#ifndef HTTP_PROTOCOL
#define HTTP_PROTOCOL "HTTP/1.1"
#endif
/* Some generic configuration options, these can be overriden if needed. */
#ifndef HTTP_STATUS_STR_SIZE
#define HTTP_STATUS_STR_SIZE 32
#endif
/* Default network activity timeout in seconds */
#define HTTP_NETWORK_TIMEOUT K_SECONDS(20)
/* It seems enough to hold 'Content-Length' and its value */
#define HTTP_CONTENT_LEN_SIZE 48
/* Default HTTP Header Field values for HTTP Requests if using the
* HTTP_HEADER_FIELDS define.
*/
#ifndef HTTP_ACCEPT
#define HTTP_ACCEPT "text/plain"
#endif
#ifndef HTTP_ACCEPT_ENC
#define HTTP_ACCEPT_ENC "identity"
#endif
#ifndef HTTP_ACCEPT_LANG
#define HTTP_ACCEPT_LANG "en-US"
#endif
#ifndef HTTP_CONNECTION
#define HTTP_CONNECTION "Close"
#endif
#ifndef HTTP_USER_AGENT
#define HTTP_USER_AGENT "Zephyr-HTTP-Client/1.8"
#endif
/* This can be used in http_client_send_get_req() when supplying
* extra_header_fields parameter.
*/
#ifndef HTTP_HEADER_FIELDS
#define HTTP_HEADER_FIELDS \
"Accept: " HTTP_ACCEPT HTTP_CRLF \
"Accept-Encoding: " HTTP_ACCEPT_ENC HTTP_CRLF \
"Accept-Language: " HTTP_ACCEPT_LANG HTTP_CRLF \
"User-Agent: " HTTP_USER_AGENT HTTP_CRLF \
"Connection: " HTTP_CONNECTION HTTP_CRLF
#endif
struct http_client_ctx;
/**
* @typedef http_receive_cb_t
* @brief Callback used when TCP data has been received from peer.
*
* @param ctx HTTP context.
* @param pkt Network packet.
*/
typedef void (*http_receive_cb_t)(struct http_client_ctx *ctx,
struct net_pkt *pkt);
/**
* @typedef http_response_cb_t
* @brief Callback used when a response has been received from peer.
*
* @param ctx HTTP context.
* @param data Received data buffer
* @param buflen Data buffer len (as specified by user)
* @param datalen Received data len, if this is larger than buflen,
* then some data was skipped.
* @param final_data Does this data buffer contain all the data or
* is there still more data to come.
* @param user_data A valid pointer on some user data or NULL
*/
typedef void (*http_response_cb_t)(struct http_client_ctx *ctx,
u8_t *data, size_t buflen,
size_t datalen,
enum http_final_call final_data,
void *user_data);
/**
* HTTP client context information. This contains all the data that is
* needed when doing HTTP requests.
*/
struct http_client_ctx {
struct http_parser parser;
struct http_parser_settings settings;
/** Server name */
const char *server;
#if defined(CONFIG_DNS_RESOLVER)
/** Remember the DNS query id so that it can be cancelled
* if the HTTP context is released and the query is active
* at that time.
*/
u16_t dns_id;
#endif
struct {
/** Local socket address */
struct sockaddr local;
/** Remote (server) socket address */
struct sockaddr remote;
/** IP stack network context */
struct net_context *ctx;
/** Network timeout */
s32_t timeout;
/** User can define this callback if it wants to have
* special handling of the received raw data.
*/
http_receive_cb_t receive_cb;
} tcp;
/** HTTP request information */
struct {
/**
* Semaphore to signal HTTP request completion
*/
struct k_sem wait;
/** Hostname to be used in the request */
const char *host;
/** User provided data */
void *user_data;
/** What method we used here (GET, POST, HEAD etc.)
*/
enum http_method method;
} req;
/** HTTP response information */
struct {
/** 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 response is stored, this is to be provided
* by the user.
*/
u8_t *response_buf;
/** Where the body starts.
*/
u8_t *body_start;
/** Response buffer maximum length */
size_t response_buf_len;
/** Length of the data in the result buf. If the value is
* larger than response_buf_len, then it means that the data
* is truncated and could not be fully copied into
* response_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;
} rsp;
};
/**
* HTTP client request. This contains all the data that is needed when doing
* a HTTP request.
*/
struct http_client_request {
/** The HTTP method: GET, HEAD, OPTIONS, POST */
char *method;
/** The HTTP method: GET, HEAD, OPTIONS, POST, ... */
enum http_method method;
/** The URL for this request, for example: /index.html */
char *url;
const char *url;
/** The HTTP protocol: HTTP/1.1 */
char *protocol;
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.
*/
char *header_fields;
const char *header_fields;
/** The value of the Content-Type header field, may be NULL */
char *content_type_value;
const char *content_type_value;
/** Hostname to be used in the request */
const char *host;
/** Payload, may be NULL */
char *payload;
const char *payload;
/** Payload size, may be 0 */
u16_t payload_size;
};
int http_request(struct net_context *net_ctx, s32_t timeout,
struct http_client_request *req);
/**
* @brief Generic function to send a HTTP request to the network. Normally
* applications would not need to use this function.
*
* @param net_ctx Network context.
* @param req HTTP request to perform.
* @param timeout Timeout when doing net_buf allocations.
*
* @return Return 0 if ok, and <0 if error.
*/
int http_request(struct net_context *net_ctx, struct http_client_request *req,
s32_t timeout);
int http_request_get(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields);
/**
* @brief Send a HTTP request to peer.
*
* @param http_ctx HTTP context.
* @param req HTTP request to perform.
* @param cb Callback to call when the response has been received from peer.
* @param response_buf Caller supplied buffer where the HTTP response will be
* stored
* @param response_buf_len Length of the caller suppied buffer.
* @param user_data A valid pointer on some user data or NULL
* @param timeout Amount of time to wait for a reply. If the timeout is 0,
* then we return immediately and the callback (if set) will be called later.
*
* @return Return 0 if ok, and <0 if error.
*/
int http_client_send_req(struct http_client_ctx *http_ctx,
struct http_client_request *req,
http_response_cb_t cb,
u8_t *response_buf,
size_t response_buf_len,
void *user_data,
s32_t timeout);
int http_request_head(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields);
/**
* @brief Send a HTTP GET request to peer.
*
* @param http_ctx HTTP context.
* @param url URL to use.
* @param host Host field in HTTP header. If set to NULL, then server
* name is used.
* @param extra_header_fields Any extra header fields that caller wants
* to add. This can be set to NULL. The format is "name: value\r\n"
* Example: "Accept: text/plain\r\nConnection: Close\r\n"
* @param cb Callback to call when the response has been received from peer.
* @param response_buf Caller supplied buffer where the HTTP request will be
* stored
* @param response_buf_len Length of the caller suppied buffer.
* @param user_data A valid pointer on some user data or NULL
* @param timeout Amount of time to wait for a reply. If the timeout is 0,
* then we return immediately and the callback (if set) will be called later.
*
* @return Return 0 if ok, and <0 if error.
*/
static inline int http_client_send_get_req(struct http_client_ctx *http_ctx,
const char *url,
const char *host,
const char *extra_header_fields,
http_response_cb_t cb,
u8_t *response_buf,
size_t response_buf_len,
void *user_data,
s32_t timeout)
{
struct http_client_request req = {
.method = HTTP_GET,
.url = url,
.host = host,
.protocol = " " HTTP_PROTOCOL HTTP_CRLF,
.header_fields = extra_header_fields,
};
int http_request_options(struct net_context *net_ctx, s32_t timeout,
char *url, char *header_fields);
return http_client_send_req(http_ctx, &req, cb, response_buf,
response_buf_len, user_data, timeout);
}
int http_request_post(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields, char *content_type_value,
char *payload);
/**
* @brief Send a HTTP POST request to peer.
*
* @param http_ctx HTTP context.
* @param url URL to use.
* @param host Host field in HTTP header. If set to NULL, then server
* name is used.
* @param extra_header_fields Any extra header fields that caller wants
* to add. This can be set to NULL. The format is "name: value\r\n"
* Example: "Accept: text/plain\r\nConnection: Close\r\n"
* @param content_type Content type of the data.
* @param payload Payload data.
* @param cb Callback to call when the response has been received from peer.
* @param response_buf Caller supplied buffer where the HTTP response will be
* stored
* @param response_buf_len Length of the caller suppied buffer.
* @param user_data A valid pointer on some user data or NULL
* @param timeout Amount of time to wait for a reply. If the timeout is 0,
* then we return immediately and the callback (if set) will be called later.
*
* @return Return 0 if ok, and <0 if error.
*/
static inline int http_client_send_post_req(struct http_client_ctx *http_ctx,
const char *url,
const char *host,
const char *extra_header_fields,
const char *content_type,
const char *payload,
http_response_cb_t cb,
u8_t *response_buf,
size_t response_buf_len,
void *user_data,
s32_t timeout)
{
struct http_client_request req = {
.method = HTTP_POST,
.url = url,
.host = host,
.protocol = " " HTTP_PROTOCOL HTTP_CRLF,
.header_fields = extra_header_fields,
.content_type_value = content_type,
.payload = payload,
};
return http_client_send_req(http_ctx, &req, cb, response_buf,
response_buf_len, user_data, timeout);
}
/**
* @brief Initialize user supplied HTTP context.
*
* @detail Caller can set the various fields in http_ctx after this call
* if needed.
*
* @param http_ctx HTTP context.
* @param server HTTP server address or host name. If host name is given,
* then DNS resolver support (CONFIG_DNS_RESOLVER) must be enabled. If caller
* sets the server parameter as NULL, then it no attempt is done to figure out
* the remote address and caller must set the address in http_ctx.tcp.remote
* itself.
* @param server_port HTTP server TCP port.
*
* @return Return 0 if ok, <0 if error.
*/
int http_client_init(struct http_client_ctx *http_ctx,
const char *server, u16_t server_port);
/**
* @brief Release all the resources allocated for HTTP context.
*
* @param http_ctx HTTP context.
*/
void http_client_release(struct http_client_ctx *http_ctx);
#endif
#if defined(CONFIG_HTTP_SERVER)

View file

@ -285,7 +285,7 @@ void http_parser_settings_init(struct http_parser_settings *settings);
* `parser->http_errno` on error.
*/
int http_parser_execute(struct http_parser *parser,
size_t http_parser_execute(struct http_parser *parser,
const struct http_parser_settings *settings,
const char *data, size_t len);

View file

@ -4,7 +4,7 @@
# SPDX-License-Identifier: Apache-2.0
#
BOARD ?= frdm_k64f
BOARD ?= qemu_x86
CONF_FILE ?= prj_$(BOARD).conf
include $(ZEPHYR_BASE)/Makefile.inc

View file

@ -16,17 +16,17 @@ The source code for this sample application can be found at:
Requirements
************
- Freedom Board (FRDM-K64F)
- LAN for testing purposes (Ethernet)
- QEMU
- Terminal emulator software
- HTTP Server
- DNS server (optional)
Building and Running
********************
Open the project configuration file for your platform, for example:
:file:`prj_frdm_k64f.conf` is the configuration file for the
:ref:`frdm_k64f` board. For IPv4 networks, set the following variables:
:file:`prj_qemu_x86.conf` is the configuration file for QEMU.
For IPv4 networks, set the following variables:
.. code-block:: console
@ -36,8 +36,8 @@ Open the project configuration file for your platform, for example:
IPv6 is the preferred routing technology for this sample application,
if CONFIG_NET_IPV6=y is set, the value of CONFIG_NET_IPV4=y is ignored.
In this sample application, only static IP addresses are supported,
those addresses are specified in the project configuration file,
In this sample application, both static IP addresses and DHCPv4 are supported.
Static IP addresses are specified in the project configuration file,
for example:
.. code-block:: console
@ -46,10 +46,14 @@ for example:
CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2"
are the IPv6 addresses for the HTTP client running Zephyr and the
HTTP server, respectively.
HTTP server, respectively. The application also supports DNS resolving so the
peer address is resolved automatically if host name is given, so you
can also write the HTTP server name like this:
Alternatively, the IP addresses may be specified in the
:file:`src/config.h` file.
.. code-block:: console
CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_APP_PEER_IPV6_ADDR="6.zephyr.test"
Open the :file:`src/config.h` file and set the server port
to match the HTTP server setup, for example:
@ -59,173 +63,68 @@ to match the HTTP server setup, for example:
#define SERVER_PORT 80
assumes that the HTTP server is listening at the TCP port 80.
If the default example HTTP server is used, then the default
port is 8000.
HTTP Server
===========
Setting up an HTTP server on your host computer is beyond the scope
of this document.
(We used `Apache 2 <http://httpd.apache.org/docs/2.4/getting-started.html>`_
for testing this sample application.
A very simple HTTP server is provided in net-tool project.
However, this application assumes that there is a server's
resource that can process an HTTP 1.1 POST request.
The net-tools can be downloaded from
For example, assuming that the Apache 2 server with PHP support
is used, and that the client sends a POST request with
"Content-Type = application/x-www-form-urlencoded" the following
PHP script will echo the POST payload back to the client:
https://github.com/zephyrproject-rtos/net-tools
.. code-block:: html
<html>
<head>
<title>HTTP Server POST test</title>
</head>
<body>
<?php
echo '<p>POST key/values:</p>';
foreach ($_POST as $key => $value) {
echo "<p> {$key} : {$value} </p>";
}
?>
</body>
</html>
In the machine hosting the HTTP server, this php script is at
:file:`/var/www/html/post_test.php`. However, for your test machine
this path can be different, but should be at your server's root folder.
HTTP Responses
==============
Server's responses are processed by the http_receive_cb routine
defined inside the :file:`src/http_client_rcv.c` file.
This sample application only prints the HTTP header fields via
the HTTP Parser Library, see :file:`include/net/http_parser.h`.
To process the HTTP response's body, use the HTTP Parser's callbacks
to determine where the body begins. Depending on the payload's size,
it may be necessary to traverse the network buffer's fragment chain.
See the :file:`src/http_client_rcv.c` file at line 70 for sample code
that shows how to walk the fragment chain.
FRDM K64F
=========
Open a terminal window and type:
.. code-block:: console
$ make BOARD=frdm_k64f
$ cd net-tools
$ ./http-server.sh
The FRDM K64F board is detected as a USB storage device. The board
must be mounted (i.e. to /mnt) to 'flash' the binary:
DNS setup
=========
The net-tool project provides a simple DNS resolver. You can activate
it like this if you want to test the DNS resolving with HTTP client.
Open a terminal window and type:
.. code-block:: console
$ cp outdir/frdm_k64f/zephyr.bin /mnt
$ cd net-tools
$ ./dnsmasq.sh
On Linux, use the 'dmesg' program to find the right USB device for the
FRDM serial console. Assuming that this device is ttyACM0, open a
terminal window and type:
.. code-block:: console
$ screen /dev/ttyACM0 115200
Once the binary is loaded into the FRDM board, press the RESET button.
Refer to the board documentation in Zephyr, :ref:`frdm_k64f`,
for more information about this board and how to access the FRDM
serial console under other operating systems.
Sample Output
=============
This sample application loops a specified number of times doing four
HTTP 1.1 requests and displays the header fields that were extracted
from the server's response. The four requests are:
This sample application loops a specified number of times doing several
HTTP 1.1 requests and printing some output. The requests are:
- GET "/index.html"
- HEAD "/"
- OPTIONS "/"
- OPTIONS "/index.html"
- POST "/post_test.php"
- GET "/big-file.html"
The terminal window where screen is running will show something similar
The terminal window where QEMU is running will show something similar
to the following:
.. code-block:: console
*******************************************
HTTP Client: 2001:db8::1
Connecting to: 2001:db8::2 port 80
Hostname: 2001:db8::2
HTTP Request: GET
--------- HTTP response (headers) ---------
Date: Thu, 02 Feb 2017 00:51:31 GMT
Server: Apache/2.4.10 (Debian)
Last-Modified: Sat, 28 Jan 2017 02:55:09 GMT
ETag: "3c-5471eb5db3c73"
Accept-Ranges: bytes
Content-Length: 60
Connection: close
Content-Type: text/html
HTTP server response status: OK
HTTP parser status: success
HTTP body: 60 bytes, expected: 60 bytes
*******************************************
HTTP Client: 2001:db8::1
Connecting to: 2001:db8::2 port 80
Hostname: 2001:db8::2
HTTP Request: HEAD
--------- HTTP response (headers) ---------
Date: Thu, 02 Feb 2017 00:51:37 GMT
Server: Apache/2.4.10 (Debian)
Last-Modified: Sat, 28 Jan 2017 02:55:09 GMT
ETag: "3c-5471eb5db3c73"
Accept-Ranges: bytes
Content-Length: 60
Connection: close
Content-Type: text/html
HTTP server response status: OK
HTTP parser status: success
*******************************************
HTTP Client: 2001:db8::1
Connecting to: 2001:db8::2 port 80
Hostname: 2001:db8::2
HTTP Request: OPTIONS
--------- HTTP response (headers) ---------
Date: Thu, 02 Feb 2017 00:51:43 GMT
Server: Apache/2.4.10 (Debian)
Allow: GET,HEAD,POST,OPTIONS
Content-Length: 0
Connection: close
Content-Type: text/html
HTTP server response status: OK
HTTP parser status: success
*******************************************
HTTP Client: 2001:db8::1
Connecting to: 2001:db8::2 port 80
Hostname: 2001:db8::2
HTTP Request: POST
--------- HTTP response (headers) ---------
Date: Thu, 02 Feb 2017 00:51:49 GMT
Server: Apache/2.4.10 (Debian)
Vary: Accept-Encoding
Content-Length: 231
Connection: close
Content-Type: text/html; charset=UTF-8
HTTP server response status: OK
HTTP parser status: success
[http-client] [INF] response: Received 364 bytes piece of data
[http-client] [INF] response: HTTP server response status: OK
[http-client] [INF] response: HTTP body: 178 bytes, expected: 178 bytes
[http-client] [INF] response: HTTP server response status: OK
[http-client] [INF] response: HTTP server response status: Unsupported method ('OPTIONS')
[http-client] [INF] response: Received 163 bytes piece of data
[http-client] [INF] response: HTTP server response status: OK
[http-client] [INF] response: HTTP body: 24 bytes, expected: 24 bytes
[http-client] [INF] response: Received 657 bytes piece of data
[http-client] [INF] response: Received 640 bytes piece of data
[http-client] [INF] response: Received 446 bytes piece of data
[http-client] [INF] response: HTTP server response status: OK
[http-client] [INF] response: HTTP body: 1556 bytes, expected: 1556 bytes

View file

@ -4,6 +4,8 @@ CONFIG_NETWORKING=y
CONFIG_NET_TCP=y
CONFIG_NET_ARP=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_LOG=y
CONFIG_NET_SHELL=y
CONFIG_NET_IPV6_RA_RDNSS=y
CONFIG_NET_IFACE_UNICAST_IPV4_ADDR_COUNT=3
@ -13,11 +15,11 @@ CONFIG_NET_PKT_TX_COUNT=64
CONFIG_NET_BUF_RX_COUNT=16
CONFIG_NET_BUF_TX_COUNT=16
CONFIG_NET_IPV4=n
CONFIG_NET_IPV6=y
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_IPV6=n
CONFIG_HTTP_CLIENT=y
CONFIG_HTTP_PARSER=y
CONFIG_STDOUT_CONSOLE=y
@ -27,11 +29,8 @@ CONFIG_STDOUT_CONSOLE=y
CONFIG_NET_APP_SETTINGS=y
CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_APP_MY_IPV4_ADDR="192.168.1.101"
CONFIG_NET_APP_PEER_IPV4_ADDR="192.168.1.10"
CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2"
#CONFIG_MAIN_STACK_SIZE=8192
# See the config.h file and the LINEARIZE_BUFFER define
#
#CONFIG_NET_BUF_DATA_SIZE=512
CONFIG_NET_MGMT=y
CONFIG_NET_MGMT_EVENT=y

View file

@ -12,17 +12,20 @@ CONFIG_NET_BUF_RX_COUNT=16
CONFIG_NET_BUF_TX_COUNT=16
CONFIG_NET_IPV6_RA_RDNSS=y
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=2
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4
# Enable IPv6 support
CONFIG_NET_IPV6=n
# Enable IPv4 support
CONFIG_NET_IPV4=y
CONFIG_INIT_STACKS=y
CONFIG_SYS_LOG_SHOW_COLOR=y
CONFIG_SYS_LOG_NET_LEVEL=4
#CONFIG_NET_DEBUG_HTTP=y
CONFIG_HTTP_CLIENT=y
CONFIG_HTTP_PARSER=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_NET_IPV6=y
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_APP_SETTINGS=y
CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1"
@ -30,3 +33,19 @@ CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2"
CONFIG_NET_SHELL=y
CONFIG_DNS_RESOLVER=y
CONFIG_DNS_RESOLVER_ADDITIONAL_BUF_CTR=2
CONFIG_DNS_RESOLVER_ADDITIONAL_QUERIES=2
CONFIG_DNS_RESOLVER_MAX_SERVERS=2
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_NUM_CONCUR_QUERIES=2
CONFIG_NET_MGMT=y
CONFIG_NET_MGMT_EVENT=y
# Example DNS servers running in linux using dnsmasq
CONFIG_DNS_SERVER1="192.0.2.2:5353"
CONFIG_DNS_SERVER2="[2001:db8::2]:5353"

View file

@ -5,7 +5,3 @@
#
obj-y += main.o
obj-y += tcp_client.o
obj-y += http_client.o
obj-y += http_client_rcv.o
obj-y += http_client_cb.o

View file

@ -4,71 +4,20 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <net/net_ip.h>
#define APP_REQ_TIMEOUT K_SECONDS(5)
#define APP_NAP_TIME 3000
/* The startup time needs to be longish if DHCP is enabled as setting
* DHCP up takes some time.
*/
#define APP_STARTUP_TIME K_SECONDS(20)
#define HTTP_POOL_BUF_CTR 4
#define HTTP_POOL_BUF_SIZE 1024
#define HTTP_STATUS_STR_SIZE 32
#define POST_CONTENT_TYPE "application/x-www-form-urlencoded"
#define POST_PAYLOAD "os=ZephyrRTOS&arch=" CONFIG_ARCH
/* server port */
#define SERVER_PORT 80
/* rx tx timeout */
#define HTTP_NETWORK_TIMEOUT 300
#define SERVER_PORT 8000
#ifdef CONFIG_NET_APP_SETTINGS
#ifdef CONFIG_NET_IPV6
#define LOCAL_ADDR CONFIG_NET_APP_MY_IPV6_ADDR
#if defined(CONFIG_NET_IPV6)
#define SERVER_ADDR CONFIG_NET_APP_PEER_IPV6_ADDR
#else
#define LOCAL_ADDR CONFIG_NET_APP_MY_IPV4_ADDR
#define SERVER_ADDR CONFIG_NET_APP_PEER_IPV4_ADDR
#endif
#else
#ifdef CONFIG_NET_IPV6
#define LOCAL_ADDR "2001:db8::1"
#define SERVER_ADDR "2001:db8::2"
#else
#define LOCAL_ADDR "192.168.1.101"
#define SERVER_ADDR "192.168.1.10"
#endif
#endif /* CONFIG */
/* It seems enough to hold 'Content-Length' and its value */
#define CON_LEN_SIZE 48
/* Default HTTP Header Field values for HTTP Requests */
#define ACCEPT "text/plain"
#define ACCEPT_ENC "identity"
#define ACCEPT_LANG "en-US"
#define CONNECTION "Close"
#define USER_AGENT "ZephyrHTTPClient/1.7"
#define HOST_NAME SERVER_ADDR /* or example.com, www.example.com */
#define HEADER_FIELDS "Accept: "ACCEPT"\r\n" \
"Accept-Encoding: "ACCEPT_ENC"\r\n" \
"Accept-Language: "ACCEPT_LANG"\r\n" \
"User-Agent: "USER_AGENT"\r\n" \
"Host: "HOST_NAME"\r\n" \
"Connection: "CONNECTION"\r\n"
/* Parsing and token tracking becomes a bit complicated if the
* RX buffer is fragmented. for example: an HTTP response with
* header fields that lie in two fragments. So, here we have
* two options:
*
* - Use the fragmented buffer, but increasing the fragment size
* - Linearize the buffer, it works better but consumes more memory
*
* Comment the following define to test the first case, set the
* CONFIG_NET_BUF_DATA_SIZE variable to 384 or 512. See the
* prj_frdm_k64f.conf file.
*/
#define LINEARIZE_BUFFER
#ifndef LINEARIZE_BUFFER
#if CONFIG_NET_BUF_DATA_SIZE <= 256
#error set CONFIG_NET_BUF_DATA_SIZE to 384 or 512
#endif
#endif

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "http_client.h"
#include "http_client_rcv.h"
#include "http_client_cb.h"
#include "config.h"
#include <misc/printk.h>
int http_init(struct http_client_ctx *http_ctx)
{
memset(http_ctx, 0, sizeof(struct http_client_ctx));
http_ctx->settings.on_body = on_body;
http_ctx->settings.on_chunk_complete = on_chunk_complete;
http_ctx->settings.on_chunk_header = on_chunk_header;
http_ctx->settings.on_headers_complete = on_headers_complete;
http_ctx->settings.on_header_field = on_header_field;
http_ctx->settings.on_header_value = on_header_value;
http_ctx->settings.on_message_begin = on_message_begin;
http_ctx->settings.on_message_complete = on_message_complete;
http_ctx->settings.on_status = on_status;
http_ctx->settings.on_url = on_url;
return 0;
}
int http_reset_ctx(struct http_client_ctx *http_ctx)
{
http_parser_init(&http_ctx->parser, HTTP_RESPONSE);
memset(http_ctx->http_status, 0, sizeof(http_ctx->http_status));
http_ctx->cl_present = 0;
http_ctx->content_length = 0;
http_ctx->processed = 0;
http_ctx->body_found = 0;
return 0;
}

View file

@ -1,17 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _HTTP_CLIENT_H_
#define _HTTP_CLIENT_H_
#include "http_client_types.h"
#include "http_client_rcv.h"
int http_init(struct http_client_ctx *http_ctx);
int http_reset_ctx(struct http_client_ctx *http_ctx);
#endif

View file

@ -1,133 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "http_client_cb.h"
#include "http_client_types.h"
#include <stdlib.h>
#include <stdio.h>
#define MAX_NUM_DIGITS 16
int on_url(struct http_parser *parser, const char *at, size_t length)
{
ARG_UNUSED(parser);
printf("URL: %.*s\n", (int)length, at);
return 0;
}
int on_status(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_ctx *ctx;
u16_t len;
ARG_UNUSED(parser);
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
len = min(length, sizeof(ctx->http_status) - 1);
memcpy(ctx->http_status, at, len);
ctx->http_status[len] = 0;
return 0;
}
int on_header_field(struct http_parser *parser, const char *at, size_t length)
{
char *content_len = "Content-Length";
struct http_client_ctx *ctx;
u16_t len;
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
len = strlen(content_len);
if (length >= len && memcmp(at, content_len, len) == 0) {
ctx->cl_present = 1;
}
printf("%.*s: ", (int)length, at);
return 0;
}
int on_header_value(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_ctx *ctx;
char str[MAX_NUM_DIGITS];
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
if (ctx->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;
}
ctx->content_length = num;
}
ctx->cl_present = 0;
}
printf("%.*s\n", (int)length, at);
return 0;
}
int on_body(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_ctx *ctx;
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
ctx->body_found = 1;
ctx->processed += length;
return 0;
}
int on_headers_complete(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}
int on_message_begin(struct http_parser *parser)
{
ARG_UNUSED(parser);
printf("\n--------- HTTP response (headers) ---------\n");
return 0;
}
int on_message_complete(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}
int on_chunk_header(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}
int on_chunk_complete(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _HTTP_CLIENT_CB_H_
#define _HTTP_CLIENT_CB_H_
#include <net/http_parser.h>
/*
* This are the callbacks executed by the parser. Some of them
* are only useful when parsing requests (or responses).
* Unused callbacks may be removed.
*/
int on_url(struct http_parser *parser, const char *at, size_t length);
int on_status(struct http_parser *parser, const char *at, size_t length);
int on_header_field(struct http_parser *parser, const char *at, size_t length);
int on_header_value(struct http_parser *parser, const char *at, size_t length);
int on_body(struct http_parser *parser, const char *at, size_t length);
int on_headers_complete(struct http_parser *parser);
int on_message_begin(struct http_parser *parser);
int on_message_complete(struct http_parser *parser);
int on_chunk_header(struct http_parser *parser);
int on_chunk_complete(struct http_parser *parser);
#endif

View file

@ -1,101 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "http_client_rcv.h"
#include "http_client_types.h"
#include "config.h"
#include <net/net_pkt.h>
#ifdef LINEARIZE_BUFFER
NET_BUF_POOL_DEFINE(http_pool, HTTP_POOL_BUF_CTR, HTTP_POOL_BUF_SIZE, 0, NULL);
void http_receive_cb(struct tcp_client_ctx *tcp_ctx, struct net_pkt *rx)
{
struct http_client_ctx *http_ctx;
struct net_buf *data_buf = NULL;
u16_t data_len;
u16_t offset;
int rc;
if (!rx) {
return;
}
data_buf = net_buf_alloc(&http_pool, tcp_ctx->timeout);
if (data_buf == NULL) {
goto lb_exit;
}
data_len = min(net_pkt_appdatalen(rx), HTTP_POOL_BUF_SIZE);
offset = net_pkt_get_len(rx) - data_len;
rc = net_frag_linear_copy(data_buf, rx->frags, offset, data_len);
if (rc != 0) {
rc = -ENOMEM;
goto lb_exit;
}
http_ctx = CONTAINER_OF(tcp_ctx, struct http_client_ctx, tcp_ctx);
/* The parser's error can be catched outside, reading the
* http_errno struct member
*/
http_parser_execute(&http_ctx->parser, &http_ctx->settings,
data_buf->data, data_buf->len);
lb_exit:
net_buf_unref(data_buf);
net_pkt_unref(rx);
}
#else
void http_receive_cb(struct tcp_client_ctx *tcp_ctx, struct net_pkt *rx)
{
struct http_client_ctx *http_ctx;
struct net_buf *frag = rx->frags;
u16_t offset;
if (!rx) {
return;
}
http_ctx = CONTAINER_OF(tcp_ctx, struct http_client_ctx, tcp_ctx);
offset = net_pkt_get_len(rx) - net_pkt_appdatalen(rx);
/* find the fragment */
while (frag && offset >= frag->len) {
offset -= frag->len;
frag = frag->frags;
}
while (frag) {
(void)http_parser_execute(&http_ctx->parser,
&http_ctx->settings,
frag->data + offset,
frag->len - offset);
/* after the first iteration, we set offset to 0 */
offset = 0;
/* The parser's error can be catched outside, reading the
* http_errno struct member
*/
if (http_ctx->parser.http_errno != HPE_OK) {
goto lb_exit;
}
frag = frag->frags;
}
lb_exit:
net_pkt_unref(rx);
}
#endif

View file

@ -1,15 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _HTTP_CLIENT_RCV_H_
#define _HTTP_CLIENT_RCV_H_
#include "tcp_client.h"
/* HTTP reception callback */
void http_receive_cb(struct tcp_client_ctx *tcp_ctx, struct net_pkt *rx);
#endif

View file

@ -1,37 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _HTTP_CLIENT_TYPES_H_
#define _HTTP_CLIENT_TYPES_H_
#include <net/http_parser.h>
#include "tcp_client.h"
#include "config.h"
struct http_client_ctx {
struct http_parser parser;
struct http_parser_settings settings;
struct tcp_client_ctx tcp_ctx;
u32_t content_length;
u32_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;
};
#endif

View file

@ -4,131 +4,324 @@
* SPDX-License-Identifier: Apache-2.0
*/
#if 1
#define SYS_LOG_DOMAIN "http-client"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <errno.h>
#include <misc/printk.h>
#include <net/net_core.h>
#include <net/net_ip.h>
#include <net/http.h>
#include "http_client.h"
#include "http_client_types.h"
#include "config.h"
#define POST_CONTENT_TYPE "application/x-www-form-urlencoded"
#define POST_PAYLOAD "os=ZephyrRTOS&arch="CONFIG_ARCH
#define MAX_ITERATIONS 20
#define WAIT_TIME (APP_REQ_TIMEOUT * 2)
#define MAX_ITERATIONS 100
#define RESULT_BUF_SIZE 1024
static u8_t result[RESULT_BUF_SIZE];
/*
* Note that the http_client_ctx is quite large so be careful if that is
* allocated from stack.
*/
static struct http_client_ctx http_ctx;
static void send_http_method(enum http_method method, char *url,
char *content_type, char *payload);
struct waiter {
struct http_client_ctx *ctx;
struct k_sem wait;
size_t total_len;
size_t header_len;
};
void main(void)
void panic(const char *msg)
{
int i = MAX_ITERATIONS;
int rc;
http_init(&http_ctx);
http_ctx.tcp_ctx.receive_cb = http_receive_cb;
http_ctx.tcp_ctx.timeout = HTTP_NETWORK_TIMEOUT;
rc = tcp_set_local_addr(&http_ctx.tcp_ctx, LOCAL_ADDR);
if (rc) {
printk("tcp_set_local_addr error\n");
goto lb_exit;
if (msg) {
NET_ERR("%s", msg);
}
while (i-- > 0) {
send_http_method(HTTP_GET, "/index.html", NULL, NULL);
k_sleep(APP_NAP_TIME);
send_http_method(HTTP_HEAD, "/", NULL, NULL);
k_sleep(APP_NAP_TIME);
send_http_method(HTTP_OPTIONS, "/index.html", NULL, NULL);
k_sleep(APP_NAP_TIME);
send_http_method(HTTP_POST, "/post_test.php",
POST_CONTENT_TYPE, POST_PAYLOAD);
k_sleep(APP_NAP_TIME);
for (;;) {
k_sleep(K_FOREVER);
}
}
lb_exit:
printk("\nBye!\n");
}
void print_banner(enum http_method method)
static int do_sync_http_req(struct http_client_ctx *ctx,
enum http_method method,
const char *url,
const char *content_type,
const char *payload)
{
printk("\n*******************************************\n"
"HTTP Client: %s\nConnecting to: %s port %d\n"
"Hostname: %s\nHTTP Request: %s\n",
LOCAL_ADDR, SERVER_ADDR, SERVER_PORT,
HOST_NAME, http_method_str(method));
struct http_client_request req = {};
int ret;
req.method = method;
req.url = url;
req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;
ret = http_client_send_req(ctx, &req, NULL, result, sizeof(result),
NULL, APP_REQ_TIMEOUT);
if (ret < 0) {
NET_ERR("Cannot send %s request (%d)", http_method_str(method),
ret);
goto out;
}
static void send_http_method(enum http_method method, char *url,
char *content_type, char *payload)
if (ctx->rsp.data_len > sizeof(result)) {
NET_ERR("Result buffer overflow by %zd bytes",
ctx->rsp.data_len - sizeof(result));
ret = -E2BIG;
} else {
NET_INFO("HTTP server response status: %s",
ctx->rsp.http_status);
if (ctx->parser.http_errno) {
if (method == HTTP_OPTIONS) {
/* Ignore error if OPTIONS is not found */
goto out;
}
NET_INFO("HTTP parser status: %s",
http_errno_description(ctx->parser.http_errno));
ret = -EINVAL;
goto out;
}
if (method != HTTP_HEAD) {
if (ctx->rsp.body_found) {
NET_INFO("HTTP body: %zd bytes, "
"expected: %zd bytes",
ctx->rsp.processed,
ctx->rsp.content_length);
} else {
NET_ERR("Error detected during HTTP msg "
"processing");
}
}
}
out:
return ret;
}
void response(struct http_client_ctx *ctx,
u8_t *data, size_t buflen,
size_t datalen,
enum http_final_call data_end,
void *user_data)
{
struct net_context *net_ctx;
s32_t timeout;
int rc;
struct waiter *waiter = user_data;
int ret;
print_banner(method);
if (data_end == HTTP_DATA_MORE) {
NET_INFO("Received %zd bytes piece of data", datalen);
http_reset_ctx(&http_ctx);
/* Do something with the data here. For this example
* we just ignore the received data.
*/
waiter->total_len += datalen;
if (ctx->rsp.body_start) {
/* This fragment contains the start of the body
* Note that the header length is not proper if
* the header is spanning over multiple recv
* fragments.
*/
waiter->header_len = ctx->rsp.body_start -
ctx->rsp.response_buf;
}
rc = tcp_connect(&http_ctx.tcp_ctx, SERVER_ADDR, SERVER_PORT);
if (rc) {
printk("tcp_connect error\n");
return;
}
net_ctx = http_ctx.tcp_ctx.net_ctx;
timeout = http_ctx.tcp_ctx.timeout;
waiter->total_len += datalen;
switch (method) {
case HTTP_GET:
rc = http_request_get(net_ctx, timeout, url, HEADER_FIELDS);
break;
case HTTP_POST:
rc = http_request_post(net_ctx, timeout, url, HEADER_FIELDS,
content_type, payload);
break;
case HTTP_HEAD:
rc = http_request_head(net_ctx, timeout, url, HEADER_FIELDS);
break;
case HTTP_OPTIONS:
rc = http_request_options(net_ctx, timeout, url, HEADER_FIELDS);
break;
default:
printk("Not yet implemented\n");
goto lb_exit;
NET_INFO("HTTP server response status: %s", ctx->rsp.http_status);
if (ctx->parser.http_errno) {
if (ctx->req.method == HTTP_OPTIONS) {
/* Ignore error if OPTIONS is not found */
goto out;
}
if (rc) {
printk("Send error\n");
goto lb_exit;
NET_INFO("HTTP parser status: %s",
http_errno_description(ctx->parser.http_errno));
ret = -EINVAL;
goto out;
}
/* this is async, so we wait until the reception callback
* processes the server's response (if any)
*/
k_sleep(APP_NAP_TIME);
printk("\nHTTP server response status: %s\n", http_ctx.http_status);
printk("HTTP parser status: %s\n",
http_errno_description(http_ctx.parser.http_errno));
if (method == HTTP_GET) {
if (http_ctx.body_found) {
printk("HTTP body: %u bytes, expected: %u bytes\n",
http_ctx.processed, http_ctx.content_length);
if (ctx->req.method != HTTP_HEAD && ctx->req.method != HTTP_OPTIONS) {
if (ctx->rsp.body_found) {
NET_INFO("HTTP body: %zd bytes, expected: %zd bytes",
ctx->rsp.processed, ctx->rsp.content_length);
} else {
printk("Error detected during HTTP msg processing\n");
NET_ERR("Error detected during HTTP msg processing");
}
if (waiter->total_len !=
waiter->header_len + ctx->rsp.content_length) {
NET_ERR("Error while receiving data, "
"received %zd expected %zd bytes",
waiter->total_len, waiter->header_len +
ctx->rsp.content_length);
}
}
lb_exit:
tcp_disconnect(&http_ctx.tcp_ctx);
out:
k_sem_give(&waiter->wait);
}
static int do_async_http_req(struct http_client_ctx *ctx,
enum http_method method,
const char *url,
const char *content_type,
const char *payload)
{
struct http_client_request req = {};
struct waiter waiter;
int ret;
req.method = method;
req.url = url;
req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;
k_sem_init(&waiter.wait, 0, 1);
waiter.total_len = 0;
ret = http_client_send_req(ctx, &req, response, result, sizeof(result),
&waiter, APP_REQ_TIMEOUT);
if (ret < 0 && ret != -EINPROGRESS) {
NET_ERR("Cannot send %s request (%d)", http_method_str(method),
ret);
goto out;
}
if (k_sem_take(&waiter.wait, WAIT_TIME)) {
NET_ERR("Timeout while waiting HTTP response");
http_client_release(ctx);
ret = -ETIMEDOUT;
goto out;
}
ret = 0;
out:
return ret;
}
static inline int do_sync_reqs(struct http_client_ctx *ctx, int count)
{
int ret;
/* These examples use the HTTP client API synchronously so they
* do not set the callback parameter.
*/
while (count--) {
ret = do_sync_http_req(&http_ctx, HTTP_GET, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(&http_ctx, HTTP_HEAD, "/",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(&http_ctx, HTTP_OPTIONS, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(&http_ctx, HTTP_POST, "/post_test.php",
POST_CONTENT_TYPE, POST_PAYLOAD);
if (ret < 0) {
goto out;
}
/* Note that we cannot receive data bigger than RESULT_BUF_SIZE
* if we wait the buffer synchronously. If you want to receive
* bigger data, then you need to set the callback when sending
* the HTTP request using http_client_send_req()
*/
}
out:
return ret;
}
static inline int do_async_reqs(struct http_client_ctx *ctx, int count)
{
int ret;
/* These examples use the HTTP client API asynchronously so they
* do set the callback parameter.
*/
while (count--) {
ret = do_async_http_req(&http_ctx, HTTP_GET, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(&http_ctx, HTTP_HEAD, "/",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(&http_ctx, HTTP_OPTIONS, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(&http_ctx, HTTP_POST, "/post_test.php",
POST_CONTENT_TYPE, POST_PAYLOAD);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(&http_ctx, HTTP_GET, "/big-file.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
}
out:
return ret;
}
void main(void)
{
int ret;
ret = http_client_init(&http_ctx, SERVER_ADDR, SERVER_PORT);
if (ret < 0) {
NET_ERR("HTTP init failed (%d)", ret);
panic(NULL);
}
ret = do_sync_reqs(&http_ctx, MAX_ITERATIONS);
if (ret < 0) {
goto out;
}
ret = do_async_reqs(&http_ctx, MAX_ITERATIONS);
if (ret < 0) {
goto out;
}
out:
http_client_release(&http_ctx);
NET_INFO("Done!");
}

View file

@ -1,162 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tcp_client.h"
#include "config.h"
#include <net/net_core.h>
#include <net/net_if.h>
#include <net/net_pkt.h>
#include <misc/printk.h>
static
int set_addr(struct sockaddr *sock_addr, const char *addr, u16_t server_port)
{
void *ptr = NULL;
int rc;
#ifdef CONFIG_NET_IPV6
net_sin6(sock_addr)->sin6_port = htons(server_port);
sock_addr->family = AF_INET6;
ptr = &(net_sin6(sock_addr)->sin6_addr);
rc = net_addr_pton(AF_INET6, addr, ptr);
#else
net_sin(sock_addr)->sin_port = htons(server_port);
sock_addr->family = AF_INET;
ptr = &(net_sin(sock_addr)->sin_addr);
rc = net_addr_pton(AF_INET, addr, ptr);
#endif
if (rc) {
printk("Invalid IP address: %s\n", addr);
}
return rc;
}
static
int if_addr_add(struct sockaddr *local_sock)
{
void *p = NULL;
#ifdef CONFIG_NET_IPV6
p = net_if_ipv6_addr_add(net_if_get_default(),
&net_sin6(local_sock)->sin6_addr,
NET_ADDR_MANUAL, 0);
#else
p = net_if_ipv4_addr_add(net_if_get_default(),
&net_sin(local_sock)->sin_addr,
NET_ADDR_MANUAL, 0);
#endif
if (p) {
return 0;
}
return -EINVAL;
}
int tcp_set_local_addr(struct tcp_client_ctx *ctx, const char *local_addr)
{
int rc;
rc = set_addr(&ctx->local_sock, local_addr, 0);
if (rc) {
printk("set_addr (local) error\n");
goto lb_exit;
}
rc = if_addr_add(&ctx->local_sock);
if (rc) {
printk("if_addr_add error\n");
}
lb_exit:
return rc;
}
static
void recv_cb(struct net_context *net_ctx, struct net_pkt *rx, int status,
void *data)
{
struct tcp_client_ctx *ctx = (struct tcp_client_ctx *)data;
ARG_UNUSED(net_ctx);
if (status) {
return;
}
if (rx == NULL || net_pkt_appdatalen(rx) == 0) {
goto lb_exit;
}
/* receive_cb must take ownership of the rx packet */
if (ctx->receive_cb) {
ctx->receive_cb(ctx, rx);
return;
}
lb_exit:
net_pkt_unref(rx);
}
int tcp_connect(struct tcp_client_ctx *ctx, const char *server_addr,
u16_t server_port)
{
#if CONFIG_NET_IPV6
socklen_t addr_len = sizeof(struct sockaddr_in6);
sa_family_t family = AF_INET6;
#else
socklen_t addr_len = sizeof(struct sockaddr_in);
sa_family_t family = AF_INET;
#endif
struct sockaddr server_sock;
int rc;
rc = net_context_get(family, SOCK_STREAM, IPPROTO_TCP, &ctx->net_ctx);
if (rc) {
printk("net_context_get error\n");
return rc;
}
rc = net_context_bind(ctx->net_ctx, &ctx->local_sock, addr_len);
if (rc) {
printk("net_context_bind error\n");
goto lb_exit;
}
rc = set_addr(&server_sock, server_addr, server_port);
if (rc) {
printk("set_addr (server) error\n");
goto lb_exit;
}
rc = net_context_connect(ctx->net_ctx, &server_sock, addr_len, NULL,
ctx->timeout, NULL);
if (rc) {
printk("net_context_connect error\n");
goto lb_exit;
}
(void)net_context_recv(ctx->net_ctx, recv_cb, K_NO_WAIT, ctx);
return 0;
lb_exit:
net_context_put(ctx->net_ctx);
return rc;
}
int tcp_disconnect(struct tcp_client_ctx *ctx)
{
if (ctx->net_ctx) {
net_context_put(ctx->net_ctx);
ctx->net_ctx = NULL;
}
return 0;
}

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _TCP_CLIENT_H_
#define _TCP_CLIENT_H_
#include <net/net_context.h>
#include <net/net_ip.h>
struct tcp_client_ctx {
/* IP stack network context */
struct net_context *net_ctx;
/* Local sock address */
struct sockaddr local_sock;
/* Network timeout */
s32_t timeout;
/* User defined call back*/
void (*receive_cb)(struct tcp_client_ctx *ctx, struct net_pkt *rx);
};
int tcp_set_local_addr(struct tcp_client_ctx *ctx, const char *local_addr);
int tcp_connect(struct tcp_client_ctx *ctx, const char *server_addr,
u16_t server_port);
int tcp_disconnect(struct tcp_client_ctx *ctx);
#endif

View file

@ -4,23 +4,20 @@
#
config HTTP
bool
prompt "HTTP support"
bool "HTTP support"
default n
help
This option enables the HTTP library
config HTTP_SERVER
bool
prompt "HTTP server support"
bool "HTTP server support"
default n
select HTTP
help
Enables HTTP server routines
config HTTP_HEADER_FIELD_ITEMS
int
prompt "HTTP header field max number of items"
int "HTTP header field max number of items"
depends on HTTP_SERVER
default 8
help
@ -28,16 +25,15 @@ config HTTP_HEADER_FIELD_ITEMS
application will handle
config HTTP_CLIENT
bool
prompt "HTTP client support"
bool "HTTP client support"
default n
select HTTP_PARSER
select HTTP
help
Enables HTTP client routines
config HTTP_PARSER
bool
prompt "HTTP Parser support"
bool "HTTP Parser support"
default n
select HTTP
help
@ -46,9 +42,15 @@ config HTTP_PARSER
provided by a libc implementation.
config HTTP_PARSER_STRICT
bool
prompt "HTTP strict parsing"
bool "HTTP strict parsing"
default n
depends on HTTP_PARSER
help
This option enables the strict parsing option
config NET_DEBUG_HTTP
bool "Debug HTTP"
default n
depends on HTTP && NET_LOG
help
Enables HTTP output debug messages

View file

@ -4,151 +4,857 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <net/http.h>
#if defined(CONFIG_NET_DEBUG_HTTP)
#define SYS_LOG_DOMAIN "http/client"
#define NET_LOG_ENABLED 1
#endif
#include <net/net_pkt.h>
#include <stdlib.h>
#include <misc/printk.h>
#include <net/net_core.h>
#include <net/net_pkt.h>
#include <net/dns_resolve.h>
#include <net/http.h>
#define BUF_ALLOC_TIMEOUT K_SECONDS(1)
/* HTTP client defines */
#define HTTP_PROTOCOL "HTTP/1.1"
#define HTTP_EOF "\r\n\r\n"
#define HTTP_CONTENT_TYPE "Content-Type: "
#define HTTP_CONT_LEN_SIZE 64
int http_request(struct net_context *net_ctx, s32_t timeout,
struct http_client_request *req)
{
struct net_pkt *tx;
int rc = -ENOMEM;
struct waiter {
struct http_client_ctx *ctx;
struct k_sem wait;
};
tx = net_pkt_get_tx(net_ctx, timeout);
if (!tx) {
int http_request(struct net_context *net_ctx, struct http_client_request *req,
s32_t timeout)
{
const char *method = http_method_str(req->method);
struct net_pkt *pkt;
int ret = -ENOMEM;
pkt = net_pkt_get_tx(net_ctx, timeout);
if (!pkt) {
return -ENOMEM;
}
if (!net_pkt_append_all(tx, strlen(req->method), (u8_t *)req->method,
if (!net_pkt_append_all(pkt, strlen(method), (u8_t *)method,
timeout)) {
goto lb_exit;
goto out;
}
if (!net_pkt_append_all(tx, strlen(req->url), (u8_t *)req->url,
timeout)) {
goto lb_exit;
/* Space after method string. */
if (!net_pkt_append_all(pkt, 1, (u8_t *)" ", timeout)) {
goto out;
}
if (!net_pkt_append_all(tx, strlen(req->protocol),
if (!net_pkt_append_all(pkt, strlen(req->url), (u8_t *)req->url,
timeout)) {
goto out;
}
if (!net_pkt_append_all(pkt, strlen(req->protocol),
(u8_t *)req->protocol, timeout)) {
goto lb_exit;
goto out;
}
if (!net_pkt_append_all(tx, strlen(req->header_fields),
if (req->host) {
if (!net_pkt_append_all(pkt, strlen(req->host),
(u8_t *)req->host, timeout)) {
goto out;
}
if (!net_pkt_append_all(pkt, strlen(HTTP_CRLF),
(u8_t *)HTTP_CRLF, timeout)) {
goto out;
}
}
if (req->header_fields) {
if (!net_pkt_append_all(pkt, strlen(req->header_fields),
(u8_t *)req->header_fields,
timeout)) {
goto lb_exit;
goto out;
}
}
if (req->content_type_value) {
if (!net_pkt_append_all(tx, strlen(HTTP_CONTENT_TYPE),
if (!net_pkt_append_all(pkt, strlen(HTTP_CONTENT_TYPE),
(u8_t *)HTTP_CONTENT_TYPE,
timeout)) {
goto lb_exit;
goto out;
}
if (!net_pkt_append_all(tx, strlen(req->content_type_value),
if (!net_pkt_append_all(pkt, strlen(req->content_type_value),
(u8_t *)req->content_type_value,
timeout)) {
goto lb_exit;
goto out;
}
}
if (req->payload && req->payload_size) {
char content_len_str[HTTP_CONT_LEN_SIZE];
rc = snprintk(content_len_str, HTTP_CONT_LEN_SIZE,
"\r\nContent-Length: %u\r\n\r\n",
ret = snprintk(content_len_str, HTTP_CONT_LEN_SIZE,
HTTP_CRLF "Content-Length: %u"
HTTP_CRLF HTTP_CRLF,
req->payload_size);
if (rc <= 0 || rc >= HTTP_CONT_LEN_SIZE) {
rc = -ENOMEM;
goto lb_exit;
if (ret <= 0 || ret >= HTTP_CONT_LEN_SIZE) {
ret = -ENOMEM;
goto out;
}
if (!net_pkt_append_all(tx, rc, (u8_t *)content_len_str,
if (!net_pkt_append_all(pkt, ret, (u8_t *)content_len_str,
timeout)) {
rc = -ENOMEM;
goto lb_exit;
ret = -ENOMEM;
goto out;
}
if (!net_pkt_append_all(tx, req->payload_size,
if (!net_pkt_append_all(pkt, req->payload_size,
(u8_t *)req->payload,
timeout)) {
rc = -ENOMEM;
goto lb_exit;
ret = -ENOMEM;
goto out;
}
} else {
if (!net_pkt_append_all(tx, strlen(HTTP_EOF),
if (!net_pkt_append_all(pkt, strlen(HTTP_EOF),
(u8_t *)HTTP_EOF,
timeout)) {
goto lb_exit;
goto out;
}
}
return net_context_send(tx, NULL, timeout, NULL, NULL);
return net_context_send(pkt, NULL, timeout, NULL, NULL);
lb_exit:
net_pkt_unref(tx);
out:
net_pkt_unref(pkt);
return rc;
return ret;
}
int http_request_get(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields)
static void print_header_field(size_t len, const char *str)
{
struct http_client_request req = {
.method = "GET ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
#if defined(CONFIG_NET_DEBUG_HTTP)
#define MAX_OUTPUT_LEN 128
char output[MAX_OUTPUT_LEN];
return http_request(net_ctx, timeout, &req);
/* 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;
}
int http_request_head(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields)
snprintk(output, len + 1, "%s", str);
NET_DBG("[%zd] %s", len, output);
#endif
}
static int on_url(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_request req = {
.method = "HEAD ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
ARG_UNUSED(parser);
return http_request(net_ctx, timeout, &req);
print_header_field(length, at);
return 0;
}
int http_request_options(struct net_context *net_ctx, s32_t timeout,
char *url, char *header_fields)
static int on_status(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_request req = {
.method = "OPTIONS ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
struct http_client_ctx *ctx;
u16_t len;
return http_request(net_ctx, timeout, &req);
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
len = min(length, sizeof(ctx->rsp.http_status) - 1);
memcpy(ctx->rsp.http_status, at, len);
ctx->rsp.http_status[len] = 0;
NET_DBG("HTTP response status %s", ctx->rsp.http_status);
return 0;
}
int http_request_post(struct net_context *net_ctx, s32_t timeout, char *url,
char *header_fields, char *content_type_value,
char *payload)
static int on_header_field(struct http_parser *parser, const char *at,
size_t length)
{
struct http_client_request req = {
.method = "POST ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields,
.content_type_value = content_type_value,
.payload = payload };
char *content_len = "Content-Length";
struct http_client_ctx *ctx;
u16_t len;
return http_request(net_ctx, timeout, &req);
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
len = strlen(content_len);
if (length >= len && memcmp(at, content_len, len) == 0) {
ctx->rsp.cl_present = true;
}
print_header_field(length, at);
return 0;
}
#define MAX_NUM_DIGITS 16
static int on_header_value(struct http_parser *parser, const char *at,
size_t length)
{
struct http_client_ctx *ctx;
char str[MAX_NUM_DIGITS];
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
if (ctx->rsp.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;
}
ctx->rsp.content_length = num;
}
ctx->rsp.cl_present = false;
}
print_header_field(length, at);
return 0;
}
static int on_body(struct http_parser *parser, const char *at, size_t length)
{
struct http_client_ctx *ctx = CONTAINER_OF(parser,
struct http_client_ctx,
parser);
ctx->rsp.body_found = 1;
ctx->rsp.processed += length;
NET_DBG("Processed %zd length %zd", ctx->rsp.processed, length);
if (!ctx->rsp.body_start) {
ctx->rsp.body_start = (u8_t *)at;
}
if (ctx->rsp.cb) {
NET_DBG("Calling callback for partitioned %zd len data",
ctx->rsp.data_len);
ctx->rsp.cb(ctx,
ctx->rsp.response_buf,
ctx->rsp.response_buf_len,
ctx->rsp.data_len,
HTTP_DATA_MORE,
ctx->req.user_data);
/* Re-use the result buffer and start to fill it again */
ctx->rsp.data_len = 0;
}
return 0;
}
static int on_headers_complete(struct http_parser *parser)
{
struct http_client_ctx *ctx = CONTAINER_OF(parser,
struct http_client_ctx,
parser);
if (parser->status_code >= 500 && parser->status_code < 600) {
NET_DBG("Status %d, skipping body", parser->status_code);
return 1;
}
if ((ctx->req.method == HTTP_HEAD || ctx->req.method == HTTP_OPTIONS)
&& ctx->rsp.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)
{
#if defined(CONFIG_NET_DEBUG_HTTP)
struct http_client_ctx *ctx = CONTAINER_OF(parser,
struct http_client_ctx,
parser);
NET_DBG("-- HTTP %s response (headers) --",
http_method_str(ctx->req.method));
#else
ARG_UNUSED(parser);
#endif
return 0;
}
static int on_message_complete(struct http_parser *parser)
{
struct http_client_ctx *ctx = CONTAINER_OF(parser,
struct http_client_ctx,
parser);
NET_DBG("-- HTTP %s response (complete) --",
http_method_str(ctx->req.method));
if (ctx->rsp.cb) {
ctx->rsp.cb(ctx,
ctx->rsp.response_buf,
ctx->rsp.response_buf_len,
ctx->rsp.data_len,
HTTP_DATA_FINAL,
ctx->req.user_data);
}
k_sem_give(&ctx->req.wait);
return 0;
}
static int on_chunk_header(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}
static int on_chunk_complete(struct http_parser *parser)
{
ARG_UNUSED(parser);
return 0;
}
static void http_receive_cb(struct http_client_ctx *ctx,
struct net_pkt *pkt)
{
size_t start = ctx->rsp.data_len;
size_t len = 0;
struct net_buf *frag;
int header_len;
if (!pkt) {
return;
}
/* Get rid of possible IP headers in the first fragment. */
frag = pkt->frags;
header_len = net_pkt_appdata(pkt) - frag->data;
NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt));
/* After this pull, the frag->data points directly to application data.
*/
net_buf_pull(frag, header_len);
while (frag) {
/* If this fragment cannot be copied to result buf,
* then parse what we have which will cause the callback to be
* called in function on_body(), and continue copying.
*/
if (ctx->rsp.data_len + frag->len > ctx->rsp.response_buf_len) {
/* If the caller has not supplied a callback, then
* we cannot really continue if the response buffer
* overflows. Set the data_len to mark how many bytes
* should be needed in the response_buf.
*/
if (!ctx->rsp.cb) {
ctx->rsp.data_len = net_pkt_get_len(pkt);
goto out;
}
http_parser_execute(&ctx->parser,
&ctx->settings,
ctx->rsp.response_buf + start,
len);
ctx->rsp.data_len = 0;
len = 0;
start = 0;
}
memcpy(ctx->rsp.response_buf + ctx->rsp.data_len,
frag->data, frag->len);
ctx->rsp.data_len += frag->len;
len += frag->len;
frag = frag->frags;
}
out:
/* The parser's error can be catched outside, reading the
* http_errno struct member
*/
http_parser_execute(&ctx->parser, &ctx->settings,
ctx->rsp.response_buf + start, len);
net_pkt_unref(pkt);
}
int client_reset(struct http_client_ctx *ctx)
{
http_parser_init(&ctx->parser, HTTP_RESPONSE);
memset(ctx->rsp.http_status, 0, sizeof(ctx->rsp.http_status));
ctx->rsp.cl_present = 0;
ctx->rsp.content_length = 0;
ctx->rsp.processed = 0;
ctx->rsp.body_found = 0;
ctx->rsp.body_start = NULL;
memset(ctx->rsp.response_buf, 0, ctx->rsp.response_buf_len);
ctx->rsp.data_len = 0;
return 0;
}
static void tcp_disconnect(struct http_client_ctx *ctx)
{
if (ctx->tcp.ctx) {
net_context_put(ctx->tcp.ctx);
ctx->tcp.ctx = NULL;
}
}
static void recv_cb(struct net_context *net_ctx, struct net_pkt *pkt,
int status, void *data)
{
struct http_client_ctx *ctx = data;
ARG_UNUSED(net_ctx);
if (status) {
return;
}
if (!pkt || net_pkt_appdatalen(pkt) == 0) {
goto out;
}
/* receive_cb must take ownership of the received packet */
if (ctx->tcp.receive_cb) {
ctx->tcp.receive_cb(ctx, pkt);
return;
}
out:
net_pkt_unref(pkt);
}
static int get_local_addr(struct http_client_ctx *ctx)
{
if (ctx->tcp.local.family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
struct in6_addr *dst = &net_sin6(&ctx->tcp.remote)->sin6_addr;
net_ipaddr_copy(&net_sin6(&ctx->tcp.local)->sin6_addr,
net_if_ipv6_select_src_addr(NULL, dst));
#else
return -EPFNOSUPPORT;
#endif
} else if (ctx->tcp.local.family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
struct net_if *iface = net_if_get_default();
/* For IPv4 we take the first address in the interface */
net_ipaddr_copy(&net_sin(&ctx->tcp.local)->sin_addr,
&iface->ipv4.unicast[0].address.in_addr);
#else
return -EPFNOSUPPORT;
#endif
}
return 0;
}
static int tcp_connect(struct http_client_ctx *ctx)
{
socklen_t addrlen = sizeof(struct sockaddr_in);
int ret;
if (ctx->tcp.remote.family == AF_INET6) {
addrlen = sizeof(struct sockaddr_in6);
}
ret = get_local_addr(ctx);
if (ret < 0) {
NET_DBG("Cannot get local address (%d)", ret);
return ret;
}
ret = net_context_get(ctx->tcp.remote.family, SOCK_STREAM,
IPPROTO_TCP, &ctx->tcp.ctx);
if (ret) {
NET_DBG("Get context error (%d)", ret);
return ret;
}
ret = net_context_bind(ctx->tcp.ctx, &ctx->tcp.local,
addrlen);
if (ret) {
NET_DBG("Bind error (%d)", ret);
goto out;
}
ret = net_context_connect(ctx->tcp.ctx,
&ctx->tcp.remote, addrlen,
NULL, ctx->tcp.timeout, NULL);
if (ret) {
NET_DBG("Connect error (%d)", ret);
goto out;
}
return net_context_recv(ctx->tcp.ctx, recv_cb, K_NO_WAIT, ctx);
out:
net_context_put(ctx->tcp.ctx);
return ret;
}
#if defined(CONFIG_NET_DEBUG_HTTP)
static void sprint_addr(char *buf, int len,
sa_family_t family,
struct sockaddr *addr)
{
if (family == AF_INET6) {
net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf, len);
} else if (family == AF_INET) {
net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf, len);
} else {
NET_DBG("Invalid protocol family");
}
}
#endif
static inline void print_info(struct http_client_ctx *ctx,
enum http_method method)
{
#if defined(CONFIG_NET_DEBUG_HTTP)
char local[NET_IPV6_ADDR_LEN];
char remote[NET_IPV6_ADDR_LEN];
sprint_addr(local, NET_IPV6_ADDR_LEN, ctx->tcp.local.family,
&ctx->tcp.local);
sprint_addr(remote, NET_IPV6_ADDR_LEN, ctx->tcp.remote.family,
&ctx->tcp.remote);
NET_DBG("HTTP %s (%s) %s -> %s port %d",
http_method_str(method), ctx->req.host, local, remote,
ntohs(net_sin(&ctx->tcp.remote)->sin_port));
#endif
}
int http_client_send_req(struct http_client_ctx *ctx,
struct http_client_request *req,
http_response_cb_t cb,
u8_t *response_buf,
size_t response_buf_len,
void *user_data,
s32_t timeout)
{
int ret;
if (!response_buf || response_buf_len == 0) {
return -EINVAL;
}
client_reset(ctx);
ret = tcp_connect(ctx);
if (ret) {
NET_DBG("TCP connect error (%d)", ret);
return ret;
}
if (!req->host) {
req->host = ctx->server;
}
ctx->req.host = req->host;
ctx->req.method = req->method;
ctx->req.user_data = user_data;
ctx->rsp.cb = cb;
ctx->rsp.response_buf = response_buf;
ctx->rsp.response_buf_len = response_buf_len;
print_info(ctx, ctx->req.method);
ret = http_request(ctx->tcp.ctx, req, BUF_ALLOC_TIMEOUT);
if (ret) {
NET_DBG("Send error (%d)", ret);
goto out;
}
if (timeout != 0 && k_sem_take(&ctx->req.wait, timeout)) {
ret = -ETIMEDOUT;
goto out;
}
if (timeout == 0) {
return -EINPROGRESS;
}
return 0;
out:
tcp_disconnect(ctx);
return ret;
}
#if defined(CONFIG_DNS_RESOLVER)
static void dns_cb(enum dns_resolve_status status,
struct dns_addrinfo *info,
void *user_data)
{
struct waiter *waiter = user_data;
struct http_client_ctx *ctx = waiter->ctx;
if (!(status == DNS_EAI_INPROGRESS && info)) {
return;
}
if (info->ai_family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
net_ipaddr_copy(&net_sin(&ctx->tcp.remote)->sin_addr,
&net_sin(&info->ai_addr)->sin_addr);
#else
goto out;
#endif
} else if (info->ai_family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
net_ipaddr_copy(&net_sin6(&ctx->tcp.remote)->sin6_addr,
&net_sin6(&info->ai_addr)->sin6_addr);
#else
goto out;
#endif
} else {
goto out;
}
ctx->tcp.remote.family = info->ai_family;
out:
k_sem_give(&waiter->wait);
}
#define DNS_WAIT K_SECONDS(2)
#define DNS_WAIT_SEM (DNS_WAIT + K_SECONDS(1))
static int resolve_name(struct http_client_ctx *ctx,
const char *server,
enum dns_query_type type)
{
struct waiter dns_waiter;
int ret;
dns_waiter.ctx = ctx;
k_sem_init(&dns_waiter.wait, 0, 1);
ret = dns_get_addr_info(server, type, &ctx->dns_id, dns_cb,
&dns_waiter, DNS_WAIT);
if (ret < 0) {
NET_ERR("Cannot resolve %s (%d)", server, ret);
ctx->dns_id = 0;
return ret;
}
/* Wait a little longer for the DNS to finish so that
* the DNS will timeout before the semaphore.
*/
if (k_sem_take(&dns_waiter.wait, DNS_WAIT_SEM)) {
NET_ERR("Timeout while resolving %s", server);
ctx->dns_id = 0;
return -ETIMEDOUT;
}
ctx->dns_id = 0;
if (ctx->tcp.remote.family == AF_UNSPEC) {
return -EINVAL;
}
return 0;
}
#endif /* CONFIG_DNS_RESOLVER */
static inline int set_remote_addr(struct http_client_ctx *ctx,
const char *server, u16_t server_port)
{
int ret;
#if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4)
ret = net_addr_pton(AF_INET6, server,
&net_sin6(&ctx->tcp.remote)->sin6_addr);
if (ret < 0) {
/* Could be hostname, try DNS if configured. */
#if !defined(CONFIG_DNS_RESOLVER)
NET_ERR("Invalid IPv6 address %s", server);
return -EINVAL;
#else
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_AAAA);
if (ret < 0) {
NET_ERR("Cannot resolve %s (%d)", server, ret);
return ret;
}
#endif
}
net_sin6(&ctx->tcp.remote)->sin6_port = htons(server_port);
net_sin6(&ctx->tcp.remote)->sin6_family = AF_INET6;
#endif /* IPV6 && !IPV4 */
#if defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
ret = net_addr_pton(AF_INET, server,
&net_sin(&ctx->tcp.remote)->sin_addr);
if (ret < 0) {
/* Could be hostname, try DNS if configured. */
#if !defined(CONFIG_DNS_RESOLVER)
NET_ERR("Invalid IPv4 address %s", server);
return -EINVAL;
#else
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_A);
if (ret < 0) {
NET_ERR("Cannot resolve %s (%d)", server, ret);
return ret;
}
#endif
}
net_sin(&ctx->tcp.remote)->sin_port = htons(server_port);
net_sin(&ctx->tcp.remote)->sin_family = AF_INET;
#endif /* IPV6 && !IPV4 */
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6)
ret = net_addr_pton(AF_INET, server,
&net_sin(&ctx->tcp.remote)->sin_addr);
if (ret < 0) {
ret = net_addr_pton(AF_INET6, server,
&net_sin6(&ctx->tcp.remote)->sin6_addr);
if (ret < 0) {
/* Could be hostname, try DNS if configured. */
#if !defined(CONFIG_DNS_RESOLVER)
NET_ERR("Invalid IPv4 or IPv6 address %s", server);
return -EINVAL;
#else
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_A);
if (ret < 0) {
ret = resolve_name(ctx, server,
DNS_QUERY_TYPE_AAAA);
if (ret < 0) {
NET_ERR("Cannot resolve %s (%d)",
server, ret);
return ret;
}
goto ipv6;
}
goto ipv4;
#endif /* !CONFIG_DNS_RESOLVER */
} else {
#if defined(CONFIG_DNS_RESOLVER)
ipv6:
#endif
net_sin6(&ctx->tcp.remote)->sin6_port =
htons(server_port);
net_sin6(&ctx->tcp.remote)->sin6_family = AF_INET6;
}
} else {
#if defined(CONFIG_DNS_RESOLVER)
ipv4:
#endif
net_sin(&ctx->tcp.remote)->sin_port = htons(server_port);
net_sin(&ctx->tcp.remote)->sin_family = AF_INET;
}
#endif /* IPV4 && IPV6 */
/* If we have not yet figured out what is the protocol family,
* then we cannot continue.
*/
if (ctx->tcp.remote.family == AF_UNSPEC) {
NET_ERR("Unknown protocol family.");
return -EPFNOSUPPORT;
}
return 0;
}
int http_client_init(struct http_client_ctx *ctx,
const char *server, u16_t server_port)
{
int ret;
memset(ctx, 0, sizeof(*ctx));
if (server) {
ret = set_remote_addr(ctx, server, server_port);
if (ret < 0) {
return ret;
}
ctx->tcp.local.family = ctx->tcp.remote.family;
ctx->server = server;
}
ctx->settings.on_body = on_body;
ctx->settings.on_chunk_complete = on_chunk_complete;
ctx->settings.on_chunk_header = on_chunk_header;
ctx->settings.on_headers_complete = on_headers_complete;
ctx->settings.on_header_field = on_header_field;
ctx->settings.on_header_value = on_header_value;
ctx->settings.on_message_begin = on_message_begin;
ctx->settings.on_message_complete = on_message_complete;
ctx->settings.on_status = on_status;
ctx->settings.on_url = on_url;
ctx->tcp.receive_cb = http_receive_cb;
ctx->tcp.timeout = HTTP_NETWORK_TIMEOUT;
k_sem_init(&ctx->req.wait, 0, 1);
return 0;
}
void http_client_release(struct http_client_ctx *ctx)
{
if (!ctx) {
return;
}
net_context_put(ctx->tcp.ctx);
ctx->tcp.receive_cb = NULL;
ctx->rsp.cb = NULL;
k_sem_give(&ctx->req.wait);
#if defined(CONFIG_DNS_RESOLVER)
if (ctx->dns_id) {
dns_cancel_addr_info(ctx->dns_id);
}
#endif
/* Let all the pending waiters run */
k_yield();
memset(ctx, 0, sizeof(*ctx));
}

View file

@ -2533,7 +2533,7 @@ error:
return -HTTP_PARSER_ERRNO(parser);
}
int http_parser_execute(struct http_parser *parser,
size_t http_parser_execute(struct http_parser *parser,
const struct http_parser_settings *settings,
const char *data, size_t len)
{