ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
http_client.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file http_client.h
6 * @brief Layer 7 - the user agent (RFC 9110 sec 3.5): one outbound HTTP/1.1 message exchange.
7 *
8 * RFC 9110 sec 3.5: "the term 'user agent' refers to any of the various client programs that
9 * initiate a request." This is that side of HTTP. The serving side is network_drivers/presentation/http.
10 *
11 * The exchange runs in three steps, each a call on ::HttpClient:
12 *
13 * - @ref HttpClientNs::parse_target_uri splits the target URI (RFC 9110 sec 7.1) into the authority
14 * it dials and the origin-form request target it sends (RFC 9112 sec 3.2.1).
15 * - @ref HttpClientNs::build_request writes the request-line and field lines of an HTTP-message
16 * (RFC 9112 sec 2.1, sec 3).
17 * - @ref HttpClientNs::parse_response reads the status-line (RFC 9112 sec 4) and frames the message
18 * body by the rules of RFC 9112 sec 6.3.
19 *
20 * Those three touch no socket and are unit-tested on the host. @ref HttpClientNs::get and
21 * @ref HttpClientNs::post run all three over the shared outbound transport (::TcpClient), with the
22 * TLS record layer under them for an https target URI (RFC 9112 sec 9.7).
23 *
24 * The content is returned by pointer into the module's receive buffer and is valid until the next
25 * call. One exchange at a time: the module holds one connection and one buffer.
26 *
27 * @author Douglas Quigg (dstroy0)
28 * @date 2026
29 */
30
31#ifndef PROTOCORE_HTTP_CLIENT_H
32#define PROTOCORE_HTTP_CLIENT_H
33
34#include "protocore_config.h" // the entry point: protocore_types.h for the widths
35
36#if PROTOCORE_ENABLE_HTTP_CLIENT
37
39
40/**
41 * @brief What went wrong below the status code.
42 *
43 * RFC 9110 sec 15: every status code is in the range 100..599, so a negative value can never
44 * collide with one and @ref HttpClientNs::status carries both.
45 */
46typedef enum PROTO_ENUM_PACKED
47{
48 HTTP_CLIENT_ERR_URL = -1, ///< the target URI does not parse (RFC 9110 sec 4.2.1 / 4.2.2)
49 HTTP_CLIENT_ERR_DNS = -2, ///< the uri-host did not resolve; the transport reports that as a close, so an
50 ///< exchange that fails to resolve reports ::HTTP_CLIENT_ERR_CONNECT
51 HTTP_CLIENT_ERR_CONNECT = -3, ///< the connection did not come up (RFC 9112 sec 9.1)
52 HTTP_CLIENT_ERR_TIMEOUT = -4, ///< no complete message arrived before the deadline
53 HTTP_CLIENT_ERR_SEND = -5, ///< the request message did not go out
54 HTTP_CLIENT_ERR_RESPONSE = -6, ///< the response does not parse (RFC 9112 sec 4)
55 HTTP_CLIENT_ERR_TLS = -7, ///< an https target URI with no TLS, or a failed handshake
56} HttpClientError;
57
58/**
59 * @brief RFC 9110 sec 7.1: the target URI, split into the parts a request sends.
60 *
61 * A parse writes @c host, @c path, @c port and @c https out of @c url; a build reads them back.
62 * Scheme defaults are RFC 9110 sec 4.2.1 (http, TCP port 80) and sec 4.2.2 (https, TCP port 443).
63 */
64typedef struct
65{
66 const char *url; ///< the absolute target URI, "http://..." or "https://..."
67 char *host; ///< where the uri-host goes, and what the Host field carries (RFC 9110 sec 7.2)
68 size_t host_cap; ///< how much room it has
69 char *path; ///< where the origin-form request target goes (RFC 9112 sec 3.2.1)
70 size_t path_cap; ///< how much room it has
71 uint16_t port; ///< the authority's port; 80 for http, 443 for https when the URI omits it
72 proto_bool https; ///< the scheme is "https", so the exchange runs over TLS (RFC 9112 sec 9.7)
73} HttpTargetArgs;
74
75/** @brief RFC 9112 sec 3: the request-line's method, the content it encloses, and where it is built. */
76typedef struct
77{
78 const char *method; ///< the method token (RFC 9112 sec 3.1), "GET" or "POST" here
79 const char *content_type; ///< the Content-Type field value (RFC 9110 sec 8.3); null takes application/octet-stream
80 const uint8_t *body; ///< the content the message encloses, or null
81 size_t body_len; ///< its octet count, sent as Content-Length (RFC 9110 sec 8.6)
82 char *out; ///< where the request message is written
83 size_t cap; ///< how much room it has
84} HttpRequestArgs;
85
86/** @brief RFC 9112 sec 2.1: the received message a parse frames. Chunked decoding rewrites it. */
87typedef struct
88{
89 uint8_t *buf; ///< the octets received, start-line first
90 size_t len; ///< how many
91} HttpMessageArgs;
92
93/**
94 * @brief The HTTP user agent (RFC 9110 sec 3.5).
95 *
96 * A caller sets the members a call takes, invokes it through ::HttpClient, and reads the outcome off
97 * the same handle. There is no slot member: the module runs one exchange at a time, so no call names
98 * a row.
99 *
100 * An exchange sets @c target.host, @c target.path and @c request.out to the module's own buffers
101 * before it splits the target URI, so a caller sets only @c target.url and reads the parts back.
102 *
103 * @var HttpClientNs::target the target URI a call splits, dials, or names in a request-line
104 * @var HttpClientNs::request the method, the content, and where the request message is built
105 * @var HttpClientNs::message the received message a parse frames
106 * @var HttpClientNs::ok a call's true/false outcome
107 * @var HttpClientNs::status the status-code (RFC 9112 sec 4), or a negative ::HttpClientError
108 * @var HttpClientNs::n octets a build wrote; 0 when the message would not fit
109 * @var HttpClientNs::body_off where the content starts inside the parsed message
110 * @var HttpClientNs::body_len the content's octet count (RFC 9112 sec 6.3)
111 * @var HttpClientNs::body the content an exchange read, pointing into the module's receive buffer
112 * @var HttpClientNs::parse_target_uri split the target URI into authority, port and request target
113 * @var HttpClientNs::build_request write the request-line and field lines of one HTTP-message
114 * @var HttpClientNs::parse_response read the status-line and frame the message body
115 * @var HttpClientNs::get run one GET exchange (RFC 9110 sec 9.3.1)
116 * @var HttpClientNs::post run one POST exchange (RFC 9110 sec 9.3.3)
117 */
118typedef struct
119{
120 HttpTargetArgs target; ///< the target URI and its parts (RFC 9110 sec 7.1)
121 HttpRequestArgs request; ///< what a request-line and its field lines carry (RFC 9112 sec 3)
122 HttpMessageArgs message; ///< the received message a parse frames (RFC 9112 sec 2.1)
123 proto_bool ok;
124 int32_t status;
125 size_t n;
126 size_t body_off;
127 size_t body_len;
128 const uint8_t *body;
129} HttpClientVars;
130
131/** @brief The operands and the outcome. */
132extern HttpClientVars HttpClientV;
133
134/** @brief The entries. */
135typedef struct
136{
137 void (*const parse_target_uri)(uint8_t *work);
138 void (*const build_request)(uint8_t *work);
139 void (*const parse_response)(uint8_t *work);
140 void (*const get)(uint8_t *work);
141 void (*const post)(uint8_t *work);
142} HttpClientNs;
143
144// What the table binds, defined once in the .c and taking one parameter each: everything
145// else an entry needs is an operand in HttpClientV or a region of the borrow at a fixed offset.
146void protocore_http_client_parse_target_uri(uint8_t *work);
147void protocore_http_client_build_request(uint8_t *work);
148void protocore_http_client_parse_response(uint8_t *work);
149void protocore_http_client_get(uint8_t *work);
150void protocore_http_client_post(uint8_t *work);
151
152// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
153// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
154// `HttpClient.parse_target_uri(work)` resolves to a named function and becomes a DIRECT call. An extern table
155// leaves the call indirect and the symbol live at every level, -O2 -flto included.
156static const HttpClientNs HttpClient __attribute__((unused)) = {
157 .parse_target_uri = protocore_http_client_parse_target_uri,
158 .build_request = protocore_http_client_build_request,
159 .parse_response = protocore_http_client_parse_response,
160 .get = protocore_http_client_get,
161 .post = protocore_http_client_post,
162};
163
164/**
165 * @brief The PROTOCORE_HTTP_CLIENT_BORROW bytes this module's state lives in.
166 *
167 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
168 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
169 * walks, so the state lasts the life of the program.
170 *
171 * @return the span.
172 */
173// Not under PROTOCORE_HAS_NET_STACK: the borrow is the MODULE's, the same size and offset stack or
174// no stack, and http_client.c defines this above its own capability gate for that reason. Guarding
175// the declaration while the definition is unconditional hid a symbol that exists, so a caller
176// asking a stackless build for its borrow saw no declaration. Only the entries take the capability.
177uint8_t *protocore_http_client_span(void);
178
180
181#endif // PROTOCORE_ENABLE_HTTP_CLIENT
182
183#endif // PROTOCORE_HTTP_CLIENT_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97