Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
HttpClient.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "../Common/CompilerMacrosExport.h"
6#include "../Common/CompilerMacrosType.h"
7#ifndef SC_EXPORT_LIBRARY_HTTP_CLIENT
8#define SC_EXPORT_LIBRARY_HTTP_CLIENT 0
9#endif
10#define SC_HTTP_CLIENT_EXPORT SC_COMPILER_LIBRARY_EXPORT(SC_EXPORT_LIBRARY_HTTP_CLIENT)
11
12#include "../Common/Result.h"
13#include "../Common/Span.h"
14#include "../Common/StringSpan.h"
15#include "Internal/HttpClientThreading.h"
16
17namespace SC
18{
21
22struct SC_HTTP_CLIENT_EXPORT HttpClientRequestBodyProvider;
23struct SC_HTTP_CLIENT_EXPORT HttpClientRequestOptions;
24
26struct SC_HTTP_CLIENT_EXPORT HttpClientHeader
27{
28 StringSpan name;
29 StringSpan value;
30};
31
33struct SC_HTTP_CLIENT_EXPORT HttpClientResponseHeaderIterator
34{
35 size_t offset = 0;
36};
37
39struct SC_HTTP_CLIENT_EXPORT HttpClientContentCoding
40{
41 enum Type : uint8_t
42 {
43 Unknown,
44 Identity,
45 GZip,
46 Deflate,
47 Compress,
48 Brotli,
49 };
50
51 Type type = Unknown;
52 StringSpan name;
53
54 [[nodiscard]] bool isIdentity() const { return type == Identity; }
55 [[nodiscard]] static Type parseName(StringSpan name);
56 [[nodiscard]] static const char* getName(Type type);
57 [[nodiscard]] static Result writeAcceptEncoding(Span<const Type> types, Span<char> destination, StringSpan& value);
58};
59
61struct SC_HTTP_CLIENT_EXPORT HttpClientContentCodingIterator
62{
64 StringSpan headerValue;
65 size_t valueOffset = 0;
66 bool hasHeaderValue = false;
67};
68
70struct SC_HTTP_CLIENT_EXPORT HttpClientTransferCoding
71{
72 enum Type : uint8_t
73 {
74 Unknown,
75 Chunked,
76 Compress,
77 Deflate,
78 GZip,
79 };
80
81 Type type = Unknown;
82 StringSpan name;
83
84 [[nodiscard]] bool isChunked() const { return type == Chunked; }
85 [[nodiscard]] static Type parseName(StringSpan name);
86 [[nodiscard]] static const char* getName(Type type);
87};
88
90struct SC_HTTP_CLIENT_EXPORT HttpClientTransferCodingIterator
91{
93 StringSpan headerValue;
94 size_t valueOffset = 0;
95 bool hasHeaderValue = false;
96};
97
99struct SC_HTTP_CLIENT_EXPORT HttpClientCapabilities
100{
101 enum Backend : uint8_t
102 {
103 Unsupported,
104 AppleURLSession,
105 LibCurl,
106 WinHttp,
107 };
108
109 enum Feature : uint8_t
110 {
111 MultipleOperationsPerClient,
112 FixedRequestBody,
113 SizedStreamRequestBody,
114 ChunkedStreamRequestBody,
115 RedirectPolicy,
116 ProtocolHttp11Only,
117 ProtocolHttp2Preferred,
118 ProtocolHttp2Required,
119 TlsDisablePeerVerification,
120 TlsCustomCaPath,
121 ProxyNoProxy,
122 ProxyHttp,
123 ProxyAuthorization,
124 ProxyBypassList,
125 ContentCodingPolicy,
126 };
127
128 Backend backend = Unsupported;
129
130 bool multipleOperationsPerClient = false;
131 bool fixedRequestBody = false;
132 bool sizedStreamRequestBody = false;
133 bool chunkedStreamRequestBody = false;
134 bool redirectPolicy = false;
135 bool protocolHttp11Only = false;
136 bool protocolHttp2Preferred = false;
137 bool protocolHttp2Required = false;
138 bool tlsDisablePeerVerification = false;
139 bool tlsCustomCaPath = false;
140 bool proxyNoProxy = false;
141 bool proxyHttp = false;
142 bool proxyAuthorization = false;
143 bool proxyBypassList = false;
144 bool contentCodingPolicy = false;
145
146 [[nodiscard]] bool hasBackend(Backend requiredBackend) const;
147 [[nodiscard]] bool supports(Feature feature) const;
148 [[nodiscard]] bool supportsRequestOptions(const HttpClientRequestOptions& options) const;
149 [[nodiscard]] bool supportsAll(Span<const Feature> features) const;
150 [[nodiscard]] Result requireBackend(Backend requiredBackend) const;
151 [[nodiscard]] Result requireFeatures(Span<const Feature> features) const;
152 [[nodiscard]] Result requireRequestOptions(const HttpClientRequestOptions& options) const;
153 [[nodiscard]] const char* getBackendName() const;
154 [[nodiscard]] static const char* getBackendName(Backend backend);
155 [[nodiscard]] static const char* getFeatureName(Feature feature);
156};
157
159struct SC_HTTP_CLIENT_EXPORT HttpClientRequestBody
160{
165 enum Framing : uint8_t
166 {
167 FixedSize,
168 SizedStream,
169 ChunkedStream,
170 };
171
172 Span<const char> bytes;
173 HttpClientRequestBodyProvider* provider = nullptr;
174
175 uint64_t sizeInBytes = 0;
176 bool canReplay = false;
177
178 Framing framing = FixedSize;
179
180 [[nodiscard]] bool isStreamed() const { return framing == SizedStream or framing == ChunkedStream; }
181 [[nodiscard]] bool isChunkedStream() const { return framing == ChunkedStream; }
182 [[nodiscard]] const char* getFramingName() const { return getFramingName(framing); }
183 [[nodiscard]] static const char* getFramingName(Framing framing);
184 [[nodiscard]] uint64_t getDeclaredSizeInBytes() const
185 {
186 if (framing == FixedSize)
187 {
188 return static_cast<uint64_t>(bytes.sizeInBytes());
189 }
190 return framing == SizedStream ? sizeInBytes : 0;
191 }
192};
193
195struct SC_HTTP_CLIENT_EXPORT HttpClientRequestRedirectOptions
196{
197 enum Mode : uint8_t
198 {
199 NoRedirects,
200 FollowGetHead,
201 FollowAll,
202 };
203
204 Mode mode = NoRedirects;
205 uint8_t maxRedirects = 10;
206
207 [[nodiscard]] const char* getModeName() const { return getModeName(mode); }
208 [[nodiscard]] static const char* getModeName(Mode mode);
209};
210
212struct SC_HTTP_CLIENT_EXPORT HttpClientRequestTimeoutOptions
213{
214 uint32_t requestTimeoutMs = 30000;
215};
216
218struct SC_HTTP_CLIENT_EXPORT HttpClientRequestTlsOptions
219{
220 bool verifyPeer = true;
221 StringSpan caCertificatesPath;
222};
223
225struct SC_HTTP_CLIENT_EXPORT HttpClientRequestProtocolOptions
226{
227 enum Preference : uint8_t
228 {
229 Default,
230 Http11Only,
231 Http2Preferred,
232 Http2Required,
233 };
234
235 Preference preference = Default;
236
237 [[nodiscard]] const char* getPreferenceName() const { return getPreferenceName(preference); }
238 [[nodiscard]] static const char* getPreferenceName(Preference preference);
239};
240
242struct SC_HTTP_CLIENT_EXPORT HttpClientRequestProxyOptions
243{
244 enum Mode : uint8_t
245 {
249 };
250
251 Mode mode = Default;
252 StringSpan url;
253 StringSpan authorization;
254 StringSpan bypassList;
255
256 [[nodiscard]] const char* getModeName() const { return getModeName(mode); }
257 [[nodiscard]] static const char* getModeName(Mode mode);
258};
259
269
274struct SC_HTTP_CLIENT_EXPORT HttpClientRequest
275{
276 enum Method : uint8_t
277 {
278 HttpGET,
279 HttpPOST,
280 HttpPUT,
281 HttpHEAD,
282 HttpDELETE,
283 HttpPATCH,
284 HttpOPTIONS,
285 };
286
287 Method method = HttpGET;
288
289 StringSpan url;
290
291 Span<const HttpClientHeader> headers;
294
295 [[nodiscard]] const char* getMethodName() const { return getMethodName(method); }
296 [[nodiscard]] static const char* getMethodName(Method method);
297 [[nodiscard]] Result validate() const;
298};
299
303struct SC_HTTP_CLIENT_EXPORT HttpClientResponse
304{
305 enum class Protocol : uint8_t
306 {
307 Unknown,
308 Http11,
309 Http2,
310 };
311
312 int statusCode = 0;
313
314 Span<const char> headers;
315
316 size_t headersLength = 0;
317 Protocol negotiatedProtocol = Protocol::Unknown;
318 StringSpan effectiveUrl;
319 uint32_t redirectCount = 0;
320
321 [[nodiscard]] bool getHeader(StringSpan name, StringSpan& value) const;
322 [[nodiscard]] bool hasHeader(StringSpan name) const;
323 [[nodiscard]] bool findNextHeader(StringSpan name, HttpClientResponseHeaderIterator& iterator,
324 StringSpan& value) const;
325 [[nodiscard]] bool getNextHeader(HttpClientResponseHeaderIterator& iterator, HttpClientHeader& header) const;
326 [[nodiscard]] bool getContentLength(uint64_t& value) const;
327 [[nodiscard]] bool getContentType(StringSpan& value) const;
328 [[nodiscard]] bool getContentEncoding(StringSpan& value) const;
329 [[nodiscard]] bool getTransferEncoding(StringSpan& value) const;
330 [[nodiscard]] bool getLocation(StringSpan& value) const;
331 [[nodiscard]] bool getWwwAuthenticate(StringSpan& value) const;
332 [[nodiscard]] bool getProxyAuthenticate(StringSpan& value) const;
333 [[nodiscard]] bool getNextContentCoding(HttpClientContentCodingIterator& iterator,
334 HttpClientContentCoding& contentCoding) const;
335 [[nodiscard]] bool getNextTransferCoding(HttpClientTransferCodingIterator& iterator,
336 HttpClientTransferCoding& transferCoding) const;
337 [[nodiscard]] bool hasContentCoding(HttpClientContentCoding::Type type) const;
338 [[nodiscard]] bool hasTransferCoding(HttpClientTransferCoding::Type type) const;
339 [[nodiscard]] bool isHttp11() const { return negotiatedProtocol == Protocol::Http11; }
340 [[nodiscard]] bool isHttp2() const { return negotiatedProtocol == Protocol::Http2; }
341 [[nodiscard]] bool isInformationalStatus() const { return statusCode >= 100 and statusCode < 200; }
342 [[nodiscard]] bool isSuccessfulStatus() const { return statusCode >= 200 and statusCode < 300; }
343 [[nodiscard]] bool isRedirectStatus() const { return statusCode >= 300 and statusCode < 400; }
344 [[nodiscard]] bool isClientErrorStatus() const { return statusCode >= 400 and statusCode < 500; }
345 [[nodiscard]] bool isServerErrorStatus() const { return statusCode >= 500 and statusCode < 600; }
346 [[nodiscard]] bool isErrorStatus() const { return statusCode >= 400; }
347 [[nodiscard]] const char* getProtocolName() const { return getProtocolName(negotiatedProtocol); }
348 [[nodiscard]] static const char* getProtocolName(Protocol protocol);
349};
350
352struct SC_HTTP_CLIENT_EXPORT HttpClientRequestBodyProvider
353{
355
361 virtual Result pullRequestBody(Span<char> dest, size_t& bytesWritten, bool& endReached) = 0;
362};
363
365struct SC_HTTP_CLIENT_EXPORT HttpClientOperationListener
366{
368
371 virtual void onResponseHead(HttpClientResponse& response) { (void)(response); }
372
375 virtual void onResponseBody(Span<const char> data) { (void)(data); }
376
378 virtual void onResponseComplete() {}
379
382 virtual void onError(Result error) { (void)(error); }
383};
384
385struct SC_HTTP_CLIENT_EXPORT HttpClientOperation;
386
388struct SC_HTTP_CLIENT_EXPORT HttpClientOperationNotifier
389{
391
394 virtual void notifyHttpClientOperation(HttpClientOperation& operation) = 0;
395};
396
398struct SC_HTTP_CLIENT_EXPORT HttpClientResponseBuffer
399{
400 Span<char> data;
401
402 private:
403 friend struct HttpClientOperation;
404 bool inUse = false;
405};
406
408struct SC_HTTP_CLIENT_EXPORT HttpClientOperationEvent
409{
410 enum class Type : uint8_t
411 {
412 ResponseHead,
413 ResponseData,
414 ResponseComplete,
415 Error,
416 };
417
418 Type type = Type::ResponseHead;
419 size_t size = 0;
420 size_t bufferIndex = 0;
421 Result error = Result(true);
422};
423
431struct SC_HTTP_CLIENT_EXPORT HttpClientOperationMemory
432{
433 Span<HttpClientResponseBuffer> responseBuffers;
434 Span<HttpClientOperationEvent> eventQueue;
435
437 Span<char> responseHeaders;
438 Span<char> responseMetadata;
439 Span<char> backendScratch;
440};
441
443#if SC_COMPILER_MSVC
444#pragma warning(push)
445#pragma warning(disable : 4324)
446#endif
447struct SC_HTTP_CLIENT_EXPORT HttpClient
448{
449 HttpClient();
450 ~HttpClient();
451
452 HttpClient(const HttpClient&) = delete;
453 HttpClient(HttpClient&&) = delete;
454 HttpClient& operator=(const HttpClient&) = delete;
455 HttpClient& operator=(HttpClient&&) = delete;
456
457 [[nodiscard]] Result init();
458 [[nodiscard]] Result init(HttpClientCapabilities::Backend requiredBackend);
459 [[nodiscard]] Result init(Span<const HttpClientCapabilities::Feature> requiredFeatures);
460 [[nodiscard]] Result init(HttpClientCapabilities::Backend requiredBackend,
461 Span<const HttpClientCapabilities::Feature> requiredFeatures);
462 [[nodiscard]] Result close();
463
464 [[nodiscard]] static HttpClientCapabilities getCapabilities();
465 [[nodiscard]] bool isInitialized() const { return initialized; }
466
474 [[nodiscard]] static Result executeBlocking(const HttpClientRequest& request, HttpClientResponse& response,
475 Span<char> bodyBuffer, size_t& bodyLength,
476 const HttpClientOperationMemory& memory);
477
478 friend struct HttpClientOperation;
479
480 private:
481 friend struct Internal;
482 friend struct HttpClientLinuxCallbacks;
483 struct Internal;
484
485 Result platformInit();
486 Result platformClose();
487
488 bool initialized = false;
489
490#if SC_PLATFORM_APPLE
491 alignas(uint64_t) char storage[128];
492#elif SC_PLATFORM_WINDOWS
493 alignas(uint64_t) char storage[128];
494#elif SC_PLATFORM_LINUX
495 alignas(uint64_t) char storage[512];
496#else
497 alignas(uint64_t) char storage[8];
498#endif
499};
500
502struct SC_HTTP_CLIENT_EXPORT HttpClientOperation
503{
506
509 HttpClientOperation& operator=(const HttpClientOperation&) = delete;
510 HttpClientOperation& operator=(HttpClientOperation&&) = delete;
511
512 [[nodiscard]] Result init(HttpClient& client, const HttpClientOperationMemory& memory);
513 [[nodiscard]] Result close();
514 [[nodiscard]] Result cancel();
515
521 [[nodiscard]] Result start(const HttpClientRequest& request, HttpClientResponse& response,
522 HttpClientOperationListener* listener = nullptr);
523
527 [[nodiscard]] Result poll(uint32_t timeoutMilliseconds = 0);
528
531 void setNotifier(HttpClientOperationNotifier* notifierValue) { notifier = notifierValue; }
532
533 [[nodiscard]] bool isInitialized() const { return initialized; }
534 [[nodiscard]] bool isRequestInFlight() const { return requestInFlight; }
535
536 friend struct Internal;
537 struct Internal;
538
539 private:
540 friend struct HttpClientAppleCallbacks;
541 friend struct HttpClientLinuxCallbacks;
542 friend struct HttpClientWindowsCallbacks;
543
544 Result platformInit();
545 Result platformClose();
546 Result platformStart();
547 Result platformCancel();
548
549 Result enqueueEvent(const HttpClientOperationEvent& event);
550 bool dequeueEvent(HttpClientOperationEvent& event);
551
552 Result allocateResponseBuffer(size_t minimumSizeInBytes, size_t& bufferIndex, Span<char>& data);
553 void releaseResponseBuffer(size_t bufferIndex);
554 Result enqueueResponseDataCopy(Span<const char> data);
555
556 void enqueueResponseHead();
557 void enqueueResponseBuffer(size_t bufferIndex, size_t size);
558 void enqueueResponseComplete();
559 void enqueueError(Result error);
560
561 void resetResponseState(HttpClientResponse& response);
562 void resetRequestBodyState();
563 bool isAutomaticRedirectEnabled() const;
564 bool canAutomaticRedirectRequestReplay() const;
565 Result copyResponseEffectiveUrl(StringSpan url);
566
567 size_t readRequestBodyChunk(Span<char> dest, Result& outError, bool& outEnd);
568 bool hasPendingEvents() const;
569 Result processPendingEvents();
570
571 HttpClient* client = nullptr;
572 HttpClientResponse* currentResponse = nullptr;
573 HttpClientOperationListener* currentListener = nullptr;
574 HttpClientOperationNotifier* notifier = nullptr;
575 HttpClientRequest currentRequest;
576
577 Span<HttpClientResponseBuffer> responseBuffers;
578 Span<HttpClientOperationEvent> eventQueue;
579
580 Span<char> responseHeaders;
581 Span<char> responseMetadata;
582 Span<char> backendScratch;
583
584 mutable HttpClientLocalMutex eventMutex;
585 HttpClientLocalConditionVariable eventCV;
586 HttpClientOperationEvent dequeuedEvent;
587
588 size_t eventHead = 0;
589 size_t eventTail = 0;
590 size_t eventCount = 0;
591 bool requestBodyFinished = false;
592 Result requestBodyError = Result(true);
593 uint64_t requestBodyBytesRead = 0;
594 bool initialized = false;
595 bool requestInFlight = false;
596
597#if SC_PLATFORM_APPLE
598 alignas(uint64_t) char storage[512];
599#elif SC_PLATFORM_WINDOWS
600 alignas(uint64_t) char storage[256];
601#elif SC_PLATFORM_LINUX
602 alignas(uint64_t) char storage[512];
603#else
604 alignas(uint64_t) char storage[8];
605#endif
606};
607#if SC_COMPILER_MSVC
608#pragma warning(pop)
609#endif
610
612} // namespace SC
Compile-time backend capability report for the active HttpClient backend.
Definition HttpClient.h:100
Caller-owned cursor for iterating comma-separated Content-Encoding values.
Definition HttpClient.h:62
Parsed response content-coding token view.
Definition HttpClient.h:40
HTTP header name/value view.
Definition HttpClient.h:27
Event slot storage used by HttpClientOperation to hand off backend notifications.
Definition HttpClient.h:409
Listener receiving response notifications during HttpClientOperation::poll.
Definition HttpClient.h:366
virtual void onError(Result error)
Called when the request fails.
Definition HttpClient.h:382
virtual void onResponseComplete()
Called when the response body completed successfully.
Definition HttpClient.h:378
virtual void onResponseHead(HttpClientResponse &response)
Called once the response status code and headers are available.
Definition HttpClient.h:371
virtual void onResponseBody(Span< const char > data)
Called for each response body chunk delivered by poll()
Definition HttpClient.h:375
Caller-owned memory for one HttpClientOperation.
Definition HttpClient.h:432
Span< char > responseBufferMemory
Optional; split equally into responseBuffers if non-empty.
Definition HttpClient.h:436
Optional notifier used by external adapters to wake up their own event loop.
Definition HttpClient.h:389
virtual void notifyHttpClientOperation(HttpClientOperation &operation)=0
Notifies an external adapter that the operation has queued new events.
One in-flight HTTP request/response operation.
Definition HttpClient.h:503
void setNotifier(HttpClientOperationNotifier *notifierValue)
Registers an optional notifier used by adapters such as HttpClientAsyncT.
Definition HttpClient.h:531
Result start(const HttpClientRequest &request, HttpClientResponse &response, HttpClientOperationListener *listener=nullptr)
Starts a new request on this operation.
Result poll(uint32_t timeoutMilliseconds=0)
Processes queued backend events and optionally waits for more work.
Pull-based provider for streamed request bodies.
Definition HttpClient.h:353
virtual Result pullRequestBody(Span< char > dest, size_t &bytesWritten, bool &endReached)=0
Writes the next request body chunk in dest.
Outgoing request body description.
Definition HttpClient.h:160
Framing
Transfer framing requested for the outgoing body.
Definition HttpClient.h:166
Extended request options grouped by transport concern.
Definition HttpClient.h:262
HTTP protocol preference for one request.
Definition HttpClient.h:226
Proxy policy for one request.
Definition HttpClient.h:243
StringSpan authorization
Optional exact Proxy-Authorization header value for Http
Definition HttpClient.h:253
StringSpan url
Required for Http, must use the http:// scheme.
Definition HttpClient.h:252
Mode
Definition HttpClient.h:245
@ NoProxy
Bypass proxies for this request.
Definition HttpClient.h:247
@ Default
Use the backend default proxy configuration.
Definition HttpClient.h:246
@ Http
Use url as an explicit HTTP proxy URL.
Definition HttpClient.h:248
StringSpan bypassList
Optional comma-separated proxy bypass list for Http
Definition HttpClient.h:254
Redirect handling policy for one request.
Definition HttpClient.h:196
Timeout policy for one request.
Definition HttpClient.h:213
TLS policy for one request.
Definition HttpClient.h:219
Configuration for an outgoing HTTP request.
Definition HttpClient.h:275
StringSpan url
Full URL including scheme (e.g. "https://example.com/path")
Definition HttpClient.h:289
Caller-owned response buffer descriptor for one HttpClientOperation.
Definition HttpClient.h:399
Caller-owned cursor for iterating response headers.
Definition HttpClient.h:34
Parsed response metadata filled when headers arrive.
Definition HttpClient.h:304
Caller-owned cursor for iterating comma-separated Transfer-Encoding values.
Definition HttpClient.h:91
Parsed response transfer-coding token view.
Definition HttpClient.h:71
Reusable HTTP backend/session owner.
Definition HttpClient.h:448
static Result executeBlocking(const HttpClientRequest &request, HttpClientResponse &response, Span< char > bodyBuffer, size_t &bodyLength, const HttpClientOperationMemory &memory)
Convenience helper executing a request synchronously on top of HttpClientOperation::poll.