12#if DETWS_ENABLE_HTTP_CLIENT
23bool http_client_parse_url(
const char *url,
bool *is_https,
char *host,
size_t host_cap, uint16_t *port,
char *path,
26 if (!url || !is_https || !host || !port || !path)
30 if (strncmp(p,
"https://", 8) == 0)
36 else if (strncmp(p,
"http://", 7) == 0)
47 while (*p && *p !=
':' && *p !=
'/')
49 size_t hlen = (size_t)(p - h);
50 if (hlen == 0 || hlen >= host_cap)
52 memcpy(host, h, hlen);
59 if (*p <
'0' || *p >
'9')
62 while (*p >=
'0' && *p <=
'9')
64 pn = pn * 10 + (uint32_t)(*p++ -
'0');
81 size_t plen = strnlen(p, path_cap + 1);
84 memcpy(path, p, plen + 1);
89size_t http_client_build_request(
const char *method,
const char *host, uint16_t port,
const char *path,
90 const char *content_type,
const uint8_t *body,
size_t body_len,
char *out,
size_t cap)
92 if (!method || !host || !path || !out || cap == 0)
97 if (port == 80 || port == 443)
98 snprintf(hosthdr,
sizeof(hosthdr),
"%s", host);
100 snprintf(hosthdr,
sizeof(hosthdr),
"%s:%u", host, (
unsigned)port);
103 if (body && body_len)
105 n = snprintf(out, cap,
108 "User-Agent: DetWS\r\n"
109 "Content-Type: %s\r\n"
110 "Content-Length: %u\r\n"
111 "Connection: close\r\n\r\n",
112 method, path, hosthdr, content_type ? content_type : DET_MIME_OCTET_STREAM, (unsigned)body_len);
113 if (n < 0 || (
size_t)n + body_len > cap)
115 memcpy(out + n, body, body_len);
116 return (
size_t)n + body_len;
119 n = snprintf(out, cap,
122 "User-Agent: DetWS\r\n"
123 "Connection: close\r\n\r\n",
124 method, path, hosthdr);
125 if (n < 0 || (
size_t)n >= cap)
132static const char *find_header(
const uint8_t *buf,
const uint8_t *end,
const char *name)
134 size_t nlen = strnlen(name, (
size_t)(end - buf) + 1);
135 const uint8_t *p = buf;
136 while (p + nlen + 1 < end)
139 if (strncasecmp((
const char *)p, name, nlen) == 0 && p[nlen] ==
':')
141 const uint8_t *v = p + nlen + 1;
142 while (v < end && (*v ==
' ' || *v ==
'\t'))
144 return (
const char *)v;
147 while (p < end && *p !=
'\n')
155int http_client_parse_response(uint8_t *buf,
size_t len,
size_t *body_off,
size_t *body_len)
157 if (!buf || len < 12 || memcmp(buf,
"HTTP/1.", 7) != 0)
158 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
161 const uint8_t *sp = (
const uint8_t *)memchr(buf,
' ', len);
162 if (!sp || sp + 4 > buf + len)
163 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
164 int status = (sp[1] -
'0') * 100 + (sp[2] -
'0') * 10 + (sp[3] -
'0');
165 if (status < 100 || status > 599)
166 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
169 uint8_t *hdr_end =
nullptr;
170 for (
size_t i = 0; i + 3 < len; i++)
171 if (buf[i] ==
'\r' && buf[i + 1] ==
'\n' && buf[i + 2] ==
'\r' && buf[i + 3] ==
'\n')
177 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
179 size_t off = (size_t)(hdr_end + 4 - buf);
180 size_t avail = len - off;
182 const char *te = find_header(buf, hdr_end,
"Transfer-Encoding");
183 if (te && strncasecmp(te,
"chunked", 7) == 0)
186 size_t in = off, out = off;
192 while (in < len && buf[in] !=
'\r')
194 uint8_t c = buf[in++];
196 if (c >=
'0' && c <=
'9')
198 else if (c >=
'a' && c <=
'f')
200 else if (c >=
'A' && c <=
'F')
204 csz = csz * 16 + (size_t)d;
208 while (in < len && buf[in] !=
'\n')
221 memmove(buf + out, buf + in, csz);
225 while (in < len && (buf[in] ==
'\r' || buf[in] ==
'\n'))
229 *body_len = out - off;
233 const char *cl = find_header(buf, hdr_end,
"Content-Length");
239 size_t want = (size_t)n;
241 *body_len = (want < avail) ? want : avail;
259#if DETWS_ENABLE_HTTP_CLIENT_TLS
261#include <mbedtls/ssl.h>
266#ifdef DETWS_HTTP_CLIENT_DEBUG
267#define CL_DBG(...) printf(__VA_ARGS__)
269#define CL_DBG(...) ((void)0)
281static HttpClientCtx s_http;
283#if DETWS_ENABLE_HTTP_CLIENT_TLS
286static int cl_tls_send(
void *ctx,
const unsigned char *buf,
size_t len)
289 size_t cap = len > 0xFFFF ? 0xFFFF : len;
292static int cl_tls_recv(
void *ctx,
unsigned char *buf,
size_t len)
303static int http_request(
const char *method,
const char *url,
const char *content_type,
const uint8_t *body,
304 size_t body_len, HttpClientResult *out)
314 char host[80], path[160];
316 if (!http_client_parse_url(url, &is_https, host,
sizeof(host), &port, path,
sizeof(path)))
317 return (
int)HttpClientError::HTTP_CLIENT_ERR_URL;
318 CL_DBG(
"[hc] url host=%s port=%u https=%d path=%s\n", host, (
unsigned)port, (
int)is_https, path);
319#if !DETWS_ENABLE_HTTP_CLIENT_TLS
321 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
326 size_t reqlen = http_client_build_request(method, host, port, path, content_type, body, body_len, req,
sizeof(req));
328 return (
int)HttpClientError::HTTP_CLIENT_ERR_URL;
334 CL_DBG(
"[hc] det_client_open cid=%d\n", s_http.cid);
336 return (s_http.cid == -2) ? (int)HttpClientError::HTTP_CLIENT_ERR_DNS
337 : (int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
345#if DETWS_ENABLE_HTTP_CLIENT_TLS
346 int rc = det_tls_client_run(host, (
const uint8_t *)req, reqlen, s_http.rx,
sizeof(s_http.rx), &resp_len,
347 cl_tls_send, cl_tls_recv, deadline);
348 CL_DBG(
"[hc] tls rc=%d pt_len=%u\n", rc, (
unsigned)resp_len);
353 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
358 return (
int)HttpClientError::HTTP_CLIENT_ERR_TLS;
369 return (
int)HttpClientError::HTTP_CLIENT_ERR_SEND;
371 while ((int32_t)(deadline - millis()) > 0)
373 size_t n =
det_client_read(s_http.cid, s_http.rx + resp_len,
sizeof(s_http.rx) - resp_len);
375 if (resp_len >=
sizeof(s_http.rx))
384 CL_DBG(
"[hc] done resp_len=%u\n", (
unsigned)resp_len);
389 return (
int)HttpClientError::HTTP_CLIENT_ERR_TIMEOUT;
391 size_t body_off = 0, blen = 0;
392 int status = http_client_parse_response(s_http.rx, resp_len, &body_off, &blen);
394 return (
int)HttpClientError::HTTP_CLIENT_ERR_RESPONSE;
397 out->status = status;
398 out->body = s_http.rx + body_off;
399 out->body_len = blen;
404int http_get(
const char *url, HttpClientResult *out)
406 return http_request(
"GET", url,
nullptr,
nullptr, 0, out);
409int http_post(
const char *url,
const char *content_type,
const uint8_t *body,
size_t body_len, HttpClientResult *out)
411 return http_request(
"POST", url, content_type, body, body_len, out);
414void http_client_set_ca(
const uint8_t *ca,
size_t ca_len)
416#if DETWS_ENABLE_HTTP_CLIENT_TLS
417 det_tls_client_set_ca(ca, ca_len);
423void http_client_set_pin(
const uint8_t sha256[32])
425#if DETWS_ENABLE_HTTP_CLIENT_TLS
426 det_tls_client_set_pin(sha256);
431void http_client_clear_verify()
433#if DETWS_ENABLE_HTTP_CLIENT_TLS
434 det_tls_client_clear_verify();
440int http_get(
const char *, HttpClientResult *)
442 return (
int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
444int http_post(
const char *,
const char *,
const uint8_t *,
size_t, HttpClientResult *)
446 return (
int)HttpClientError::HTTP_CLIENT_ERR_CONNECT;
448void http_client_set_ca(
const uint8_t *,
size_t)
451void http_client_set_pin(
const uint8_t *)
454void http_client_clear_verify()
#define DETWS_HTTP_CLIENT_TIMEOUT_MS
Outbound HTTP client connect/response timeout in milliseconds.
#define DETWS_HTTP_CLIENT_BUF_SIZE
Receive buffer (and max response size) for the outbound HTTP client, bytes.
size_t det_client_available(int)
Wire bytes currently buffered and ready to read.
bool det_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int det_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t det_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
void det_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
bool det_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Zero-heap outbound HTTP(S) client over raw lwIP (DETWS_ENABLE_HTTP_CLIENT).
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
long det_strtol(const char *s, const char **end)
Parse a base-10 long; sets end past the digits (or to s if none).
Deterministic TLS engine: mbedTLS over a static memory pool (DETWS_ENABLE_TLS).