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;
19struct SC_HTTP_EXPORT HttpAsyncClientTransportSetup
20{
21 HttpConnectionBase* connection = nullptr;
22 AsyncEventLoop* eventLoop = nullptr;
23
24 const HttpURLParser* url = nullptr;
25
26 SocketDescriptor::Handle nativeSocket = SocketDescriptor::Invalid;
27
28 Function<void(Result)> complete;
29};
30
31template <int ReadQueue, int WriteQueue, int HeaderBytes, int StreamBytes>
32struct SC_HTTP_EXPORT HttpAsyncClientConnection
33 : public HttpStaticConnection<ReadQueue, WriteQueue, HeaderBytes, StreamBytes, 8, HttpConnectionBase>
34{
35 static constexpr int ExtraBuffers = 8;
36
38 {
39 this->readableSocketStream.setAutoDestroy(false);
40 this->writableSocketStream.setAutoDestroy(false);
41 }
42};
43
64struct SC_HTTP_EXPORT HttpAsyncClient
65{
66 struct Header
67 {
68 StringSpan name;
69 StringSpan value;
70 };
71
73 {
74 enum class BodyMode : uint8_t
75 {
76 None,
77 Span,
78 Stream,
79 Multipart,
80 };
81
82 HttpParser::Method method = HttpParser::Method::HttpGET;
83 StringSpan url;
84
85 Span<const Header> headers;
86
87 Span<const char> body;
88 AsyncReadableStream* bodyStream = nullptr;
89 HttpMultipartWriter* multipartWriter = nullptr;
90
91 uint64_t bodyLength = 0;
92
93 BodyMode bodyMode = BodyMode::None;
94 bool keepAlive = false;
95
96 RequestOptions& setRequest(HttpParser::Method newMethod, StringSpan newURL, bool newKeepAlive = false)
97 {
98 method = newMethod;
99 url = newURL;
100 keepAlive = newKeepAlive;
101 return *this;
102 }
103
104 RequestOptions& setHeaders(Span<const Header> newHeaders)
105 {
106 headers = newHeaders;
107 return *this;
108 }
109
110 RequestOptions& setKeepAlive(bool newKeepAlive = true)
111 {
112 keepAlive = newKeepAlive;
113 return *this;
114 }
115
116 RequestOptions& clearBody()
117 {
118 body = {};
119 bodyStream = nullptr;
120 multipartWriter = nullptr;
121 bodyLength = 0;
122 bodyMode = BodyMode::None;
123 return *this;
124 }
125
126 RequestOptions& setBody(Span<const char> newBody)
127 {
128 clearBody();
129 body = newBody;
130 bodyLength = newBody.sizeInBytes();
131 bodyMode = BodyMode::Span;
132 return *this;
133 }
134
135 RequestOptions& setBody(StringSpan newBody) { return setBody(newBody.toCharSpan()); }
136
137 RequestOptions& setBody(const char* newBody)
138 {
139 return setBody(StringSpan::fromNullTerminated(newBody, StringEncoding::Ascii));
140 }
141
142 RequestOptions& setBody(AsyncReadableStream& newBodyStream, uint64_t newBodyLength)
143 {
144 clearBody();
145 bodyStream = &newBodyStream;
146 bodyLength = newBodyLength;
147 bodyMode = BodyMode::Stream;
148 return *this;
149 }
150
151 RequestOptions& setMultipart(HttpMultipartWriter& newMultipartWriter)
152 {
153 clearBody();
154 multipartWriter = &newMultipartWriter;
155 bodyMode = BodyMode::Multipart;
156 return *this;
157 }
158 };
159
162 Result init(HttpConnectionBase& storage);
163
165 Result close();
166
172 void setResponseDecompression(SyncZLibTransformStream& decoder) { responseDecoder = &decoder; }
173
175 void clearResponseDecompression() { responseDecoder = nullptr; }
176
181 void setTransportSetup(Function<Result(HttpAsyncClientTransportSetup&)>&& setup) { transportSetup = move(setup); }
182
186 void setTransportClose(Function<void()>&& close) { transportClose = move(close); }
187
190 {
191 transportSetup = {};
192 transportClose = {};
193 }
194
197
201 Result start(AsyncEventLoop& loop, HttpParser::Method method, StringSpan url, bool keepAlive = false);
202
205 Result sendRequest(AsyncEventLoop& loop, const RequestOptions& options);
206
208 Result get(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
209
211 Result head(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
212
214 Result options(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
215
217 Result deleteRequest(AsyncEventLoop& loop, StringSpan url, bool keepAlive = false);
218
220 Result put(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
221 Result put(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
222 {
223 return put(loop, url, body.toCharSpan(), keepAlive);
224 }
225
227 Result post(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
228 Result post(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
229 {
230 return post(loop, url, body.toCharSpan(), keepAlive);
231 }
232
234 Result patch(AsyncEventLoop& loop, StringSpan url, Span<const char> body, bool keepAlive = false);
235 Result patch(AsyncEventLoop& loop, StringSpan url, StringSpan body, bool keepAlive = false)
236 {
237 return patch(loop, url, body.toCharSpan(), keepAlive);
238 }
239
241 Result postMultipart(AsyncEventLoop& loop, StringSpan url, HttpMultipartWriter& writer, bool keepAlive = false);
242
243 [[nodiscard]] HttpAsyncClientResponse& getResponse() { return response; }
244 [[nodiscard]] const HttpAsyncClientResponse& getResponse() const { return response; }
245
248
251
253 Function<void(Result)> onError;
254
255 private:
256 struct RequestPreset
257 {
258 enum class BodyMode : uint8_t
259 {
260 None,
261 Span,
262 Stream,
263 Multipart,
264 };
265
266 HttpParser::Method method = HttpParser::Method::HttpGET;
267
268 StringSpan url;
269
270 bool keepAlive = false;
271 bool autoSend = false;
272
273 Span<const char> bodySpan;
274
275 BodyMode bodyMode = BodyMode::None;
276 uint64_t contentLength = 0;
277
278 Span<const Header> headers;
279
280 AsyncReadableStream* bodyStream = nullptr;
281 HttpMultipartWriter* multipartWriter = nullptr;
282 };
283
284 enum class State : uint8_t
285 {
286 Idle,
287 Connecting,
288 Sending,
289 WaitingResponse,
290 StreamingResponse,
291 };
292
293 Result startRequest(AsyncEventLoop& loop, const RequestPreset& preset);
294 Result prepareRequest(const RequestPreset& preset);
295 Result startPreparedRequest(const RequestPreset& preset);
296 Result ensureConnected();
297 Result beginSocketConnection();
298 Result beginResponseRead();
299 Result beginRequestSend();
300 Result onResponseBodyStreamRead();
301 Result validateActiveRequest() const;
302
303 void completeTransportSetup(Result result);
304 [[nodiscard]] Result rememberConnectedOrigin();
305
306 void finalizeResponse(bool finishBodyStream);
307 void closeConnection();
308 void finishResponse();
309 void fail(Result error);
310
311 void onConnected(AsyncSocketConnect::Result& result);
312 void onReadableError(Result result);
313 void onWritableError(Result result);
314 void onPipelineError(Result result);
315 void onReadableEnd();
316 void onHeadersBufferWritten(AsyncBufferView::ID bufferID);
317 void onResponseData(AsyncBufferView::ID bufferID);
318 void onResponseBodyData(AsyncBufferView::ID bufferID);
319 void onCompressedResponseBodyData(AsyncBufferView::ID bufferID);
320 void onCompressedResponseBodyWritten(AsyncBufferView::ID bufferID);
321 void onCompressedResponseBodyEnd();
322 void onCompressedResponseError(Result result);
323
324 [[nodiscard]] bool canReuseConnectionFor(StringSpan protocol, StringSpan host, uint16_t port) const;
325 [[nodiscard]] bool responseMustNotHaveBody() const;
326 [[nodiscard]] bool responseHasKnownLength() const;
327 [[nodiscard]] Result prepareResponseDecompression();
328 [[nodiscard]] Result startResponseStreams();
329 void detachResponseDecompression();
330
331 HttpConnectionBase* connection = nullptr;
332
333 AsyncEventLoop* eventLoop = nullptr;
334 HttpAsyncClientRequest* currentRequest = nullptr;
335
336 HttpAsyncClientRequest request;
337 HttpAsyncClientResponse response;
338 AsyncSocketConnect connectAsync;
339 RequestPreset currentPreset;
340
341 SyncZLibTransformStream* responseDecoder = nullptr;
342 bool responseDecoderActive = false;
343
344 Function<Result(HttpAsyncClientTransportSetup&)> transportSetup;
345 Function<void()> transportClose;
346
347 State state = State::Idle;
348
349 StringSpan currentProtocol;
350 char currentProtocolStorage[16] = {0};
351
352 StringSpan currentHost;
353 char currentHostStorage[256] = {0};
354 uint16_t currentPort = 0;
355
356 HttpURLParser currentURL;
357 uint32_t requestCount = 0;
358
359 bool hasOpenConnection = false;
360 bool responseDelivered = false;
361 bool responseFinalized = false;
362 bool webSocketUpgraded = false;
363};
364
366} // namespace SC
Asynchronous I/O (files, sockets, timers, processes, fs events, threads wake-up) (see Async) AsyncEve...
Definition Async.h:1486
Async source abstraction emitting data events in caller provided byte buffers.
Definition AsyncStreams.h:228
Definition HttpAsyncClient.h:34
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:20
Definition HttpAsyncClient.h:67
Definition HttpAsyncClient.h:73
Asynchronous HTTP/1.1 client using caller-provided fixed storage.
Definition HttpAsyncClient.h:65
Result deleteRequest(AsyncEventLoop &loop, StringSpan url, bool keepAlive=false)
Convenience wrapper for a DELETE request without a request body.
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:175
Function< void(HttpAsyncClientResponse &)> onResponse
Called after the response headers have been parsed.
Definition HttpAsyncClient.h:250
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:253
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.
void clearTransportSetup()
Clears the optional transport setup hook and restores default socket transport setup.
Definition HttpAsyncClient.h:189
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:181
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:172
Function< void(HttpAsyncClientRequest &)> onPrepareRequest
Called after the request has been created and can still be customized.
Definition HttpAsyncClient.h:247
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:186
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:654
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:58
Definition ZLibTransformStreams.h:10