Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
HttpClientAsync.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "../HttpClient/HttpClient.h"
6#include "../HttpClient/Internal/HttpClientEvent.h"
7#include "../HttpClient/Internal/HttpClientThreading.h"
8
9#include <string.h>
10
11namespace SC
12{
15
18template <typename T_AsyncStreams>
19struct SC_HTTP_CLIENT_EXPORT HttpClientAsyncOperationMemoryT
20{
21 using T_AsyncBufferView = typename T_AsyncStreams::BufferView;
22 using T_AsyncReadableStream = typename T_AsyncStreams::ReadableStream;
23 using T_AsyncWritableStream = typename T_AsyncStreams::WritableStream;
24 using T_AsyncReadableRequest = typename T_AsyncReadableStream::Request;
25 using T_AsyncWritableRequest = typename T_AsyncWritableStream::Request;
26
27 Span<T_AsyncBufferView> responseBuffers;
28 Span<char> responseBufferMemory;
29 Span<T_AsyncReadableRequest> responseReadQueue;
30 Span<T_AsyncWritableRequest> requestWriteQueue;
31};
32
33template <typename T_AsyncEventLoop, typename T_AsyncStreams>
37{
38 using T_AsyncLoopWakeUp = typename T_AsyncEventLoop::LoopWakeUp;
39 using T_AsyncBufferView = typename T_AsyncStreams::BufferView;
40 using T_AsyncBuffersPool = typename T_AsyncStreams::BuffersPool;
41 using T_AsyncReadableStream = typename T_AsyncStreams::ReadableStream;
42 using T_AsyncWritableStream = typename T_AsyncStreams::WritableStream;
43 using T_AsyncReadableRequest = typename T_AsyncReadableStream::Request;
44 using T_AsyncWritableRequest = typename T_AsyncWritableStream::Request;
45
46 static constexpr int MaxListeners = 8;
47
48 struct ResponseBodyStream final : public T_AsyncReadableStream
49 {
50 virtual Result asyncRead() override { return Result(true); }
51 };
52
53 struct RequestBodySink final : public T_AsyncWritableStream
54 {
55 Result init(T_AsyncBuffersPool& buffersPool, Span<T_AsyncWritableRequest> writeQueueSpan,
56 HttpClientAsyncT& ownerValue)
57 {
58 owner = &ownerValue;
59 this->setWriteQueue(writeQueueSpan);
60 return T_AsyncWritableStream::init(buffersPool);
61 }
62
63 private:
64 virtual Result asyncWrite(typename T_AsyncBufferView::ID bufferID,
65 Function<void(typename T_AsyncBufferView::ID)> cb) override
66 {
67 if (owner == nullptr)
68 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncRequestBodySinkOwnerMissing);
69 owner->onRequestBodyWritableBuffer(bufferID, move(cb));
70 return Result(true);
71 }
72
73 virtual Result asyncDestroyWritable() override
74 {
75 if (owner != nullptr and owner->operation.isRequestInFlight() and not owner->requestBodyFinished)
76 {
77 owner->onRequestBodyWritableError(
78 Result::Error(HttpClientResultCategory, HttpClientError::AsyncRequestBodySinkDestroyed));
79 }
80 T_AsyncWritableStream::finishedDestroyingWritable();
81 return Result(true);
82 }
83
84 virtual bool canEndWritable() override
85 {
86 if (owner != nullptr)
87 {
88 owner->onRequestBodyWritableFinished();
89 }
90 return true;
91 }
92
93 HttpClientAsyncT* owner = nullptr;
94 };
95
96 HttpClientEvent<MaxListeners, HttpClientResponse&> eventResponseHead;
97
98 Result init(HttpClient& client, T_AsyncEventLoop& loop, const HttpClientOperationMemory& operationMemory,
100 {
101 if (initialized)
102 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncAlreadyInitialized);
103 if (asyncMemory.responseBuffers.empty())
104 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncResponseBuffersMissing);
105 if (asyncMemory.responseReadQueue.empty())
106 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncResponseReadQueueMissing);
107 if (asyncMemory.requestWriteQueue.empty())
108 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncRequestWriteQueueMissing);
109
110 if (asyncMemory.responseBufferMemory.sizeInBytes() > 0)
111 {
112 SC_TRY(T_AsyncBuffersPool::sliceInEqualParts(asyncMemory.responseBuffers, asyncMemory.responseBufferMemory,
113 asyncMemory.responseBuffers.sizeInElements()));
114 }
115
116 SC_TRY(operation.init(client, operationMemory));
117
118 eventLoop = &loop;
119 responseReadQueue = asyncMemory.responseReadQueue;
120 requestWriteQueue = asyncMemory.requestWriteQueue;
121
122 responseBuffersPool.setBuffers(asyncMemory.responseBuffers);
123
124 responseBodyStream.setReadQueue(responseReadQueue);
125 responseBodyStream.setAutoDestroy(false);
126 requestBodySink.setAutoDestroy(false);
127 wakeUp.callback = [this](typename T_AsyncLoopWakeUp::Result& result) { onWakeUp(result); };
128
129 operation.setNotifier(this);
130 initialized = true;
131 return Result(true);
132 }
133
134 Result close()
135 {
136 if (not initialized)
137 {
138 return Result(true);
139 }
140
141 if (wakeUpStarted and eventLoop != nullptr)
142 {
143 (void)wakeUp.stop(*eventLoop);
144 wakeUpStarted = false;
145 }
146
147 if (requestBodySink.isStillWriting() or not requestBodySink.hasBeenDestroyed())
148 {
149 requestBodySink.destroy();
150 }
151 responseBodyStream.destroy();
152 resetRequestBodyState();
153
154 SC_TRY(operation.close());
155 operation.setNotifier(nullptr);
156
157 eventLoop = nullptr;
158 requestBodyBuffersPool = nullptr;
159 initialized = false;
160 return Result(true);
161 }
162
163 Result cancel() { return operation.cancel(); }
164
165 Result start(const HttpClientRequest& request, HttpClientResponse& response,
166 T_AsyncBuffersPool* requestBodyPool = nullptr)
167 {
168 if (not initialized)
169 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncNotInitialized);
170 if (request.body.isStreamed())
171 {
172 if (requestBodyPool == nullptr)
173 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncRequestBodyPoolMissing);
174 }
175
176 requestBodyBuffersPool = requestBodyPool;
177 resetRequestBodyState();
178
179 responseBodyStream.setReadQueue(responseReadQueue);
180 SC_TRY(responseBodyStream.init(responseBuffersPool));
181 SC_TRY(responseBodyStream.start());
182
183 if (request.body.isStreamed())
184 {
185 SC_TRY(requestBodySink.init(*requestBodyBuffersPool, requestWriteQueue, *this));
186 }
187
188 if (not wakeUpStarted)
189 {
190 SC_TRY(wakeUp.start(*eventLoop));
191 wakeUpStarted = true;
192 }
193
194 HttpClientRequest asyncRequest = request;
195 if (asyncRequest.body.isStreamed())
196 {
197 asyncRequest.body.provider = this;
198 }
199
200 const Result res = operation.start(asyncRequest, response, this);
201 if (not res)
202 {
203 if (request.body.isStreamed())
204 {
205 requestBodySink.destroy();
206 }
207 responseBodyStream.destroy();
208 return res;
209 }
210 return Result(true);
211 }
212
213 [[nodiscard]] T_AsyncReadableStream& getResponseBodyStream() { return responseBodyStream; }
214 [[nodiscard]] T_AsyncWritableStream& getRequestBodySink() { return requestBodySink; }
215
216 [[nodiscard]] bool isInitialized() const { return initialized; }
217 [[nodiscard]] bool isRequestInFlight() const { return operation.isRequestInFlight(); }
218
219 private:
220 friend struct HttpClientTest; // Test preflight and cross-library buffer-error propagation without network timing.
221 virtual void onResponseHead(HttpClientResponse& response) override { eventResponseHead.emit(response); }
222
223 virtual void onResponseBody(Span<const char> data) override
224 {
225 typename T_AsyncBufferView::ID bufferID;
226 Span<char> writable;
227 const Result bufferResult = responseBuffersPool.requestNewBuffer(data.sizeInBytes(), bufferID, writable);
228 if (not bufferResult)
229 {
230 responseBodyStream.emitError(bufferResult);
231 (void)operation.cancel();
232 return;
233 }
234 memcpy(writable.data(), data.data(), data.sizeInBytes());
235 const bool continuePushing = responseBodyStream.push(bufferID, data.sizeInBytes());
236 responseBuffersPool.unrefBuffer(bufferID);
237 if (not continuePushing)
238 {
239 (void)operation.cancel();
240 }
241 }
242
243 virtual void onResponseComplete() override
244 {
245 responseBodyStream.pushEnd();
246 resetRequestBodyState();
247 }
248
249 virtual void onError(Result error) override
250 {
251 responseBodyStream.emitError(error);
252 if (requestBodyBuffersPool != nullptr)
253 {
254 requestBodySink.destroy();
255 }
256 resetRequestBodyState();
257 }
258
259 virtual Result pullRequestBody(Span<char> dest, size_t& bytesWritten, bool& endReached) override
260 {
261 bytesWritten = 0;
262 endReached = false;
263
264 requestBodyMutex.lock();
265 while (not requestBodyHasActive and not requestBodyFinished and not requestBodyErrorSet)
266 {
267 requestBodyCV.wait(requestBodyMutex);
268 }
269
270 if (requestBodyErrorSet)
271 {
272 const Result error = requestBodyError;
273 requestBodyMutex.unlock();
274 return error;
275 }
276
277 if (not requestBodyHasActive and requestBodyFinished)
278 {
279 requestBodyMutex.unlock();
280 endReached = true;
281 return Result(true);
282 }
283
284 const typename T_AsyncBufferView::ID activeBuffer = requestBodyActiveBuffer;
285 const size_t activeOffset = requestBodyActiveOffset;
286 requestBodyMutex.unlock();
287
288 if (requestBodyBuffersPool == nullptr)
289 return Result::Error(HttpClientResultCategory, HttpClientError::AsyncRequestBodyPoolMissing);
290
291 Span<const char> readable;
292 SC_TRY(requestBodyBuffersPool->getReadableData(activeBuffer, readable));
293
294 const size_t remaining = readable.sizeInBytes() - activeOffset;
295 bytesWritten = remaining < dest.sizeInBytes() ? remaining : dest.sizeInBytes();
296 if (bytesWritten > 0)
297 {
298 memcpy(dest.data(), readable.data() + activeOffset, bytesWritten);
299 }
300
301 requestBodyMutex.lock();
302 requestBodyActiveOffset += bytesWritten;
303 const bool consumedBuffer = requestBodyActiveOffset >= readable.sizeInBytes();
304 requestBodyMutex.unlock();
305
306 if (consumedBuffer)
307 {
308 finishRequestBodyActive(Result(true));
309 }
310
311 return Result(true);
312 }
313
314 virtual void notifyHttpClientOperation(HttpClientOperation&) override
315 {
316 if (wakeUpStarted and eventLoop != nullptr)
317 {
318 (void)wakeUp.wakeUp(*eventLoop);
319 }
320 }
321
322 void onWakeUp(typename T_AsyncLoopWakeUp::Result& result)
323 {
324 const Result pollResult = operation.poll();
325 if (not pollResult)
326 {
327 responseBodyStream.emitError(pollResult);
328 }
329 result.reactivateRequest(true);
330 }
331
332 void resetRequestBodyState()
333 {
334 requestBodyMutex.lock();
335 if (requestBodyHasActive and requestBodyBuffersPool != nullptr)
336 {
337 requestBodyBuffersPool->unrefBuffer(requestBodyActiveBuffer);
338 }
339 requestBodyActiveBuffer = {};
340 requestBodyActiveCallback = {};
341 requestBodyActiveOffset = 0;
342 requestBodyHasActive = false;
343 requestBodyFinished = false;
344 requestBodyErrorSet = false;
345 requestBodyError = Result(true);
346 requestBodyCV.broadcast();
347 requestBodyMutex.unlock();
348 }
349
350 void finishRequestBodyActive(Result res)
351 {
352 requestBodyMutex.lock();
353 if (requestBodyHasActive and requestBodyBuffersPool != nullptr)
354 {
355 typename T_AsyncBufferView::ID bufferID = requestBodyActiveBuffer;
356
357 Function<void(typename T_AsyncBufferView::ID)> callback = move(requestBodyActiveCallback);
358
359 requestBodyHasActive = false;
360 requestBodyActiveBuffer = {};
361 requestBodyActiveOffset = 0;
362 requestBodyBuffersPool->unrefBuffer(bufferID);
363 requestBodyMutex.unlock();
364 requestBodySink.finishedWriting(bufferID, move(callback), res);
365 }
366 else
367 {
368 requestBodyMutex.unlock();
369 }
370 }
371
372 void onRequestBodyWritableBuffer(typename T_AsyncBufferView::ID bufferID,
373 Function<void(typename T_AsyncBufferView::ID)>&& cb)
374 {
375 requestBodyMutex.lock();
376 if (requestBodyBuffersPool != nullptr)
377 {
378 requestBodyBuffersPool->refBuffer(bufferID);
379 }
380 requestBodyActiveBuffer = bufferID;
381 requestBodyActiveCallback = move(cb);
382 requestBodyActiveOffset = 0;
383 requestBodyHasActive = true;
384 requestBodyCV.broadcast();
385 requestBodyMutex.unlock();
386 }
387
388 void onRequestBodyWritableFinished()
389 {
390 requestBodyMutex.lock();
391 requestBodyFinished = true;
392 requestBodyCV.broadcast();
393 requestBodyMutex.unlock();
394 }
395
396 void onRequestBodyWritableError(Result error)
397 {
398 requestBodyMutex.lock();
399 requestBodyErrorSet = true;
400 requestBodyError = error;
401 requestBodyCV.broadcast();
402 requestBodyMutex.unlock();
403 }
404
405 HttpClientOperation operation;
406 T_AsyncEventLoop* eventLoop = nullptr;
407
408 ResponseBodyStream responseBodyStream;
409 RequestBodySink requestBodySink;
410 T_AsyncBuffersPool responseBuffersPool;
411
412 Span<T_AsyncReadableRequest> responseReadQueue;
413 Span<T_AsyncWritableRequest> requestWriteQueue;
414
415 T_AsyncBuffersPool* requestBodyBuffersPool = nullptr;
416
417 T_AsyncLoopWakeUp wakeUp;
418 bool wakeUpStarted = false;
419
420 HttpClientLocalMutex requestBodyMutex;
421 HttpClientLocalConditionVariable requestBodyCV;
422
423 typename T_AsyncBufferView::ID requestBodyActiveBuffer = {};
424 Function<void(typename T_AsyncBufferView::ID)> requestBodyActiveCallback;
425
426 size_t requestBodyActiveOffset = 0;
427 bool requestBodyHasActive = false;
428 bool requestBodyFinished = false;
429 bool requestBodyErrorSet = false;
430 Result requestBodyError = Result(true);
431
432 bool initialized = false;
433};
434
436} // namespace SC
Caller-owned memory for one HttpClientAsyncT operation.
Definition HttpClientAsync.h:20
Definition HttpClientAsync.h:54
Definition HttpClientAsync.h:49
Definition HttpClientAsync.h:37
Listener receiving response notifications during HttpClientOperation::poll.
Definition HttpClient.h:367
Caller-owned memory for one HttpClientOperation.
Definition HttpClient.h:433
Optional notifier used by external adapters to wake up their own event loop.
Definition HttpClient.h:390
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
Reusable HTTP backend/session owner.
Definition HttpClient.h:449