Skip to content

Commit

Permalink
Add a http client utility.
Browse files Browse the repository at this point in the history
Implemented using the lwip http client.

Fixes #1386
  • Loading branch information
peterharperuk committed Jan 8, 2024
1 parent 9f45e3c commit aeae442
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/rp2_common/pico_lwip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,5 +302,15 @@ if (EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH})
pico_lwip_contrib_freertos
pico_rand)

pico_add_library(pico_lwip_http_util NOFLAG)
target_sources(pico_lwip_http_util INTERFACE
${CMAKE_CURRENT_LIST_DIR}/http_client_util.c
)
pico_mirrored_target_link_libraries(pico_lwip_http_util INTERFACE
pico_lwip_http
pico_lwip_mbedtls
pico_mbedtls
)

pico_promote_common_scope_vars()
endif()
141 changes: 141 additions & 0 deletions src/rp2_common/pico_lwip/http_client_util.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include <stdio.h>
#include <string.h>
#include "pico/http_client_util.h"
#include "pico/async_context.h"
#include "lwip/altcp.h"
#include "lwip/altcp_tls.h"

#ifndef HTTP_INFO
#define HTTP_INFO printf
#endif

#ifndef HTTP_INFOC
#define HTTP_INFOC putchar
#endif

#ifndef HTTP_INFOC
#define HTTP_INFOC putchar
#endif

#ifndef HTTP_DEBUG
#ifdef NDEBUG
#define HTTP_DEBUG
#else
#define HTTP_DEBUG printf
#endif
#endif

#ifndef HTTP_ERROR
#define HTTP_ERROR printf
#endif

// Print headers to stdout
err_t http_client_header_print_fn(__unused httpc_state_t *connection, __unused void *arg, struct pbuf *hdr, u16_t hdr_len, __unused u32_t content_len) {
HTTP_INFO("\nheaders %u\n", hdr_len);
u16_t offset = 0;
while (offset < hdr->tot_len && offset < hdr_len) {
char c = (char)pbuf_get_at(hdr, offset++);
HTTP_INFOC(c);
}
return ERR_OK;
}

// Print body to stdout
err_t http_client_receive_print_fn(__unused void *arg, __unused struct altcp_pcb *conn, struct pbuf *p, err_t err) {
HTTP_INFO("\ncontent err %d\n", err);
u16_t offset = 0;
while (offset < p->tot_len) {
char c = (char)pbuf_get_at(p, offset++);
HTTP_INFOC(c);
}
return ERR_OK;
}


static err_t internal_header_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) {
assert(arg);
PICO_HTTP_REQUEST_T *req = (PICO_HTTP_REQUEST_T*)arg;
if (req->headers_fn) {
return req->headers_fn(connection, req->callback_arg, hdr, hdr_len, content_len);
}
return ERR_OK;
}

static err_t internal_recv_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) {
assert(arg);
PICO_HTTP_REQUEST_T *req = (PICO_HTTP_REQUEST_T*)arg;
if (req->recv_fn) {
return req->recv_fn(req->callback_arg, conn, p, err);
}
return ERR_OK;
}

static void internal_result_fn(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err) {
assert(arg);
PICO_HTTP_REQUEST_T *req = (PICO_HTTP_REQUEST_T*)arg;
HTTP_DEBUG("result %d len %u server_response %u err %d\n", httpc_result, rx_content_len, srv_res, err);
req->complete = true;
req->result = httpc_result;
if (req->result_fn) {
req->result_fn(req->callback_arg, httpc_result, rx_content_len, srv_res, err);
}
}

// Override altcp_tls_alloc to set sni
static struct altcp_pcb *altcp_tls_alloc_sni(void *arg, u8_t ip_type) {
assert(arg);
PICO_HTTP_REQUEST_T *req = (PICO_HTTP_REQUEST_T*)arg;
struct altcp_pcb *pcb = altcp_tls_alloc(req->tls_config, ip_type);
if (!pcb) {
HTTP_ERROR("Failed to allocate PCB\n");
return NULL;
}
mbedtls_ssl_set_hostname(altcp_tls_context(pcb), req->hostname);
return pcb;
}

