2017-02-21 15:02:00 -06:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2017 Intel Corporation
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
|
|
#define SYS_LOG_DOMAIN "http/client"
|
|
|
|
#define NET_LOG_ENABLED 1
|
|
|
|
#endif
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
#include <stdlib.h>
|
2017-02-21 15:02:00 -06:00
|
|
|
#include <misc/printk.h>
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
#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)
|
|
|
|
|
2017-02-21 15:02:00 -06:00
|
|
|
/* HTTP client defines */
|
|
|
|
#define HTTP_EOF "\r\n\r\n"
|
|
|
|
|
|
|
|
#define HTTP_CONTENT_TYPE "Content-Type: "
|
|
|
|
#define HTTP_CONT_LEN_SIZE 64
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
struct waiter {
|
|
|
|
struct http_client_ctx *ctx;
|
|
|
|
struct k_sem wait;
|
|
|
|
};
|
|
|
|
|
|
|
|
int http_request(struct net_context *net_ctx, struct http_client_request *req,
|
|
|
|
s32_t timeout)
|
2017-02-21 15:02:00 -06:00
|
|
|
{
|
2017-04-25 17:58:18 +03:00
|
|
|
const char *method = http_method_str(req->method);
|
|
|
|
struct net_pkt *pkt;
|
|
|
|
int ret = -ENOMEM;
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
pkt = net_pkt_get_tx(net_ctx, timeout);
|
|
|
|
if (!pkt) {
|
2017-02-21 15:02:00 -06:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(method), (u8_t *)method,
|
|
|
|
timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
/* Space after method string. */
|
|
|
|
if (!net_pkt_append_all(pkt, 1, (u8_t *)" ", timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->url), (u8_t *)req->url,
|
|
|
|
timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->protocol),
|
|
|
|
(u8_t *)req->protocol, timeout)) {
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 out;
|
|
|
|
}
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (req->content_type_value) {
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_CONTENT_TYPE),
|
|
|
|
(u8_t *)HTTP_CONTENT_TYPE,
|
|
|
|
timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->content_type_value),
|
|
|
|
(u8_t *)req->content_type_value,
|
|
|
|
timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req->payload && req->payload_size) {
|
|
|
|
char content_len_str[HTTP_CONT_LEN_SIZE];
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
ret = snprintk(content_len_str, HTTP_CONT_LEN_SIZE,
|
|
|
|
HTTP_CRLF "Content-Length: %u"
|
|
|
|
HTTP_CRLF HTTP_CRLF,
|
|
|
|
req->payload_size);
|
|
|
|
if (ret <= 0 || ret >= HTTP_CONT_LEN_SIZE) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, ret, (u8_t *)content_len_str,
|
|
|
|
timeout)) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, req->payload_size,
|
|
|
|
(u8_t *)req->payload,
|
|
|
|
timeout)) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
} else {
|
2017-04-25 17:58:18 +03:00
|
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_EOF),
|
|
|
|
(u8_t *)HTTP_EOF,
|
|
|
|
timeout)) {
|
|
|
|
goto out;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
return net_context_send(pkt, NULL, timeout, NULL, NULL);
|
|
|
|
|
|
|
|
out:
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_header_field(size_t len, const char *str)
|
|
|
|
{
|
|
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
|
|
#define MAX_OUTPUT_LEN 128
|
|
|
|
char output[MAX_OUTPUT_LEN];
|
|
|
|
|
|
|
|
/* The value of len does not count \0 so we need to increase it
|
|
|
|
* by one.
|
|
|
|
*/
|
|
|
|
if ((len + 1) > sizeof(output)) {
|
|
|
|
len = sizeof(output) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintk(output, len + 1, "%s", str);
|
|
|
|
|
|
|
|
NET_DBG("[%zd] %s", len, output);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int on_url(struct http_parser *parser, const char *at, size_t length)
|
|
|
|
{
|
|
|
|
ARG_UNUSED(parser);
|
|
|
|
|
|
|
|
print_header_field(length, at);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int on_status(struct http_parser *parser, const char *at, size_t length)
|
|
|
|
{
|
|
|
|
struct http_client_ctx *ctx;
|
|
|
|
u16_t len;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static 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->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);
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
/* Re-use the result buffer and start to fill it again */
|
|
|
|
ctx->rsp.data_len = 0;
|
|
|
|
}
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
return 0;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
static int on_headers_complete(struct http_parser *parser)
|
2017-02-21 15:02:00 -06:00
|
|
|
{
|
2017-04-25 17:58:18 +03:00
|
|
|
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);
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
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;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
static int on_message_begin(struct http_parser *parser)
|
2017-02-21 15:02:00 -06:00
|
|
|
{
|
2017-04-25 17:58:18 +03:00
|
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
|
|
struct http_client_ctx *ctx = CONTAINER_OF(parser,
|
|
|
|
struct http_client_ctx,
|
|
|
|
parser);
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
NET_DBG("-- HTTP %s response (headers) --",
|
|
|
|
http_method_str(ctx->req.method));
|
|
|
|
#else
|
|
|
|
ARG_UNUSED(parser);
|
|
|
|
#endif
|
|
|
|
return 0;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
static int on_message_complete(struct http_parser *parser)
|
2017-02-21 15:02:00 -06:00
|
|
|
{
|
2017-04-25 17:58:18 +03:00
|
|
|
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);
|
|
|
|
}
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
k_sem_give(&ctx->req.wait);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int on_chunk_header(struct http_parser *parser)
|
|
|
|
{
|
|
|
|
ARG_UNUSED(parser);
|
|
|
|
|
|
|
|
return 0;
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
static int on_chunk_complete(struct http_parser *parser)
|
2017-02-21 15:02:00 -06:00
|
|
|
{
|
2017-04-25 17:58:18 +03:00
|
|
|
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:
|
2017-06-02 09:09:30 +03:00
|
|
|
if (pkt) {
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
}
|
2017-04-25 17:58:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2017-06-02 08:51:54 +03:00
|
|
|
|
|
|
|
/* If we are reconnecting, then make sure the source port
|
|
|
|
* is re-calculated so that the peer will not get confused
|
|
|
|
* which connection the connection is related to.
|
|
|
|
* This was seen in Linux which dropped packets when the same
|
|
|
|
* source port was for a new connection after the old connection
|
|
|
|
* was terminated.
|
|
|
|
*/
|
|
|
|
net_sin6(&ctx->tcp.local)->sin6_port = 0;
|
|
|
|
} else {
|
|
|
|
net_sin(&ctx->tcp.local)->sin_port = 0;
|
2017-04-25 17:58:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2017-02-21 15:02:00 -06:00
|
|
|
|
2017-04-25 17:58:18 +03:00
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
2017-02-21 15:02:00 -06:00
|
|
|
}
|