AUI Framework  master
Cross-platform module-based framework for developing C++20 desktop applications
ACurl.h
1/*
2 * AUI Framework - Declarative UI toolkit for modern C++20
3 * Copyright (C) 2020-2024 Alex2772 and Contributors
4 *
5 * SPDX-License-Identifier: MPL-2.0
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#pragma once
13
14#include <cstddef>
15#include <optional>
16#include <queue>
17#include <AUI/ACurl.h>
18
19#include "AUI/IO/AEOFException.h"
20#include "AUI/IO/IInputStream.h"
21#include "AUI/Traits/values.h"
22#include "AFormMultipart.h"
23#include <AUI/IO/APipe.h>
24#include <AUI/Common/AByteBufferView.h>
25#include <AUI/Common/AByteBuffer.h>
26#include <AUI/Common/ASignal.h>
27#include <AUI/Reflect/AEnumerate.h>
28#include <AUI/Thread/AFuture.h>
29
30class AString;
31class ACurlMulti;
32
41class API_AUI_CURL ACurl: public AObject {
42friend class ACurlMulti;
43public:
44 enum class Http {
45 VERSION_NONE, /* setting this means we don't care, and that we'd
46 like the library to choose the best possible
47 for us! */
48 VERSION_1_0, /* please use HTTP 1.0 in the request */
49 VERSION_1_1, /* please use HTTP 1.1 in the request */
50 VERSION_2_0, /* please use HTTP 2 in the request */
51 VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */
52 VERSION_2_PRIOR_KNOWLEDGE, /* please use HTTP 2 without HTTP/1.1
53 Upgrade */
54 VERSION_3 = 30, /* Makes use of explicit HTTP/3 without fallback.
55 Use CURLOPT_ALTSVC to enable HTTP/3 upgrade */
56 VERSION_LAST /* *ILLEGAL* http version */
57 };
58
59 enum class ResponseCode {
60 HTTP_100_CONTINUE = 100,
61 HTTP_101_SWITCHING_PROTOCOL = 101,
62 HTTP_102_PROCESSING = 102,
63 HTTP_103_EARLY_HINTS = 103,
64 HTTP_200_OK = 200,
65 HTTP_201_CREATED = 201,
66 HTTP_202_ACCEPTED = 202,
67 HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203,
68 HTTP_204_NO_CONTENT = 204,
69 HTTP_205_RESET_CONTENT = 205,
70 HTTP_206_PARTIAL_CONTENT = 206,
71 HTTP_300_MULTIPLE_CHOICE = 300,
72 HTTP_301_MOVED_PERMANENTLY = 301,
73 HTTP_302_FOUND = 302,
74 HTTP_303_SEE_OTHER = 303,
75 HTTP_304_NOT_MODIFIED = 304,
76 HTTP_305_USE_PROXY = 305,
77 HTTP_306_SWITCH_PROXY = 306,
78 HTTP_307_TEMPORARY_REDIRECT = 307,
79 HTTP_308_PERMANENT_REDIRECT = 308,
80 HTTP_400_BAD_REQUEST = 400,
81 HTTP_401_UNAUTHORIZED = 401,
82 HTTP_402_PAYMENT_REQUIRED = 402,
83 HTTP_403_FORBIDDEN = 403,
84 HTTP_404_NOT_FOUND = 404,
85 HTTP_405_METHOD_NOT_ALLOWED = 405,
86 HTTP_406_NOT_ACCEPTABLE = 406,
87 HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407,
88 HTTP_408_REQUEST_TIMEOUT = 408,
89 HTTP_409_CONFLICT = 409,
90 HTTP_410_GONE = 410,
91 HTTP_411_LENGTH_REQUIRED = 411,
92 HTTP_412_PRECONDITION_FAILED = 412,
93 HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413,
94 HTTP_414_REQUEST_URI_TOO_LONG = 414,
95 HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415,
96 HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
97 HTTP_417_EXPECTATION_FAILED = 417,
98 HTTP_500_INTERNAL_SERVER_ERROR = 500,
99 HTTP_501_NOT_IMPLEMENTED = 501,
100 HTTP_502_BAD_GATEWAY = 502,
101 HTTP_503_SERVICE_UNAVAILABLE = 503,
102 HTTP_504_GATEWAY_TIMEOUT = 504,
103 HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505,
104
105 /* don't forget to update AUI_ENUM_VALUES at the bottom */
106 };
107
108 enum class Method {
109 HTTP_GET,
110 HTTP_POST,
111 HTTP_PUT,
112 HTTP_DELETE,
113 };
114
118 struct Response {
119 ResponseCode code;
120 AString contentType;
121 AByteBuffer body;
122 };
123
124
125 struct API_AUI_CURL ErrorDescription {
126 int curlStatus;
127 AString description;
128
129 void throwException() const;
130 };
131
142 using WriteCallback = std::function<size_t(AByteBufferView data)>;
143
154 using ReadCallback = std::function<std::size_t(char* dst, size_t maxLen)>;
155 using HeaderCallback = std::function<void(AByteBufferView)>;
156 using ErrorCallback = std::function<void(const ErrorDescription& description)>;
157
158
159 class Exception: public AIOException {
160 public:
161 using AIOException::AIOException;
162 Exception(const ErrorDescription& errorDescription): AIOException(errorDescription.description),
163 mCurlStatus(errorDescription.curlStatus) {
164
165 }
166
167 [[nodiscard]]
168 int curlStatus() const noexcept {
169 return mCurlStatus;
170 }
171
172 private:
173 int mCurlStatus;
174 };
175
176 class API_AUI_CURL Builder {
177 friend class ACurl;
178 private:
179 void* mCURL;
180 WriteCallback mWriteCallback;
181 ReadCallback mReadCallback;
182 ErrorCallback mErrorCallback;
183 HeaderCallback mHeaderCallback;
184 bool mThrowExceptionOnError = false;
185 AVector<AString> mHeaders;
186 AString mUrl, mParams;
187 Method mMethod = Method::HTTP_GET;
188 std::function<void(ACurl&)> mOnSuccess;
189
190 public:
191 explicit Builder(AString url);
192 Builder(const Builder&) = delete;
193 ~Builder();
194
202 AUI_ASSERTX(mWriteCallback == nullptr, "write callback already set");
203 mWriteCallback = std::move(callback);
204 return *this;
205 }
206
214 withInputStream(multipart.makeInputStream());
215 mHeaders.push_back("Content-Type: multipart/form-data; boundary={}"_format(multipart.boundary()));
216 return *this;
217 }
218
225 AUI_ASSERTX(mReadCallback == nullptr, "write callback already set");
226 mReadCallback = std::move(callback);
227 return *this;
228 }
229
236 withBody([inputStream = std::move(inputStream)](char* dst, std::size_t length) {
237 auto v = inputStream->read(dst, length);
238 if (v == 0) {
239 throw AEOFException();
240 }
241 return v;
242 });
243 return *this;
244 }
245
250 Builder& withTimeout(std::chrono::seconds timeout);
251
255 Builder& withBody(std::string contents) {
256 AUI_ASSERTX(mReadCallback == nullptr, "write callback already set");
257
258 struct Body {
259 explicit Body(std::string b) : contents(std::move(b)), i(contents.begin()) {}
260
261 std::string contents;
262 std::string::iterator i;
263 };
264 auto b = _new<Body>(std::move(contents));
265
266 mReadCallback = [body = std::move(b)](char* dst, std::size_t length) mutable {
267 if (body->i == body->contents.end()) {
268 throw AEOFException();
269 }
270 std::size_t remaining = std::distance(body->i, body->contents.end());
271 length = glm::min(length, remaining);
272 std::memcpy(dst, &*body->i, length);
273 body->i += length;
274 return length;
275 };
276
277 return *this;
278 }
279
285 Builder& withHeaderCallback(HeaderCallback headerCallback) {
286 mHeaderCallback = std::move(headerCallback);
287 return *this;
288 }
289
293 Builder& withErrorCallback(ErrorCallback callback) {
294 AUI_ASSERTX(mErrorCallback == nullptr, "error callback already set");
295 mErrorCallback = std::move(callback);
296 return *this;
297 }
298
299 Builder& withDestinationBuffer(aui::constraint::avoid_copy<AByteBuffer> dst) {
300 return withWriteCallback([dst](AByteBufferView b) {
301 (*dst) << b;
302 return b.size();
303 });
304 }
305
306 Builder& withOutputStream(_<IOutputStream> dst) {
307 return withWriteCallback([dst = std::move(dst)](AByteBufferView b) {
308 (*dst) << b;
309 return b.size();
310 });
311 }
312
313 Builder& throwExceptionOnError(bool throwExceptionOnError) noexcept {
314 mThrowExceptionOnError = throwExceptionOnError;
315 return *this;
316 }
317
325 Builder& withRanges(size_t begin, size_t end);
326
334 Builder& withRanges(size_t begin) {
335 return withRanges(begin, 0);
336 }
337
343 Builder& withLowSpeedLimit(size_t speed);
344
350 Builder& withLowSpeedTime(std::chrono::seconds duration);
351
352
353 Builder& withHttpVersion(Http version);
354 Builder& withUpload(bool upload);
355 Builder& withCustomRequest(const AString& v);
356 Builder& withOnSuccess(std::function<void(ACurl&)> onSuccess) {
357 mOnSuccess = std::move(onSuccess);
358 return *this;
359 }
360
361 template<aui::invocable OnSuccess>
362 Builder& withOnSuccess(OnSuccess&& onSuccess) {
363 mOnSuccess = [onSuccess = std::forward<OnSuccess>(onSuccess)](ACurl&) {
364 onSuccess();
365 };
366 return *this;
367 }
368
374 Builder& withMethod(Method method) noexcept {
375 mMethod = method;
376 return *this;
377 };
378
379
388 Builder& withParams(const AVector<std::pair<AString /* key */, AString /* value */>>& params);
389
397 Builder& withParams(AString params) noexcept {
398 mParams = std::move(params);
399 return *this;
400 };
401
402 Builder& withHeaders(AVector<AString> headers) {
403 mHeaders = std::move(headers);
404 return *this;
405 }
406
413 _unique<IInputStream> toInputStream();
414
421 Response runBlocking();
422
427 AFuture<Response> runAsync();
428
433 AFuture<Response> runAsync(ACurlMulti& curlMulti);
434 };
435
436 explicit ACurl(Builder& builder):
437 ACurl(std::move(builder))
438 {
439
440 }
441 explicit ACurl(Builder&& builder) noexcept {
442 operator=(std::move(builder));
443 }
444 ACurl(ACurl&& o) noexcept {
445 operator=(std::move(o));
446 }
447
448 virtual ~ACurl();
449
450 ACurl& operator=(Builder&& o) noexcept;
451 ACurl& operator=(ACurl&& o) noexcept;
452
453 int64_t getContentLength() const;
454 int64_t getNumberOfBytesDownloaded() const;
455 AString getContentType() const;
456
457 void run();
458
473 virtual void close();
474
475 [[nodiscard]]
476 void* handle() const noexcept {
477 return mCURL;
478 }
479
480 [[nodiscard]]
481 ResponseCode getResponseCode() const;
482
483 [[nodiscard]]
484 AString getErrorString() const noexcept {
485 return AString::fromLatin1(mErrorBuffer);
486 }
487
488private:
489 void* mCURL;
490 struct curl_slist* mCurlHeaders = nullptr;
491 char mErrorBuffer[256];
492 bool mCloseRequested = false;
493 std::string mPostFieldsStorage;
494
495 static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
496 static size_t readCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
497 static size_t headerCallback(char *buffer, size_t size, size_t nitems, void *userdata) noexcept;
498
499 WriteCallback mWriteCallback;
500 ReadCallback mReadCallback;
501 HeaderCallback mHeaderCallback;
502
503 void reportSuccess() {
504 emit success;
505 }
506
507 void reportFail(int statusCode) {
508 emit fail(ErrorDescription{statusCode, AString::fromLatin1(mErrorBuffer)});
509 }
510
511 template<typename Ret>
512 Ret getInfo(int curlInfo) const;
513
514
515signals:
516
523
530
531
532 emits<> closeRequested;
533};
534
535
536AUI_ENUM_VALUES(ACurl::ResponseCode,
537 ACurl::ResponseCode::HTTP_100_CONTINUE,
538 ACurl::ResponseCode::HTTP_101_SWITCHING_PROTOCOL,
539 ACurl::ResponseCode::HTTP_102_PROCESSING,
540 ACurl::ResponseCode::HTTP_103_EARLY_HINTS,
541 ACurl::ResponseCode::HTTP_200_OK,
542 ACurl::ResponseCode::HTTP_201_CREATED,
543 ACurl::ResponseCode::HTTP_202_ACCEPTED,
544 ACurl::ResponseCode::HTTP_203_NON_AUTHORITATIVE_INFORMATION,
545 ACurl::ResponseCode::HTTP_204_NO_CONTENT,
546 ACurl::ResponseCode::HTTP_205_RESET_CONTENT,
547 ACurl::ResponseCode::HTTP_206_PARTIAL_CONTENT,
548 ACurl::ResponseCode::HTTP_300_MULTIPLE_CHOICE,
549 ACurl::ResponseCode::HTTP_301_MOVED_PERMANENTLY,
550 ACurl::ResponseCode::HTTP_302_FOUND,
551 ACurl::ResponseCode::HTTP_303_SEE_OTHER,
552 ACurl::ResponseCode::HTTP_304_NOT_MODIFIED,
553 ACurl::ResponseCode::HTTP_305_USE_PROXY,
554 ACurl::ResponseCode::HTTP_306_SWITCH_PROXY,
555 ACurl::ResponseCode::HTTP_307_TEMPORARY_REDIRECT,
556 ACurl::ResponseCode::HTTP_308_PERMANENT_REDIRECT,
557 ACurl::ResponseCode::HTTP_400_BAD_REQUEST,
558 ACurl::ResponseCode::HTTP_401_UNAUTHORIZED,
559 ACurl::ResponseCode::HTTP_402_PAYMENT_REQUIRED,
560 ACurl::ResponseCode::HTTP_403_FORBIDDEN,
561 ACurl::ResponseCode::HTTP_404_NOT_FOUND,
562 ACurl::ResponseCode::HTTP_405_METHOD_NOT_ALLOWED,
563 ACurl::ResponseCode::HTTP_406_NOT_ACCEPTABLE,
564 ACurl::ResponseCode::HTTP_407_PROXY_AUTHENTICATION_REQUIRED,
565 ACurl::ResponseCode::HTTP_408_REQUEST_TIMEOUT,
566 ACurl::ResponseCode::HTTP_409_CONFLICT,
567 ACurl::ResponseCode::HTTP_410_GONE,
568 ACurl::ResponseCode::HTTP_411_LENGTH_REQUIRED,
569 ACurl::ResponseCode::HTTP_412_PRECONDITION_FAILED,
570 ACurl::ResponseCode::HTTP_413_REQUEST_ENTITY_TOO_LARGE,
571 ACurl::ResponseCode::HTTP_414_REQUEST_URI_TOO_LONG,
572 ACurl::ResponseCode::HTTP_415_UNSUPPORTED_MEDIA_TYPE,
573 ACurl::ResponseCode::HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
574 ACurl::ResponseCode::HTTP_417_EXPECTATION_FAILED,
575 ACurl::ResponseCode::HTTP_500_INTERNAL_SERVER_ERROR,
576 ACurl::ResponseCode::HTTP_501_NOT_IMPLEMENTED,
577 ACurl::ResponseCode::HTTP_502_BAD_GATEWAY,
578 ACurl::ResponseCode::HTTP_503_SERVICE_UNAVAILABLE,
579 ACurl::ResponseCode::HTTP_504_GATEWAY_TIMEOUT,
580 ACurl::ResponseCode::HTTP_505_HTTP_VERSION_NOT_SUPPORTED)
Acts like std::string_view but for AByteBuffer.
Definition: AByteBufferView.h:24
std::vector-like growing array for byte storage.
Definition: AByteBuffer.h:31
Multi curl instance.
Definition: ACurlMulti.h:44
Definition: ACurl.h:176
Builder & withMultipart(const AFormMultipart &multipart)
Add multipart data.
Definition: ACurl.h:213
Builder & withBody(std::string contents)
Like withBody with callback, but wrapped with string.
Definition: ACurl.h:255
Builder & withWriteCallback(WriteCallback callback)
Called on server -> client data received (download).
Definition: ACurl.h:201
Builder & withParams(AString params) noexcept
Sets HTTP params to the query.
Definition: ACurl.h:397
Builder & withErrorCallback(ErrorCallback callback)
Definition: ACurl.h:293
Builder & withHeaderCallback(HeaderCallback headerCallback)
Called on header received.
Definition: ACurl.h:285
Builder & withInputStream(_< IInputStream > inputStream)
Called on client -> server data requested (upload).
Definition: ACurl.h:235
Builder & withBody(ReadCallback callback)
Called on client -> server data requested (upload).
Definition: ACurl.h:224
Builder & withMethod(Method method) noexcept
Sets HTTP method to the query.
Definition: ACurl.h:374
Builder & withRanges(size_t begin)
Sets: Accept-Ranges: begin-end (download part of the file)
Definition: ACurl.h:334
Definition: ACurl.h:159
Easy curl instance.
Definition: ACurl.h:41
std::function< size_t(AByteBufferView data)> WriteCallback
A read callback.
Definition: ACurl.h:142
emits success
Emitted on success.
Definition: ACurl.h:529
emits< ErrorDescription > fail
Emitted on network error.
Definition: ACurl.h:522
std::function< std::size_t(char *dst, size_t maxLen)> ReadCallback
A read callback.
Definition: ACurl.h:154
Thrown when stream has reached end (end of file).
Definition: AEOFException.h:20
Web multipart/form-data representation.
Definition: AFormMultipart.h:18
Represents a value that will be available at some point in the future.
Definition: AFuture.h:620
Exception caused by input/output stream.
Definition: AIOException.h:22
A base object class.
Definition: AObject.h:49
Represents a Unicode character string.
Definition: AString.h:37
An std::weak_ptr with AUI extensions.
Definition: SharedPtrTypes.h:177
Avoids copy of the wrapped value, pointing to a reference.
Definition: values.h:379
#define emit
emits the specified signal in context of this object.
Definition: AObject.h:196
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition: Assert.h:74
AUI_ENUM_VALUES(ATextOverflow, ATextOverflow::ELLIPSIS, ATextOverflow::CLIP) enum class AOverflowMask
Controls the behaviour of the default AView::drawStencilMask() implementation.
Definition: AOverflow.h:55
Definition: ACurl.h:125
Response struct for Builder::runBlocking() and Builder::runAsync()
Definition: ACurl.h:118