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