// Make a http request, complete when req->complete returns true
int http_client_request_async(async_context_t *context, PICO_HTTP_REQUEST_T *req) {
#if LWIP_ALTCP
const uint16_t default_port = req->tls_config ? 443 : 80;
if (req->tls_config) {
if (!req->tls_allocator.alloc) {
req->tls_allocator.alloc = altcp_tls_alloc_sni;
req->tls_allocator.arg = req;
}
req->settings.altcp_allocator = &req->tls_allocator;
}
#else
const uint16_t default_port = 80;
#endif
req->complete = false;
req->settings.headers_done_fn = req->headers_fn ? internal_header_fn : NULL;
req->settings.result_fn = internal_result_fn;
async_context_acquire_lock_blocking(context);
err_t ret = httpc_get_file_dns(req->hostname, req->port ? req->port : default_port, req->url, &req->settings, internal_recv_fn, req, NULL);
async_context_release_lock(context);
if (ret != ERR_OK) {
HTTP_ERROR("http request failed: %d", ret);
}
return ret;
}

// Make a http request and only return when it has completed. Returns true on success
int http_client_request_sync(async_context_t *context, PICO_HTTP_REQUEST_T *req) {
assert(req);
int ret = http_client_request_async(context, req);
if (ret != 0) {
return ret;
}
while(!req->complete) {
async_context_poll(context);
async_context_wait_for_work_ms(context, 1000);
}
return req->result;
}
126 changes: 126 additions & 0 deletions src/rp2_common/pico_lwip/include/pico/http_client_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#ifndef PICO_HTTP_CLIENT_UTIL_H
#define PICO_HTTP_CLIENT_UTIL_H

#include "lwip/apps/http_client.h"

/*! \brief Parameters used to make HTTP request
* \ingroup pico_lwip
*/
typedef struct PICO_HTTP_REQUEST {
/*!
* The name of the host, e.g. www.raspberrypi.com
*/
const char *hostname;
/*!
* The url to request, e.g. /favicon.ico
*/
const char *url;
/*!
* Function to callback with headers, can be null
* @see httpc_headers_done_fn
*/
httpc_headers_done_fn headers_fn;
/*!
* Function to callback with results from the server, can be null
* @see altcp_recv_fn
*/
altcp_recv_fn recv_fn;
/*!
* Function to callback with final results of the request, can be null
* @see httpc_result_fn
*/
httpc_result_fn result_fn;
/*!
* Callback to pass to calback functions
*/
void *callback_arg;
/*!
* The port to use. A default port is chosen if this is set to zero
*/
uint16_t port;
#if LWIP_ALTCP && LWIP_ALTCP_TLS
/*!
* TLS configuration, can be null or set to a correctly configured tls configuration.
* e.g altcp_tls_create_config_client(NULL, 0) would use https without a certificate
*/
struct altcp_tls_config *tls_config;
/*!
* TLS allocator, used internall for setting TLS server name indication
*/
altcp_allocator_t tls_allocator;
#endif
/*!
* LwIP HTTP client settings
*/
httpc_connection_t settings;
/*!
* Flag to indicate when the request is complete
*/
int complete;
/*!
* Overall result of http request, only valid when complete is set
*/
httpc_result_t result;

} PICO_HTTP_REQUEST_T;

struct async_context;

/*! \brief Perform a http request asynchronously
* \ingroup pico_lwip
*
* Perform the http request asynchronously
*
* @param context async context
* @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values
* @return If zero is returned the request has been made and is complete when \em req->complete is true or the result callback has been called.
* A non-zero return value indicates an error.
*
* @see async_context
*/
int http_client_request_async(struct async_context *context, PICO_HTTP_REQUEST_T *req);

/*! \brief Perform a http request synchronously
* \ingroup pico_lwip
*
* Perform the http request synchronously
*
* @param context async context
* @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values
* @param result Returns the overall result of the http request when complete. Zero indicates success.
*/
int http_client_request_sync(struct async_context *context, PICO_HTTP_REQUEST_T *req);

/*! \brief A http header callback that can be passed to \em http_client_init or \em http_client_init_secure
* \ingroup pico_http_client
*
* An implementation of the http header callback which just prints headers to stdout
*
* @param arg argument specified on initialisation
* @param hdr header pbuf(s) (may contain data also)
* @param hdr_len length of the headers in 'hdr'
* @param content_len content length as received in the headers (-1 if not received)
* @return if != zero is returned, the connection is aborted
*/
err_t http_client_header_print_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len);

/*! \brief A http recv callback that can be passed to http_client_init or http_client_init_secure
* \ingroup pico_http_client
*
* An implementation of the http recv callback which just prints the http body to stdout
*
* @param arg argument specified on initialisation
* @param conn http client connection
* @param p body pbuf(s)
* @param err Error code in the case of an error
* @return if != zero is returned, the connection is aborted
*/
err_t http_client_receive_print_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err);

#endif

0 comments on commit aeae442

Please sign in to comment.