Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
HttpAsyncClient.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "HttpConnection.h"
5#include "HttpExport.h"
6#include "HttpURLParser.h"
7
8namespace SC
9{
12
13struct HttpWebSocketTransportView;
22struct SC_HTTP_EXPORT HttpAsyncClientTransportSetup
23{
24 HttpConnectionBase* connection = nullptr;
25 AsyncEventLoop* eventLoop = nullptr;
26
27 const HttpURLParser* url = nullptr;
28
29 SocketDescriptor::Handle nativeSocket = SocketDescriptor::Invalid;
30
31 Function<void(Result)> complete;
32 Function<void(Result)> fail;
33};
34
41{
42 HttpConnectionBase* connection = nullptr;
43 AsyncEventLoop* eventLoop = nullptr;
44
45 const HttpURLParser* url = nullptr;
46
47 Function<void(Result)> complete;
48 Function<void(Result)> fail;
49};
50
51template <int ReadQueue, int WriteQueue, int HeaderBytes, int StreamBytes>
52struct SC_HTTP_EXPORT HttpAsyncClientConnection
53 : public HttpStaticConnection<ReadQueue, WriteQueue, HeaderBytes, StreamBytes, 8, HttpConnectionBase>
54{
55 static constexpr int ExtraBuffers = 8;
56
58 {
59 this->readableSocketStream.setAutoDestroy(false);
60 this->writableSocketStream.setAutoDestroy(false);
61 }
62};
63
84struct SC_HTTP_EXPORT HttpAsyncClient
85{
86 struct Header
87 {
88 StringSpan name;
89 StringSpan value;
90 };
91
93 {
94 enum class BodyMode : uint8_t
95 {
96 None,
97 Span,
98 Stream,
99 Multipart,
100 };
101
102 HttpParser::Method method = HttpParser::Method::HttpGET;
103 StringSpan url;
104
105 Span<const Header> headers;
106
107 Span<const char> body;
108 AsyncReadableStream* bodyStream = nullptr;
109 HttpMultipartWriter* multipartWriter = nullptr;
110
111 uint64_t bodyLength = 0;
112
113 BodyMode bodyMode = BodyMode::None;
114 bool keepAlive = false;
115
116 RequestOptions& setRequest(HttpParser::Method newMethod, StringSpan newURL, bool newKeepAlive = false)
117 {
118 method = newMethod;
119 url = newURL;
120 keepAlive = newKeepAlive;
121 return *this;
122 }
123
124 RequestOptions& setHeaders(Span<const Header> newHeaders)
125 {
126 headers = newHeaders;
127 return *this;
128 }
129
130 RequestOptions& setKeepAlive(bool newKeepAlive = true)
131 {
132 keepAlive = newKeepAlive;
133 return *this;
134 }
135
136 RequestOptions& clearBody()
137 {
138 body = {};
139 bodyStream = nullptr;
140 multipartWriter = nullptr;
141 bodyLength = 0;
142 bodyMode = BodyMode::None;
143 return *this;
144 }
145
146 RequestOptions& setBody(Span<const char> newBody)
147 {
148 clearBody();
149 body = newBody;
150 bodyLength = newBody.sizeInBytes();
151 bodyMode = BodyMode::Span;
152 return *this;
153 }
154
155 RequestOptions& setBody(StringSpan newBody) { return setBody(newBody.toCharSpan()); }
156
157 RequestOptions& setBody(const char* newBody)
158 {
159 return setBody(StringSpan::fromNullTerminated(newBody, StringEncoding::Ascii));
160 }
161
162 RequestOptions& setBody(AsyncReadableStream& newBodyStream, uint64_t newBodyLength)
163 {
164 clearBody();
165 bodyStream = &newBodyStream;
166 bodyLength = newBodyLength;
167 bodyMode = BodyMode::Stream;
168 return *this;
169 }
170
171 RequestOptions& setMultipart(HttpMultipartWriter& newMultipartWriter)
172 {
173 clearBody();
174 multipartWriter = &newMultipartWriter;
175 bodyMode = BodyMode::Multipart;
176 return *this;
177 }
178 };
179
182 Result init(HttpConnectionBase& storage);
183
185 Result close();
186
192 void setResponseDecompression(SyncZLibTransformStream& decoder) { responseDecoder = &decoder; }
193
195 void clearResponseDecompression() { responseDecoder = nullptr; }
196
201 void setTransportSetup(Function<Result(HttpAsyncClientTransportSetup&)>&& setup) { transportSetup = move(setup); }
202
206 void setTransportPreflight(Function<Result(const HttpURLParser&)>&& preflight)
207 {
208 transportPreflight = move(preflight);
209 }
210
215 void setExternalConnector(Function<Result(HttpAsyncClientExternalConnection&)>&& connector)
216 {
217 externalConnector = move(connector);
218 }
219
223 void setTransportClose(Function<void()>&& close) { transportClose = move(close); }
224
230 void setTransportShutdown(Function<Result(Function<void(Result)>)>&& shutdown)
231 {
232 transportShutdown = move(shutdown);
233 }
234
237 {
238 transportPreflight = {};
239 externalConnector = {};
240 transportSetup = {};
241 transportShutdown = {};
242 transportClose = {};
243 }
244
247
251 Result start(AsyncEventLoop& loop, HttpParser::Method method, StringSpan url, bool keepAlive = false);
252
255 Result sendRequest(AsyncEventLoop& loop, const RequestOptions& options);
256
258 Result get(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
259
261 Result head(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
262
264 Result options(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
265
267 Result deleteRequest(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
268
270 Result put(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
271 Result put(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
272 {
273 return put(loop, url, body.toCharSpan(), keepAlive);
274 }
275
277 Result post(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
278 Result post(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
279 {
280 return post(loop, url, body.toCharSpan(), keepAlive);
281 }
282
284 Result patch(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
285 Result patch(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
286 {
287 return patch(loop, url, body.toCharSpan(), keepAlive);
288 }
289
291 Result postMultipart(AsyncEventLoop& loop, StringSpan url, HttpMultipartWriter& writer, bool keepAlive = false);
292
293 [[nodiscard]] HttpAsyncClientResponse& getResponse() { return response; }
294 [[nodiscard]] const HttpAsyncClientResponse& getResponse() const { return response; }
295
298
301
303 Function<void(Result)> onError;
304
305 private:
306 struct RequestPreset
307 {
308 enum class BodyMode : uint8_t
309 {
310 None,
311 Span,
312 Stream,
313 Multipart,
314 };
315
316 HttpParser::Method method = HttpParser::Method::HttpGET;
317
318 StringSpan url;
319
320 bool keepAlive = false;
321 bool autoSend = false;
322
323 Span<const char> bodySpan;
324
325 BodyMode bodyMode = BodyMode::None;
326 uint64_t contentLength = 0;
327
328 Span<const Header> headers;
329
330 AsyncReadableStream* bodyStream = nullptr;
331 HttpMultipartWriter* multipartWriter = nullptr;
332 };
333
334 enum class State : uint8_t
335 {
336 Idle,
337 Connecting,
338 Sending,
339 WaitingResponse,
340 StreamingResponse,
341 };
342
343 Result startRequest(AsyncEventLoop& loop, const RequestPreset& preset);
344 Result prepareRequest(const RequestPreset& preset);
345 Result startPreparedRequest(const RequestPreset& preset);
346 Result ensureConnected();
347 Result closeConnectionForReconnect();
348 Result beginReconnectClose();
349 Result continueReconnectAfterClose();
350 void clearReconnectCloseListeners();
351 Result beginExternalConnection();
352 Result beginSocketConnection();
353 Result beginResponseRead();
354 Result beginRequestSend();
355 Result onResponseBodyStreamRead();
356 Result validateActiveRequest() const;
357
358 void completeTransportSetup(Result result);
359 [[nodiscard]] Result rememberConnectedOrigin();
360
361 void finalizeResponse(bool finishBodyStream);
362 void closeConnection(bool preserveReconnectListeners = false);
363 void finishResponse();
364 void fail(Result error);
365
366 void onReconnectReadableClosed();
367 void onReconnectWritableClosed();
368 void onReconnectSocketReadableClosed();
369 void onReconnectSocketWritableClosed();
370 void onReconnectTransportShutdown(Result result);
371
372 void onConnected(AsyncSocketConnect::Result& result);
373 void onReadableError(Result result);
374 void onWritableError(Result result);
375 void onPipelineError(Result result);
376 void onReadableEnd();
377 void onHeadersBufferWritten(AsyncBufferView::ID bufferID);
378 void onResponseData(AsyncBufferView::ID bufferID);
379 void onResponseBodyData(AsyncBufferView::ID bufferID);
380 void onCompressedResponseBodyData(AsyncBufferView::ID bufferID);
381 void onCompressedResponseBodyWritten(AsyncBufferView::ID bufferID);
382 void onCompressedResponseBodyEnd();
383 void onCompressedResponseError(Result result);
384
385 [[nodiscard]] bool canReuseConnectionFor(StringSpan protocol, StringSpan host, uint16_t port) const;
386 [[nodiscard]] bool responseMustNotHaveBody() const;
387 [[nodiscard]] bool responseHasKnownLength() const;
388 [[nodiscard]] Result prepareResponseDecompression();
389 [[nodiscard]] Result startResponseStreams();
390 void detachResponseDecompression();
391
392 HttpConnectionBase* connection = nullptr;
393
394 AsyncEventLoop* eventLoop = nullptr;
395 HttpAsyncClientRequest* currentRequest = nullptr;
396
397 HttpAsyncClientRequest request;
398 HttpAsyncClientResponse response;
399 AsyncSocketConnect connectAsync;
400 RequestPreset currentPreset;
401
402 SyncZLibTransformStream* responseDecoder = nullptr;
403 bool responseDecoderActive = false;
404
405 Function<Result(const HttpURLParser&)> transportPreflight;
406 Function<Result(HttpAsyncClientExternalConnection&)> externalConnector;
407 Function<Result(HttpAsyncClientTransportSetup&)> transportSetup;
408 Function<Result(Function<void(Result)>)> transportShutdown;
409 Function<void()> transportClose;
410
411 State state = State::Idle;
412
413 StringSpan currentProtocol;
414 char currentProtocolStorage[16] = {0};
415
416 StringSpan currentHost;
417 char currentHostStorage[256] = {0};
418 uint16_t currentPort = 0;
419
420 HttpURLParser currentURL;
421 uint32_t requestCount = 0;
422
423 bool hasOpenConnection = false;
424 bool responseDelivered = false;
425 bool responseFinalized = false;
426 bool webSocketUpgraded = false;
427
428 uint8_t reconnectClosuresPending = 0;
429 bool reconnectCloseInProgress = false;
430 bool reconnectPending = false;
431
432 AsyncReadableStream* reconnectReadableStream = nullptr;
433 AsyncWritableStream* reconnectWritableStream = nullptr;
434
435 AsyncReadableStream* reconnectSocketReadableStream = nullptr;
436 AsyncWritableStream* reconnectSocketWritableStream = nullptr;
437};
438
440} // namespace SC
Asynchronous I/O (files, sockets, timers, processes, fs events, threads wake-up) (see Async) AsyncEve...
Definition Async.h:1506
Async source abstraction emitting data events in caller provided byte buffers.
Definition AsyncStreams.h:228
Definition HttpAsyncClient.h:54
Connection request offered to an external transport before Http performs DNS or creates a socket.
Definition HttpAsyncClient.h:41
Outgoing HTTP request sent by the client.
Definition HttpConnection.h:428
Incoming HTTP response received by the client.
Definition HttpConnection.h:259
Mutable transport setup view used by HttpAsyncClient after the TCP socket connects.
Definition HttpAsyncClient.h:23
Definition HttpAsyncClient.h:87
Definition HttpAsyncClient.h:93
Asynchronous HTTP/1.1 client using caller-provided fixed storage.
Definition HttpAsyncClient.h:85
Result deleteRequest(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for a DELETE request without a request body.
void setTransportPreflight(Function< Result(const HttpURLParser &)> &&preflight)
Sets an optional transport policy hook invoked after URL parsing and before DNS or socket activity.
Definition HttpAsyncClient.h:206
Result options(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for an OPTIONS request without a request body.
void clearResponseDecompression()
Disables response decompression for future requests.
Definition HttpAsyncClient.h:195
Function< void(HttpAsyncClientResponse &)> onResponse
Called after the response headers have been parsed.
Definition HttpAsyncClient.h:300
Result patch(AsyncEventLoop &loop, StringSpan url, Span< const char > body, bool keepAlive=false)
Convenience wrapper for a PATCH request with a fixed in-memory body.
Result detachWebSocketTransport(HttpWebSocketTransportView &transport)
Hands the connected socket streams to a WebSocket owner after a validated 101 response.
Result head(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for a HEAD request without a request body.
Result start(AsyncEventLoop &loop, HttpParser::Method method, StringSpan url, bool keepAlive=false)
Starts a request that must be configured inside onPrepareRequest onPrepareRequest must send the heade...
Function< void(Result)> onError
Called on connection, protocol or streaming errors.
Definition HttpAsyncClient.h:303
Result postMultipart(AsyncEventLoop &loop, StringSpan url, HttpMultipartWriter &writer, bool keepAlive=false)
Convenience wrapper for a multipart/form-data POST request.
void setTransportShutdown(Function< Result(Function< void(Result)>)> &&shutdown)
Sets an optional asynchronous hook that drains a transport before reconnecting to another origin.
Definition HttpAsyncClient.h:230
Result close()
Closes any active connection and releases references to the initialized storage.
void clearTransportSetup()
Clears the optional transport setup hook and restores default socket transport setup.
Definition HttpAsyncClient.h:236
void setExternalConnector(Function< Result(HttpAsyncClientExternalConnection &)> &&connector)
Sets an optional connector that owns DNS and connection establishment for future requests.
Definition HttpAsyncClient.h:215
Result get(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for a GET request without a request body.
Result sendRequest(AsyncEventLoop &loop, const RequestOptions &options)
Starts an auto-sent request described by caller-owned request options.
void setTransportSetup(Function< Result(HttpAsyncClientTransportSetup &)> &&setup)
Sets an optional transport setup hook invoked after TCP connect and before HTTP request bytes are sen...
Definition HttpAsyncClient.h:201
Result init(HttpConnectionBase &storage)
Initializes the client with caller-provided connection storage The storage must outlive the client an...
Result post(AsyncEventLoop &loop, StringSpan url, Span< const char > body, bool keepAlive=false)
Convenience wrapper for a POST request with a fixed in-memory body.
void setResponseDecompression(SyncZLibTransformStream &decoder)
Enables opt-in gzip/deflate response decompression.
Definition HttpAsyncClient.h:192
Function< void(HttpAsyncClientRequest &)> onPrepareRequest
Called after the request has been created and can still be customized.
Definition HttpAsyncClient.h:297
Result put(AsyncEventLoop &loop, StringSpan url, Span< const char > body, bool keepAlive=false)
Convenience wrapper for a PUT request with a fixed in-memory body.
void setTransportClose(Function< void()> &&close)
Sets an optional transport teardown hook invoked before HTTP destroys the connected socket streams.
Definition HttpAsyncClient.h:223
Shared async transport storage for HTTP client and server endpoints.
Definition HttpConnection.h:73
Definition HttpConnection.h:40
Method
Method of the current request / response.
Definition HttpParser.h:19
Adds compile-time configurable read and write queues to any class subclassing HttpConnectionBase.
Definition HttpConnection.h:657
Parse an URL splitting it into its base components.
Definition HttpURLParser.h:71
Minimal transport handoff shape for later HTTP upgrade integration.
Definition HttpWebSocket.h:67
Definition ZLibTransformStreams.h:10