AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
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
201 Builder& withWriteCallback(WriteCallback callback) {
202 AUI_ASSERTX(mWriteCallback == nullptr, "write callback already set");
203 mWriteCallback = std::move(callback);
204 return *this;
205 }
206
213 Builder& withMultipart(const AFormMultipart& multipart) {
214 withInputStream(multipart.makeInputStream());
215 mHeaders.push_back("Content-Type: multipart/form-data; boundary={}"_format(multipart.boundary()));
216 return *this;
217 }
218
224 Builder& withBody(ReadCallback callback) {
225 AUI_ASSERTX(mReadCallback == nullptr, "write callback already set");
226 mReadCallback = std::move(callback);
227 return *this;
228 }
229
235 Builder& withInputStream(_<IInputStream> inputStream) {
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
333 Builder& withRanges(size_t begin) {
334 return withRanges(begin, 0);
335 }
336
342 Builder& withLowSpeedLimit(size_t speed);
343
349 Builder& withLowSpeedTime(std::chrono::seconds duration);
350
351
352 Builder& withHttpVersion(Http version);
353 Builder& withUpload(bool upload);
354 Builder& withCustomRequest(const AString& v);
355 Builder& withOnSuccess(std::function<void(ACurl&)> onSuccess) {
356 mOnSuccess = std::move(onSuccess);
357 return *this;
358 }
359
360 template<aui::invocable OnSuccess>
361 Builder& withOnSuccess(OnSuccess&& onSuccess) {
362 mOnSuccess = [onSuccess = std::forward<OnSuccess>(onSuccess)](ACurl&) {
363 onSuccess();
364 };
365 return *this;
366 }
367
373 Builder& withMethod(Method method) noexcept {
374 mMethod = method;
375 return *this;
376 };
377
378
387 Builder& withParams(const AVector<std::pair<AString /* key */, AString /* value */>>& params);
388
396 Builder& withParams(AString params) noexcept {
397 mParams = std::move(params);
398 return *this;
399 };
400
401 Builder& withHeaders(AVector<AString> headers) {
402 mHeaders = std::move(headers);
403 return *this;
404 }
405
412 _unique<IInputStream> toInputStream();
413
420 Response runBlocking();
421
426 AFuture<Response> runAsync();
427
432 AFuture<Response> runAsync(ACurlMulti& curlMulti);
433 };
434
435 explicit ACurl(Builder& builder):
436 ACurl(std::move(builder))
437 {
438
439 }
440 explicit ACurl(Builder&& builder) noexcept {
441 operator=(std::move(builder));
442 }
443 ACurl(ACurl&& o) noexcept {
444 operator=(std::move(o));
445 }
446
447 virtual ~ACurl();
448
449 ACurl& operator=(Builder&& o) noexcept;
450 ACurl& operator=(ACurl&& o) noexcept;
451
452 int64_t getContentLength() const;
453 int64_t getNumberOfBytesDownloaded() const;
454 AString getContentType() const;
455
456 void run();
457
472 virtual void close();
473
474 [[nodiscard]]
475 void* handle() const noexcept {
476 return mCURL;
477 }
478
479 [[nodiscard]]
480 ResponseCode getResponseCode() const;
481
482 [[nodiscard]]
483 AString getErrorString() const noexcept {
484 return AString::fromLatin1(mErrorBuffer);
485 }
486
487private:
488 void* mCURL;
489 struct curl_slist* mCurlHeaders = nullptr;
490 char mErrorBuffer[256];
491 bool mCloseRequested = false;
492 std::string mPostFieldsStorage;
493
494 static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
495 static size_t readCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
496 static size_t headerCallback(char *buffer, size_t size, size_t nitems, void *userdata) noexcept;
497
498 WriteCallback mWriteCallback;
499 ReadCallback mReadCallback;
500 HeaderCallback mHeaderCallback;
501
502 void reportSuccess() {
503 emit success;
504 }
505
506 void reportFail(int statusCode) {
507 emit fail(ErrorDescription{statusCode, AString::fromLatin1(mErrorBuffer)});
508 }
509
510 template<typename Ret>
511 Ret getInfo(int curlInfo) const;
512
513
514signals:
515
522
529
530
531 emits<> closeRequested;
532};
533
534
535AUI_ENUM_VALUES(ACurl::ResponseCode,
536 ACurl::ResponseCode::HTTP_100_CONTINUE,
537 ACurl::ResponseCode::HTTP_101_SWITCHING_PROTOCOL,
538 ACurl::ResponseCode::HTTP_102_PROCESSING,
539 ACurl::ResponseCode::HTTP_103_EARLY_HINTS,
540 ACurl::ResponseCode::HTTP_200_OK,
541 ACurl::ResponseCode::HTTP_201_CREATED,
542 ACurl::ResponseCode::HTTP_202_ACCEPTED,
543 ACurl::ResponseCode::HTTP_203_NON_AUTHORITATIVE_INFORMATION,
544 ACurl::ResponseCode::HTTP_204_NO_CONTENT,
545 ACurl::ResponseCode::HTTP_205_RESET_CONTENT,
546 ACurl::ResponseCode::HTTP_206_PARTIAL_CONTENT,
547 ACurl::ResponseCode::HTTP_300_MULTIPLE_CHOICE,
548 ACurl::ResponseCode::HTTP_301_MOVED_PERMANENTLY,
549 ACurl::ResponseCode::HTTP_302_FOUND,
550 ACurl::ResponseCode::HTTP_303_SEE_OTHER,
551 ACurl::ResponseCode::HTTP_304_NOT_MODIFIED,
552 ACurl::ResponseCode::HTTP_305_USE_PROXY,
553 ACurl::ResponseCode::HTTP_306_SWITCH_PROXY,
554 ACurl::ResponseCode::HTTP_307_TEMPORARY_REDIRECT,
555 ACurl::ResponseCode::HTTP_308_PERMANENT_REDIRECT,
556 ACurl::ResponseCode::HTTP_400_BAD_REQUEST,
557 ACurl::ResponseCode::HTTP_401_UNAUTHORIZED,
558 ACurl::ResponseCode::HTTP_402_PAYMENT_REQUIRED,
559 ACurl::ResponseCode::HTTP_403_FORBIDDEN,
560 ACurl::ResponseCode::HTTP_404_NOT_FOUND,
561 ACurl::ResponseCode::HTTP_405_METHOD_NOT_ALLOWED,
562 ACurl::ResponseCode::HTTP_406_NOT_ACCEPTABLE,
563 ACurl::ResponseCode::HTTP_407_PROXY_AUTHENTICATION_REQUIRED,
564 ACurl::ResponseCode::HTTP_408_REQUEST_TIMEOUT,
565 ACurl::ResponseCode::HTTP_409_CONFLICT,
566 ACurl::ResponseCode::HTTP_410_GONE,
567 ACurl::ResponseCode::HTTP_411_LENGTH_REQUIRED,
568 ACurl::ResponseCode::HTTP_412_PRECONDITION_FAILED,
569 ACurl::ResponseCode::HTTP_413_REQUEST_ENTITY_TOO_LARGE,
570 ACurl::ResponseCode::HTTP_414_REQUEST_URI_TOO_LONG,
571 ACurl::ResponseCode::HTTP_415_UNSUPPORTED_MEDIA_TYPE,
572 ACurl::ResponseCode::HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
573 ACurl::ResponseCode::HTTP_417_EXPECTATION_FAILED,
574 ACurl::ResponseCode::HTTP_500_INTERNAL_SERVER_ERROR,
575 ACurl::ResponseCode::HTTP_501_NOT_IMPLEMENTED,
576 ACurl::ResponseCode::HTTP_502_BAD_GATEWAY,
577 ACurl::ResponseCode::HTTP_503_SERVICE_UNAVAILABLE,
578 ACurl::ResponseCode::HTTP_504_GATEWAY_TIMEOUT,
579 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:396
Builder & withErrorCallback(ErrorCallback callback)
Definition ACurl.h:293
Builder & withHeaderCallback(HeaderCallback headerCallback)
Called on header received.
Definition ACurl.h:285
Builder & withRanges(size_t begin, size_t end)
Sets: Accept-Ranges: begin-end (download part of the file)
Definition ACurl.cpp:52
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:373
Builder & withRanges(size_t begin)
Sets: Accept-Ranges: begin-end (download part of the file)
Definition ACurl.h:333
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:528
emits< ErrorDescription > fail
Emitted on network error.
Definition ACurl.h:521
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
Represents a Unicode character string.
Definition AString.h:37
A std::vector with AUI extensions.
Definition AVector.h:39
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:178
Avoids copy of the wrapped value, pointing to a reference.
Definition values.h:383
ASignal< Args... > emits
A signal declaration.
Definition ASignal.h:348
#define emit
emits the specified signal in context of this object.
Definition AObject.h:310
#define AUI_ENUM_VALUES(enum_t,...)
Defines all enum values for AEnumerate.
Definition AEnumerate.h:208
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition Assert.h:74
Definition ACurl.h:125
Response struct for Builder::runBlocking() and Builder::runAsync()
Definition ACurl.h:118