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-2025 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
  155    using WriteCallbackV2 = std::function<size_t(ACurl& curl, AByteBufferView data)>;
  156
  167    using ReadCallback = std::function<std::size_t(char* dst, size_t maxLen)>;
  168    using HeaderCallback = std::function<void(AByteBufferView)>;
  169    using ErrorCallback = std::function<void(const ErrorDescription& description)>;
  170
  171
  172    class Exception: public AIOException {
  173    public:
  174        using AIOException::AIOException;
  175        Exception(const ErrorDescription& errorDescription): AIOException(errorDescription.description),
  176                                                             mCurlStatus(errorDescription.curlStatus) {
  177
  178        }
  179
  180        [[nodiscard]]
  181        int curlStatus() const noexcept {
  182            return mCurlStatus;
  183        }
  184
  185    private:
  186        int mCurlStatus;
  187    };
  188
  189    class API_AUI_CURL Builder {
  190    friend class ACurl;
  191    private:
  192        void* mCURL;
  193        WriteCallbackV2 mWriteCallback;
  194        ReadCallback mReadCallback;
  195        ErrorCallback mErrorCallback;
  196        HeaderCallback mHeaderCallback;
  197        bool mThrowExceptionOnError = false;
  198        AVector<AString> mHeaders;
  199        AString mUrl, mParams;
  200        Method mMethod = Method::HTTP_GET;
  201        std::function<void(ACurl&)> mOnSuccess;
  202
  203    public:
  204        explicit Builder(AString url);
  205        Builder(const Builder&) = delete;
  206        ~Builder();
  207
  214        Builder& withWriteCallback(WriteCallback callback) {
  215            return withWriteCallback([callback = std::move(callback)](ACurl&, AByteBufferView buffer) {
  216                return callback(buffer);
  217            });
  218        }
  219
  226        Builder& withWriteCallback(WriteCallbackV2 callback) {
  227            AUI_ASSERTX(mWriteCallback == nullptr, "write callback already set");
  228            mWriteCallback = std::move(callback);
  229            return *this;
  230        }
  231
  238         Builder& withMultipart(const AFormMultipart& multipart) {
  239            withInputStream(multipart.makeInputStream());
  240            mHeaders.push_back("Content-Type: multipart/form-data; boundary={}"_format(multipart.boundary()));
  241            return *this;
  242         }
  243
  249        Builder& withBody(ReadCallback callback) {
  250            AUI_ASSERTX(mReadCallback == nullptr, "write callback already set");
  251            mReadCallback = std::move(callback);
  252            return *this;
  253        }
  254
  260        Builder& withInputStream(_<IInputStream> inputStream) {
  261            withBody([inputStream = std::move(inputStream)](char* dst, std::size_t length) {
  262                auto v = inputStream->read(dst, length);
  263                if (v == 0) {
  264                    throw AEOFException();
  265                }
  266                return v;
  267            });
  268            return *this;
  269        }
  270
  275        Builder& withTimeout(std::chrono::seconds timeout);
  276
  280        Builder& withBody(std::string contents) {
  281            AUI_ASSERTX(mReadCallback == nullptr, "write callback already set");
  282
  283            struct Body {
  284                explicit Body(std::string b) : contents(std::move(b)), i(contents.begin()) {}
  285
  286                std::string contents;
  287                std::string::iterator i;
  288            };
  289            auto b = _new<Body>(std::move(contents));
  290
  291            mReadCallback = [body = std::move(b)](char* dst, std::size_t length) mutable {
  292                if (body->i == body->contents.end()) {
  293                    throw AEOFException();
  294                }
  295                std::size_t remaining = std::distance(body->i, body->contents.end());
  296                length = glm::min(length, remaining);
  297                std::memcpy(dst, &*body->i, length);
  298                body->i += length;
  299                return length;
  300            };
  301
  302            return *this;
  303        }
  304
  310        Builder& withHeaderCallback(HeaderCallback headerCallback) {
  311            mHeaderCallback = std::move(headerCallback);
  312            return *this;
  313        }
  314
  318        Builder& withErrorCallback(ErrorCallback callback) {
  319            AUI_ASSERTX(mErrorCallback == nullptr, "error callback already set");
  320            mErrorCallback = std::move(callback);
  321            return *this;
  322        }
  323
  324        Builder& withDestinationBuffer(aui::constraint::avoid_copy<AByteBuffer> dst) {
  325            return withWriteCallback([dst](AByteBufferView b) {
  326                (*dst) << b;
  327                return b.size();
  328            });
  329        }
  330
  331        Builder& withOutputStream(_<IOutputStream> dst) {
  332            return withWriteCallback([dst = std::move(dst)](AByteBufferView b) {
  333                (*dst) << b;
  334                return b.size();
  335            });
  336        }
  337
  338        Builder& throwExceptionOnError(bool throwExceptionOnError) noexcept {
  339            mThrowExceptionOnError = throwExceptionOnError;
  340            return *this;
  341        }
  342
  350        Builder& withRanges(size_t begin, size_t end);
  351
  358        Builder& withRanges(size_t begin) {
  359            return withRanges(begin, 0);
  360        }
  361
  367        Builder& withLowSpeedLimit(size_t speed);
  368
  374        Builder& withLowSpeedTime(std::chrono::seconds duration);
  375
  376
  377        Builder& withHttpVersion(Http version);
  378        Builder& withUpload(bool upload);
  379        Builder& withCustomRequest(const AString& v);
  380        Builder& withOnSuccess(std::function<void(ACurl&)> onSuccess) {
  381            mOnSuccess = std::move(onSuccess);
  382            return *this;
  383        }
  384
  385        template<aui::invocable OnSuccess>
  386        Builder& withOnSuccess(OnSuccess&& onSuccess) {
  387            mOnSuccess = [onSuccess = std::forward<OnSuccess>(onSuccess)](ACurl&) {
  388                onSuccess();
  389            };
  390            return *this;
  391        }
  392
  398        Builder& withMethod(Method method) noexcept {
  399            mMethod = method;
  400            return *this;
  401        };
  402
  403
  412        Builder& withParams(const AVector<std::pair<AString /* key */, AString /* value */>>& params);
  413
  421        Builder& withParams(AString params) noexcept {
  422            mParams = std::move(params);
  423            return *this;
  424        };
  425
  426        Builder& withHeaders(AVector<AString> headers) {
  427            mHeaders = std::move(headers);
  428            return *this;
  429        }
  430
  437        _unique<IInputStream> toInputStream();
  438
  445        Response runBlocking();
  446
  452
  457         AFuture<Response> runAsync(ACurlMulti& curlMulti);
  458    };
  459
  460    explicit ACurl(Builder& builder):
  461        ACurl(std::move(builder))
  462    {
  463
  464    }
  465    explicit ACurl(Builder&& builder) noexcept {
  466        operator=(std::move(builder));
  467    }
  468    ACurl(ACurl&& o) noexcept {
  469        operator=(std::move(o));
  470    }
  471
  472    virtual ~ACurl();
  473
  474    ACurl& operator=(Builder&& o) noexcept;
  475    ACurl& operator=(ACurl&& o) noexcept;
  476
  477    int64_t getContentLength() const;
  478    int64_t getNumberOfBytesDownloaded() const;
  479    AString getContentType() const;
  480
  481    void run();
  482
  497    virtual void close();
  498
  499    [[nodiscard]]
  500    void* handle() const noexcept {
  501        return mCURL;
  502    }
  503
  504    [[nodiscard]]
  505    ResponseCode getResponseCode() const;
  506
  507    [[nodiscard]]
  508    AString getErrorString() const noexcept {
  509        return AString::fromLatin1(mErrorBuffer);
  510    }
  511
  512private:
  513    void* mCURL;
  514    struct curl_slist* mCurlHeaders = nullptr;
  515    char mErrorBuffer[256];
  516    bool mCloseRequested = false;
  517    bool mThrowExceptionOnError = false;
  518    std::string mPostFieldsStorage;
  519
  520    static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
  521    static size_t readCallback(char* ptr, size_t size, size_t nmemb, void* userdata) noexcept;
  522    static size_t headerCallback(char *buffer, size_t size, size_t nitems, void *userdata) noexcept;
  523
  524    WriteCallbackV2 mWriteCallback;
  525    ReadCallback mReadCallback;
  526    HeaderCallback mHeaderCallback;
  527
  528    void reportSuccess() {
  529        emit success;
  530    }
  531
  532    void reportFail(const ErrorDescription& errorDescription) {
  533        emit fail(errorDescription);
  534    }
  535
  536    void reportFail(int statusCode) {
  537        reportFail(ErrorDescription{statusCode, AString::fromLatin1(mErrorBuffer)});
  538    }
  539
  540    template<typename Ret>
  541    Ret getInfo(int curlInfo) const;
  542
  543
  544signals:
  545
  552
  559
  560
  561    emits<> closeRequested;
  562};
  563
  564
  565AUI_ENUM_VALUES(ACurl::ResponseCode,
  566                ACurl::ResponseCode::HTTP_100_CONTINUE,
  567                ACurl::ResponseCode::HTTP_101_SWITCHING_PROTOCOL,
  568                ACurl::ResponseCode::HTTP_102_PROCESSING,
  569                ACurl::ResponseCode::HTTP_103_EARLY_HINTS,
  570                ACurl::ResponseCode::HTTP_200_OK,
  571                ACurl::ResponseCode::HTTP_201_CREATED,
  572                ACurl::ResponseCode::HTTP_202_ACCEPTED,
  573                ACurl::ResponseCode::HTTP_203_NON_AUTHORITATIVE_INFORMATION,
  574                ACurl::ResponseCode::HTTP_204_NO_CONTENT,
  575                ACurl::ResponseCode::HTTP_205_RESET_CONTENT,
  576                ACurl::ResponseCode::HTTP_206_PARTIAL_CONTENT,
  577                ACurl::ResponseCode::HTTP_300_MULTIPLE_CHOICE,
  578                ACurl::ResponseCode::HTTP_301_MOVED_PERMANENTLY,
  579                ACurl::ResponseCode::HTTP_302_FOUND,
  580                ACurl::ResponseCode::HTTP_303_SEE_OTHER,
  581                ACurl::ResponseCode::HTTP_304_NOT_MODIFIED,
  582                ACurl::ResponseCode::HTTP_305_USE_PROXY,
  583                ACurl::ResponseCode::HTTP_306_SWITCH_PROXY,
  584                ACurl::ResponseCode::HTTP_307_TEMPORARY_REDIRECT,
  585                ACurl::ResponseCode::HTTP_308_PERMANENT_REDIRECT,
  586                ACurl::ResponseCode::HTTP_400_BAD_REQUEST,
  587                ACurl::ResponseCode::HTTP_401_UNAUTHORIZED,
  588                ACurl::ResponseCode::HTTP_402_PAYMENT_REQUIRED,
  589                ACurl::ResponseCode::HTTP_403_FORBIDDEN,
  590                ACurl::ResponseCode::HTTP_404_NOT_FOUND,
  591                ACurl::ResponseCode::HTTP_405_METHOD_NOT_ALLOWED,
  592                ACurl::ResponseCode::HTTP_406_NOT_ACCEPTABLE,
  593                ACurl::ResponseCode::HTTP_407_PROXY_AUTHENTICATION_REQUIRED,
  594                ACurl::ResponseCode::HTTP_408_REQUEST_TIMEOUT,
  595                ACurl::ResponseCode::HTTP_409_CONFLICT,
  596                ACurl::ResponseCode::HTTP_410_GONE,
  597                ACurl::ResponseCode::HTTP_411_LENGTH_REQUIRED,
  598                ACurl::ResponseCode::HTTP_412_PRECONDITION_FAILED,
  599                ACurl::ResponseCode::HTTP_413_REQUEST_ENTITY_TOO_LARGE,
  600                ACurl::ResponseCode::HTTP_414_REQUEST_URI_TOO_LONG,
  601                ACurl::ResponseCode::HTTP_415_UNSUPPORTED_MEDIA_TYPE,
  602                ACurl::ResponseCode::HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
  603                ACurl::ResponseCode::HTTP_417_EXPECTATION_FAILED,
  604                ACurl::ResponseCode::HTTP_500_INTERNAL_SERVER_ERROR,
  605                ACurl::ResponseCode::HTTP_501_NOT_IMPLEMENTED,
  606                ACurl::ResponseCode::HTTP_502_BAD_GATEWAY,
  607                ACurl::ResponseCode::HTTP_503_SERVICE_UNAVAILABLE,
  608                ACurl::ResponseCode::HTTP_504_GATEWAY_TIMEOUT,
  609                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:189
Builder & withMultipart(const AFormMultipart &multipart)
Add multipart data.
Definition ACurl.h:238
Builder & withTimeout(std::chrono::seconds timeout)
Specifies acceptable response time.
Builder & withBody(std::string contents)
Like withBody with callback, but wrapped with string.
Definition ACurl.h:280
AFuture< Response > runAsync(ACurlMulti &curlMulti)
Constructs ACurl object and performs curl request in specified ACurlMulti.
_unique< IInputStream > toInputStream()
Makes input stream from curl builder.
Builder & withLowSpeedTime(std::chrono::seconds duration)
Duration that the transfer speed should be below the 'low speed limit' to consider it to be too slow ...
Builder & withLowSpeedLimit(size_t speed)
Set the average transfer speed in bytes per that the transfer should be below during 'low speed time'...
Builder & withRanges(size_t begin, size_t end)
Sets: Accept-Ranges: begin-end (download part of the file)
Builder & withWriteCallback(WriteCallback callback)
Called on server -> client data received (download).
Definition ACurl.h:214
Response runBlocking()
Constructs ACurl object and performs curl request in blocking mode. Use toFuture() instead if possibl...
Builder & withParams(AString params) noexcept
Sets HTTP params to the query.
Definition ACurl.h:421
Builder & withErrorCallback(ErrorCallback callback)
Definition ACurl.h:318
Builder & withHeaderCallback(HeaderCallback headerCallback)
Called on header received.
Definition ACurl.h:310
Builder & withParams(const AVector< std::pair< AString, AString > > &params)
Sets HTTP params to the query.
Builder & withInputStream(_< IInputStream > inputStream)
Called on client -> server data requested (upload).
Definition ACurl.h:260
Builder & withBody(ReadCallback callback)
Called on client -> server data requested (upload).
Definition ACurl.h:249
AFuture< Response > runAsync()
Constructs ACurl object and performs curl request in global ACurlMulti.
Builder & withMethod(Method method) noexcept
Sets HTTP method to the query.
Definition ACurl.h:398
Builder & withRanges(size_t begin)
Sets: Accept-Ranges: begin-end (download part of the file)
Definition ACurl.h:358
Builder & withWriteCallback(WriteCallbackV2 callback)
Called on server -> client data received (download).
Definition ACurl.h:226
Easy curl instance.
Definition ACurl.h:41
virtual void close()
Breaks curl loop in the run() method, closing underlying curl connection.
std::function< size_t(ACurl &curl, AByteBufferView data)> WriteCallbackV2
A read callback.
Definition ACurl.h:155
std::function< size_t(AByteBufferView data)> WriteCallback
A read callback.
Definition ACurl.h:142
emits success
Emitted on success.
Definition ACurl.h:558
emits< ErrorDescription > fail
Emitted on network error.
Definition ACurl.h:551
std::function< std::size_t(char *dst, size_t maxLen)> ReadCallback
A read callback.
Definition ACurl.h:167
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:621
Represents a Unicode character string.
Definition AString.h:38
A std::vector with AUI extensions.
Definition AVector.h:39
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:179
Avoids copy of the wrapped value, pointing to a reference.
Definition values.h:361
ASignal< Args... > emits
A signal declaration.
Definition ASignal.h:572
#define emit
emits the specified signal in context of this object.
Definition AObject.h:343
#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