Skip to content

Commit 6110966

Browse files
committed
Add HTTP client examples
Examples to demonstrate HTTP, HTTPS and TLS validation Uses the lwip HTTP client Fixes #318
1 parent bf0017c commit 6110966

File tree

8 files changed

+534
-0
lines changed

8 files changed

+534
-0
lines changed

pico_w/wifi/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ else()
1616
add_subdirectory(tcp_server)
1717
add_subdirectory(freertos)
1818
add_subdirectory(udp_beacon)
19+
add_subdirectory(http_client)
1920

2021
if (NOT PICO_MBEDTLS_PATH)
2122
message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
add_executable(picow_http_client
2+
picow_http_client.c
3+
http_client.c
4+
)
5+
target_compile_definitions(picow_http_client PRIVATE
6+
WIFI_SSID=\"${WIFI_SSID}\"
7+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
8+
)
9+
target_include_directories(picow_http_client PRIVATE
10+
${CMAKE_CURRENT_LIST_DIR}
11+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
12+
)
13+
target_link_libraries(picow_http_client
14+
pico_cyw43_arch_lwip_threadsafe_background
15+
pico_lwip_http
16+
pico_stdlib
17+
pico_lwip_mbedtls
18+
pico_mbedtls
19+
)
20+
pico_add_extra_outputs(picow_http_client)
21+
22+
add_executable(picow_http_client_verify
23+
picow_http_verify.c
24+
http_client.c
25+
)
26+
target_compile_definitions(picow_http_client_verify PRIVATE
27+
WIFI_SSID=\"${WIFI_SSID}\"
28+
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
29+
# By default verification is optional (MBEDTLS_SSL_VERIFY_OPTIONAL)
30+
# Make it required for this test
31+
ALTCP_MBEDTLS_AUTHMODE=MBEDTLS_SSL_VERIFY_REQUIRED
32+
)
33+
target_include_directories(picow_http_client_verify PRIVATE
34+
${CMAKE_CURRENT_LIST_DIR}
35+
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
36+
)
37+
target_link_libraries(picow_http_client_verify
38+
pico_cyw43_arch_lwip_threadsafe_background
39+
pico_lwip_http
40+
pico_stdlib
41+
pico_lwip_mbedtls
42+
pico_mbedtls
43+
)
44+
pico_add_extra_outputs(picow_http_client_verify)
45+
46+
# Ignore warnings from lwip code
47+
set_source_files_properties(
48+
${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c
49+
PROPERTIES
50+
COMPILE_OPTIONS "-Wno-unused-result"
51+
)

pico_w/wifi/http_client/http_client.c

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include <string.h>
9+
#include "http_client.h"
10+
#include "pico/async_context.h"
11+
#include "lwip/altcp_tls.h"
12+
13+
#ifndef DEBUG_printf
14+
#define DEBUG_printf printf
15+
#endif
16+
17+
#ifndef DEBUG_putchar
18+
#define DEBUG_putchar putchar
19+
#endif
20+
21+
typedef struct HTTP_STATE {
22+
bool complete;
23+
httpc_result_t httpc_result;
24+
struct altcp_tls_config *tls_config;
25+
altcp_allocator_t tls_allocator;
26+
httpc_connection_t settings;
27+
const char *hostname;
28+
altcp_recv_fn recv_fn;
29+
bool use_https;
30+
void *arg;
31+
} HTTP_STATE_T;
32+
static HTTP_STATE_T http_state = { 0 };
33+
34+
// Print headers to stdout
35+
err_t headers_print_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) {
36+
DEBUG_printf("\nheaders %u\n", hdr_len);
37+
u16_t offset = 0;
38+
while (offset < hdr->tot_len && offset < hdr_len) {
39+
char c = (char)pbuf_get_at(hdr, offset++);
40+
DEBUG_putchar(c);
41+
}
42+
return ERR_OK;
43+
}
44+
45+
// Print body to stdout
46+
err_t receive_print_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) {
47+
DEBUG_printf("\ncontent err %d\n", err);
48+
u16_t offset = 0;
49+
while (offset < p->tot_len) {
50+
char c = (char)pbuf_get_at(p, offset++);
51+
DEBUG_putchar(c);
52+
}
53+
return ERR_OK;
54+
}
55+
56+
static void result_fn(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err) {
57+
DEBUG_printf("result %d len %u server_response %u err %d\n", httpc_result, rx_content_len, srv_res, err);
58+
http_state.complete = true;
59+
http_state.httpc_result = httpc_result;
60+
}
61+
62+
// Override altcp_tls_alloc to set sni
63+
static struct altcp_pcb *altcp_tls_alloc_sni(void *arg, u8_t ip_type) {
64+
struct altcp_pcb *pcb = altcp_tls_alloc(http_state.tls_config, ip_type);
65+
if (!pcb) {
66+
return NULL;
67+
}
68+
mbedtls_ssl_set_hostname(altcp_tls_context(pcb), http_state.hostname);
69+
return pcb;
70+
}
71+
72+
static void http_client_init_internal(const char *hostname, bool use_https, const uint8_t *cert, size_t cert_len, httpc_headers_done_fn headers_fn, altcp_recv_fn recv_fn, void *arg) {
73+
assert(hostname && !http_state.hostname);
74+
http_state.hostname = hostname;
75+
http_state.settings.headers_done_fn = headers_fn; // can be null
76+
http_state.settings.result_fn = result_fn;
77+
http_state.recv_fn = recv_fn;
78+
http_state.use_https = use_https;
79+
http_state.arg = arg;
80+
if (http_state.use_https) {
81+
if (!cert || cert_len == 0) {
82+
DEBUG_printf("Warning: https used without a certificate is insecure\n");
83+
} else {
84+
#if ALTCP_MBEDTLS_AUTHMODE != MBEDTLS_SSL_VERIFY_REQUIRED
85+
DEBUG_printf("Warning: https used without verificatation is insecure\n");
86+
#endif
87+
}
88+
http_state.tls_config = altcp_tls_create_config_client(cert, cert_len);
89+
http_state.tls_allocator.alloc = altcp_tls_alloc_sni;
90+
http_state.settings.altcp_allocator = &http_state.tls_allocator;
91+
} else {
92+
// Can't use a cert without https
93+
assert(!cert && cert_len == 0);
94+
}
95+
}
96+
97+
void http_client_init_basic(const char *hostname, httpc_headers_done_fn headers_fn, altcp_recv_fn recv_fn, void *arg) {
98+
http_client_init_internal(hostname, false, NULL, 0, headers_fn, recv_fn, arg);
99+
}
100+
101+
void http_client_init_secure(const char *hostname, httpc_headers_done_fn headers_fn, altcp_recv_fn recv_fn, void *arg, const uint8_t *cert, size_t cert_len) {
102+
http_client_init_internal(hostname, true, cert, cert_len, headers_fn, recv_fn, arg);
103+
}
104+
105+
// Make a http request, complete when http_client_run_complete returns true
106+
bool http_client_run_async(const char *url) {
107+
http_state.httpc_result = HTTPC_RESULT_ERR_UNKNOWN; // make sure we see real success
108+
http_state.complete = false;
109+
err_t ret = httpc_get_file_dns(http_state.hostname, http_state.use_https ? 443 : 80, url, &http_state.settings, http_state.recv_fn, &http_state, NULL);
110+
if (ret != ERR_OK) {
111+
DEBUG_printf("http request failed: %d", ret);
112+
return false;
113+
}
114+
return true;
115+
}
116+
117+
// Check if the http request is complete and return the result
118+
bool http_client_run_complete(httpc_result_t *result) {
119+
if (http_state.complete) {
120+
*result = http_state.httpc_result;
121+
return true;
122+
}
123+
return false;
124+
}
125+
126+
// Make a http request and only return when it has completed. Returns true on success
127+
bool http_client_run_sync(async_context_t *context, const char *url) {
128+
if (!http_client_run_async(url)) {
129+
return false;
130+
}
131+
while(!http_state.complete) {
132+
async_context_poll(context);
133+
async_context_wait_for_work_ms(context, 1000);
134+
}
135+
return http_state.httpc_result == HTTPC_RESULT_OK;
136+
}
137+
138+
// Deinitialise the http client
139+
void http_client_deinit(void) {
140+
if (http_state.tls_config) {
141+
altcp_tls_free_config(http_state.tls_config);
142+
}
143+
memset(&http_state, 0, sizeof(http_state));
144+
}

