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 Result setTransportSetup(Function<Result(HttpAsyncClientTransportSetup&)>&& setup, const void* owner = nullptr)
202 {
203 SC_TRY(checkTransportMutation(owner));
204 transportSetup = move(setup);
205 return Result(true);
206 }
207
211 Result setTransportPreflight(Function<Result(const HttpURLParser&)>&& preflight, const void* owner = nullptr)
212 {
213 SC_TRY(checkTransportMutation(owner));
214 transportPreflight = move(preflight);
215 return Result(true);
216 }
217
222 Result setExternalConnector(Function<Result(HttpAsyncClientExternalConnection&)>&& connector,
223 const void* owner = nullptr)
224 {
225 SC_TRY(checkTransportMutation(owner));
226 externalConnector = move(connector);
227 return Result(true);
228 }
229
233 Result setTransportClose(Function<void()>&& close, const void* owner = nullptr)
234 {
235 SC_TRY(checkTransportMutation(owner));
236 transportClose = move(close);
237 return Result(true);
238 }
239
245 Result setTransportShutdown(Function<Result(Function<void(Result)>)>&& shutdown, const void* owner = nullptr)
246 {
247 SC_TRY(checkTransportMutation(owner));
248 transportShutdown = move(shutdown);
249 return Result(true);
250 }
251
253 Result clearTransportSetup(const void* owner = nullptr);
254
258 Result claimTransport(const void* owner);
259
262 Result releaseTransport(const void* owner);
263
266
270 Result start(AsyncEventLoop& loop, HttpParser::Method method, StringSpan url, bool keepAlive = false);
271
274 Result sendRequest(AsyncEventLoop& loop, const RequestOptions& options);
275
277 Result get(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
278
280 Result head(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
281
283 Result options(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
284
286 Result deleteRequest(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
287
289 Result put(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
290 Result put(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
291 {
292 return put(loop, url, body.toCharSpan(), keepAlive);
293 }
294
296 Result post(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
297 Result post(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
298 {
299 return post(loop, url, body.toCharSpan(), keepAlive);
300 }
301
303 Result patch(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
304 Result patch(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
305 {
306 return patch(loop, url, body.toCharSpan(), keepAlive);
307 }
308
310 Result postMultipart(AsyncEventLoop& loop, StringSpan url, HttpMultipartWriter& writer, bool keepAlive = false);
311
312 [[nodiscard]] HttpAsyncClientResponse& getResponse() { return response; }
313 [[nodiscard]] const HttpAsyncClientResponse& getResponse() const { return response; }
314
317
320
322 Function<void(Result)> onError;
323
324 private:
325 Result checkTransportMutation(const void* owner) const;
326 Result checkTransportQuiescence() const;
327
328 struct RequestPreset
329 {
330 enum class BodyMode : uint8_t
331 {
332 None,
333 Span,
334 Stream,
335 Multipart,
336 };
337
338 HttpParser::Method method = HttpParser::Method::HttpGET;
339
340 StringSpan url;
341
342 bool keepAlive = false;
343 bool autoSend = false;
344
345 Span<const char> bodySpan;
346
347 BodyMode bodyMode = BodyMode::None;
348 uint64_t contentLength = 0;
349
350 Span<const Header> headers;
351
352 AsyncReadableStream* bodyStream = nullptr;
353 HttpMultipartWriter* multipartWriter = nullptr;
354 };
355
356 enum class State : uint8_t
357 {
358 Idle,
359 Connecting,
360 Sending,
361 WaitingResponse,
362 StreamingResponse,
363 };
364
365 Result startRequest(AsyncEventLoop& loop, const RequestPreset& preset);
366 Result prepareRequest(const RequestPreset& preset);
367 Result startPreparedRequest(const RequestPreset& preset);
368 Result ensureConnected();
369 Result closeConnectionForReconnect();
370 Result beginReconnectClose();
371 Result continueReconnectAfterClose();
372 void clearReconnectCloseListeners();
373 Result beginExternalConnection();
374 Result beginSocketConnection();
375 Result beginResponseRead();
376 Result beginRequestSend();
377 Result onResponseBodyStreamRead();
378 Result validateActiveRequest() const;
379
380 void completeTransportSetup(Result result);
381 [[nodiscard]] Result rememberConnectedOrigin();
382
383 void finalizeResponse(bool finishBodyStream);
384 void closeConnection(bool preserveReconnectListeners = false);
385 void finishResponse();
386 void fail(Result error);
387
388 void onReconnectReadableClosed();
389 void onReconnectWritableClosed();
390 void onReconnectSocketReadableClosed();
391 void onReconnectSocketWritableClosed();
392 void onReconnectTransportShutdown(Result result);
393
394 void onConnected(AsyncSocketConnect::Result& result);
395 void onReadableError(Result result);
396 void onWritableError(Result result);
397 void onPipelineError(Result result);
398 void onReadableEnd();
399 void onHeadersBufferWritten(AsyncBufferView::ID bufferID);
400 void onResponseData(AsyncBufferView::ID bufferID);
401 void onResponseBodyData(AsyncBufferView::ID bufferID);
402 void onCompressedResponseBodyData(AsyncBufferView::ID bufferID);
403 void onCompressedResponseBodyWritten(AsyncBufferView::ID bufferID);
404 void onCompressedResponseBodyEnd();
405 void onCompressedResponseError(Result result);
406
407 [[nodiscard]] bool canReuseConnectionFor(StringSpan protocol, StringSpan host, uint16_t port) const;
408 [[nodiscard]] bool responseMustNotHaveBody() const;
409 [[nodiscard]] bool responseHasKnownLength() const;
410 [[nodiscard]] Result prepareResponseDecompression();
411 [[nodiscard]] Result startResponseStreams();
412 void detachResponseDecompression();
413
414 HttpConnectionBase* connection = nullptr;
415
416 AsyncEventLoop* eventLoop = nullptr;
417 HttpAsyncClientRequest* currentRequest = nullptr;
418
419 HttpAsyncClientRequest request;
420 HttpAsyncClientResponse response;
421 AsyncSocketConnect connectAsync;
422 RequestPreset currentPreset;
423
424 SyncZLibTransformStream* responseDecoder = nullptr;
425 bool responseDecoderActive = false;
426
427 Function<Result(const HttpURLParser&)> transportPreflight;
428 Function<Result(HttpAsyncClientExternalConnection&)> externalConnector;
429 Function<Result(HttpAsyncClientTransportSetup&)> transportSetup;
430 Function<Result(Function<void(Result)>)> transportShutdown;
431 Function<void()> transportClose;
432
433 const void* transportOwner = nullptr;
434
435 uint32_t transportCallbackDepth = 0;
436
437 State state = State::Idle;
438
439 StringSpan currentProtocol;
440 char currentProtocolStorage[16] = {0};
441
442 StringSpan currentHost;
443 char currentHostStorage[256] = {0};
444 uint16_t currentPort = 0;
445
446 HttpURLParser currentURL;
447 uint32_t requestCount = 0;
448
449 bool hasOpenConnection = false;
450 bool responseDelivered = false;
451 bool responseFinalized = false;
452 bool webSocketUpgraded = false;
453
454 uint8_t reconnectClosuresPending = 0;
455 bool reconnectCloseInProgress = false;
456 bool reconnectPending = false;
457
458 AsyncReadableStream* reconnectReadableStream = nullptr;
459 AsyncWritableStream* reconnectWritableStream = nullptr;
460
461 AsyncReadableStream* reconnectSocketReadableStream = nullptr;
462 AsyncWritableStream* reconnectSocketWritableStream = nullptr;
463};
464
466} // namespace SC
Asynchronous I/O (files, sockets, timers, processes, fs events, threads wake-up) (see Async) AsyncEve...
Definition Async.h:1517
Async source abstraction emitting data events in caller provided byte buffers.
Definition AsyncStreams.h:229
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:427
Incoming HTTP response received by the client.
Definition HttpConnection.h:258
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.
Result releaseTransport(const void *owner)
Clears hooks and releases only the matching owner's claim on a quiescent client.
Result options(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for an OPTIONS request without a request body.
Result setTransportPreflight(Function< Result(const HttpURLParser &)> &&preflight, const void *owner=nullptr)
Sets an optional transport policy hook invoked after URL parsing and before DNS or socket activity.
Definition HttpAsyncClient.h:211
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:319
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 setExternalConnector(Function< Result(HttpAsyncClientExternalConnection &)> &&connector, const void *owner=nullptr)
Sets an optional connector that owns DNS and connection establishment for future requests.
Definition HttpAsyncClient.h:222
Result setTransportSetup(Function< Result(HttpAsyncClientTransportSetup &)> &&setup, const void *owner=nullptr)
Sets an optional transport setup hook invoked after TCP connect and before HTTP request bytes are sen...
Definition HttpAsyncClient.h:201
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:322
Result postMultipart(AsyncEventLoop &loop, StringSpan url, HttpMultipartWriter &writer, bool keepAlive=false)
Convenience wrapper for a multipart/form-data POST request.
Result close()
Closes any active connection and releases references to the initialized storage.
Result setTransportShutdown(Function< Result(Function< void(Result)>)> &&shutdown, const void *owner=nullptr)
Sets an optional asynchronous hook that drains a transport before reconnecting to another origin.
Definition HttpAsyncClient.h:245
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.
Result claimTransport(const void *owner)
Claims the complete transport hook group on a quiescent client without existing hooks or an owner.
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:316
Result clearTransportSetup(const void *owner=nullptr)
Clears the optional transport setup hook and restores default socket transport setup.
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.
Result setTransportClose(Function< void()> &&close, const void *owner=nullptr)
Sets an optional transport teardown hook invoked before HTTP destroys the connected socket streams.
Definition HttpAsyncClient.h:233
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:656
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