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