pico_w/wifi/http_client/http_client.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#ifndef PICO_HTTP_CLIENT_H
8+
#define PICO_HTTP_CLIENT_H
9+
10+
#include "lwip/apps/http_client.h"
11+
12+
/*! \brief A "header" callback that can be passed to http_client_init_basic or http_client_init_secure
13+
* \ingroup pico_http_client
14+
*
15+
* http client callback: called when the headers are received
16+
*
17+
* @param connection http client connection
18+
* @param arg argument specified when initiating the request
19+
* @param hdr header pbuf(s) (may contain data also)
20+
* @param hdr_len length of the heders in 'hdr'
21+
* @param content_len content length as received in the headers (-1 if not received)
22+
* @return if != ERR_OK is returned, the connection is aborted
23+
*
24+
* \see httpc_headers_done_fn
25+
*/
26+
err_t headers_print_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len);
27+
28+
/*! \brief A "http body" callback that can be passed to http_client_init_basic or http_client_init_secure
29+
* \ingroup pico_http_client
30+
*
31+
* http body (not the headers) are passed to this callback
32+
*
33+
* @param arg argument specified when initiating the request
34+
* @param conn http client connection
35+
* @param p body pbuf(s)
36+
* @param err Error code in the case of an error
37+
* @return if != ERR_OK is returned, the connection is aborted
38+
*/
39+
err_t receive_print_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err);
40+
41+
/*! \brief Initialise the http client to perform a basic (http) request
42+
* \ingroup pico_http_client
43+
*
44+
* Initialise the http client state to perform a basic http request
45+
*
46+
* @param hostname hostname for request
47+
* @param headers_fn function to call with headers (can be null)
48+
* @param altcp_recv_fn function to call with http body
49+
* @param arg Argument to pass to callbacks
50+
*/
51+
void http_client_init_basic(const char *hostname, httpc_headers_done_fn headers_fn, altcp_recv_fn recv_fn, void *arg);
52+
53+
/*! \brief Initialise the http client to perform a secure (https) request
54+
* \ingroup pico_http_client
55+
*
56+
* Initialise the http client state to perform a basic http request
57+
*
58+
* @param hostname hostname for request
59+
* @param headers_fn function to call with headers (can be null)
60+
* @param altcp_recv_fn function to call with http body
61+
* @param arg Argument to pass to callbacks
62+
* @param cert certificate to use for verification (can be null but this is insecure)
63+
* @param cert_len The length of the certificate data
64+
*/
65+
void http_client_init_secure(const char *hostname, httpc_headers_done_fn headers_fn, altcp_recv_fn recv_fn, void *arg, const uint8_t *cert, size_t cert_len);
66+
67+
/*! \brief Perform a http request asynchronously
68+
* \ingroup pico_http_client
69+
*
70+
* Initialise the http client state to perform a basic http request
71+
*
72+
* @param url url of request. Include initiial forward slash
73+
* @return true when request has been made, @see http_client_run_complete
74+
*
75+
* @see async_context
76+
*/
77+
bool http_client_run_async(const char *url);
78+
79+
/*! \brief Check if http request is complete
80+
* \ingroup pico_http_client
81+
*
82+
* Returns true when the http request is complete
83+
*
84+
* @param result Returns the result of the request when complete
85+
* @return true if http request complete and result is set
86+
*
87+
* @see async_context
88+
*/
89+
bool http_client_run_complete(httpc_result_t *result);
90+
91+
struct async_context;
92+
93+
/*! \brief Perform a http request synchronously
94+
* \ingroup pico_http_client
95+
*
96+
* Initialise the http client state to perform a basic http request
97+
*
98+
* @param context async context
99+
* @param url url of request. Include initiial forward slash
100+
*
101+
* @see async_context
102+
*/
103+
bool http_client_run_sync(struct async_context *context, const char *url);
104+
105+
/*! \brief Deinitialise the http client state
106+
* \ingroup pico_http_client
107+
*
108+
* Cleanup the http client state for new requests
109+
*/
110+
void http_client_deinit(void);
111+
112+
#endif

pico_w/wifi/http_client/lwipopts.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef _LWIPOPTS_H
2+
#define _LWIPOPTS_H
3+
4+
// Generally you would define your own explicit list of lwIP options
5+
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html)
6+
//
7+
// This example uses a common include to avoid repetition
8+
#include "lwipopts_examples_common.h"
9+
10+
/* TCP WND must be at least 16 kb to match TLS record size
11+
or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
12+
#undef TCP_WND
13+
#define TCP_WND 16384
14+
15+
#define LWIP_ALTCP 1
16+
17+
// If you don't want to use TLS (just a http request) you can avoid linking to mbedtls and remove the following
18+
#define LWIP_ALTCP_TLS 1
19+
#define LWIP_ALTCP_TLS_MBEDTLS 1
20+
21+
// Note bug in lwip with LWIP_ALTCP and LWIP_DEBUG
22+
// https://savannah.nongnu.org/bugs/index.php?62159
23+
//#define LWIP_DEBUG 1
24+
#undef LWIP_DEBUG
25+
#define ALTCP_MBEDTLS_DEBUG LWIP_DBG_ON
26+
27+
#endif

0 commit comments

Comments
 (0)