├── .gitignore ├── src ├── qtcurl.pri ├── CurlMulti.h ├── CurlEasy.h ├── CurlMulti.cpp └── CurlEasy.cpp ├── examples └── downloader │ ├── main.cpp │ ├── downloader.pro │ ├── MainWindow.h │ ├── MainWindow.ui │ └── MainWindow.cpp ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | examples/build* 2 | *.pro.user 3 | -------------------------------------------------------------------------------- /src/qtcurl.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD/ 2 | 3 | SOURCES += \ 4 | $$PWD/CurlMulti.cpp \ 5 | $$PWD/CurlEasy.cpp 6 | 7 | HEADERS += \ 8 | $$PWD/CurlMulti.h \ 9 | $$PWD/CurlEasy.h 10 | -------------------------------------------------------------------------------- /examples/downloader/main.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/downloader/downloader.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets 2 | 3 | TARGET = downloader 4 | TEMPLATE = app 5 | 6 | include (../../src/qtcurl.pri) 7 | 8 | SOURCES += main.cpp\ 9 | MainWindow.cpp 10 | 11 | HEADERS += MainWindow.h 12 | 13 | FORMS += MainWindow.ui 14 | 15 | 16 | # Assume libcurl is installed at place where compiler sees it by default. 17 | # Just like after 'apt install libcurl4-openssl-dev' on ubuntu, for example 18 | 19 | LIBS += -lcurl 20 | -------------------------------------------------------------------------------- /examples/downloader/MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class QFile; 11 | class CurlEasy; 12 | 13 | class MainWindow : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit MainWindow(QWidget *parent = 0); 19 | ~MainWindow(); 20 | 21 | private slots: 22 | void on_startStopButton_clicked(); 23 | void onTransferProgress(qint64 downloadTotal, qint64 downloadNow, qint64 uploadTotal, qint64 uploadNow); 24 | void onTransferDone(); 25 | void onTransferAborted(); 26 | 27 | private: 28 | void log(QString text); 29 | 30 | Ui::MainWindow *ui; 31 | 32 | CurlEasy *transfer = nullptr; 33 | QFile *downloadFile = nullptr; 34 | }; 35 | 36 | #endif // MAINWINDOW_H 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/CurlMulti.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLMULTI_H 2 | #define CURLMULTI_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class QTimer; 9 | class CurlEasy; 10 | struct CurlMultiSocket; 11 | 12 | class CurlMulti : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit CurlMulti(QObject *parent = nullptr); 17 | virtual ~CurlMulti(); 18 | 19 | static CurlMulti* threadInstance(); 20 | 21 | void addTransfer(CurlEasy *transfer); 22 | void removeTransfer(CurlEasy *transfer); 23 | 24 | protected slots: 25 | void curlMultiTimeout(); 26 | void socketReadyRead(int socketDescriptor); 27 | void socketReadyWrite(int socketDescriptor); 28 | void socketException(int socketDescriptor); 29 | 30 | protected: 31 | void curlSocketAction(curl_socket_t socketDescriptor, int eventsBitmask); 32 | int curlTimerFunction(int timeoutMsec); 33 | int curlSocketFunction(CURL *easyHandle, curl_socket_t socketDescriptor, int action, CurlMultiSocket *socket); 34 | static int staticCurlTimerFunction(CURLM *multiHandle, long timeoutMs, void *userp); 35 | static int staticCurlSocketFunction(CURL *easyHandle, curl_socket_t socketDescriptor, int what, void *userp, void *sockp); 36 | 37 | QTimer *timer_ = nullptr; 38 | CURLM *handle_ = nullptr; 39 | 40 | QSet transfers_; 41 | }; 42 | 43 | #endif // CURLMULTIINTERFACE_H 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qtcurl 2 | Run **curl_easy** transfers asynchronously just inside your Qt main thread. 3 | 4 | Behind the scenes there is **curl_multi** legally hooked on Qt socket & timer events. Not very much of code needed for that actually. But if you're too lazy to deal with **curl_multi** in event-based system on your own, you'll probably be happy with these sources. 5 | 6 | Except the **curl_multi** thing now there's just only straightforward wraparound for **curl_easy** functions with a tiny bit of C++/Qt sugar. More things may be added later. 7 | 8 | 9 | ## Usage: 10 | ### 1. Set up linking and includes for *libcurl* 11 | Be strong and do it yourself. There should be plenty of docs for this. 12 | ### 2. Include *qtcurl.pri* into your *.pro* 13 | ```qmake 14 | include (qtcurl/src/qtcurl.pri) 15 | ``` 16 | ### 3. Write your code 17 | Get started with including the header: 18 | ```c++ 19 | #include "CurlEasy.h" 20 | ``` 21 | 22 | Create a **CurlEasy** object and set it up: 23 | ```c++ 24 | CurlEasy *curl = new CurlEasy; 25 | curl->set(CURLOPT_URL, "https://www.google.com"); 26 | curl->set(CURLOPT_FOLLOWLOCATION, long(1)); // Tells libcurl to follow HTTP 3xx redirects 27 | ``` 28 | 29 | You also may want to override some curl callbacks: 30 | ```c++ 31 | curl->setWriteFunction([](char *data, size_t size)->size_t { 32 | qDebug() << "Data from google.com: " << QByteArray(data, static_cast(size)); 33 | return size; 34 | }); 35 | ``` 36 | 37 | And set some HTTP headers: 38 | ```c++ 39 | curl->setHttpHeader("User-Agent", "My poor little application"); 40 | ``` 41 | 42 | Oh, the signals are there, of course! 43 | ```c++ 44 | QObject::connect(curl, &CurlEasy::done, [curl](CURLcode result) { 45 | long httpResponseCode = curl->get(CURLINFO_RESPONSE_CODE); 46 | QString effectiveUrl = curl->get(CURLINFO_EFFECTIVE_URL); 47 | 48 | qDebug() << "Transfer for" << effectiveUrl << "is done with" 49 | << "HTTP" << httpResponseCode 50 | << "and CURL code" << result; 51 | }); 52 | ``` 53 | ```c++ 54 | QObject::connect(curl, SIGNAL(done(CURLcode)), 55 | curl, SLOT(deleteLater())); 56 | ``` 57 | 58 | Now it's time to activate the transfer and pass it to all those event-looping things: 59 | ```c++ 60 | curl->perform(); 61 | 62 | // Do your Qt stuff while the request is processing. 63 | ``` 64 | 65 | Take these usage notes into account: 66 | - **done()** signal will NOT be emitted when the transfer is aborted by **abort()** method. **aborted()** will be emitted instead. This is a mostly convenience thing. In the most cases you don't want to do anything in **done()** when you've aborted the transfer externally. 67 | - By default **CurlEasy** will run on the event loop of the thread from which **perform()** was called. 68 | - Just as almost any **QObject** stuff, **CurlEasy** is not generally thread-safe. But just like other **QObject**s you can spawn it on any thread and safely use there. Even **moveToThread** is allowed for **CurlEasy** while the transfer is not running. 69 | - All **curl_multi**-related stuff will be created and set up on the first **CurlEasy::perform()** call in the current thread. 70 | - If you want it to be initialized earlier, just call **CurlMulti::threadInstance()** to kick that lazy thing. 71 | - All **curl_multi**-related stuff will be destroyed on thread exit (or **QApplication** exit, see Qt manual for **QThreadStorage**). If any related **CurlEasy** transfer has been running at this moment, it will receive **aborted()** signal. 72 | - (Some further notes) 73 | 74 | That's all for now. Dig into the sources for details =) 75 | -------------------------------------------------------------------------------- /examples/downloader/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 440 10 | 300 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 16777215 22 | 16777215 23 | 24 | 25 | 26 | qtcurl Downloader Example 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | QFormLayout::ExpandingFieldsGrow 36 | 37 | 38 | 39 | 40 | Download Url: 41 | 42 | 43 | 44 | 45 | 46 | 47 | http://google.com 48 | 49 | 50 | 51 | 52 | 53 | 54 | Download to File: 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 0 63 | 0 64 | 65 | 66 | 67 | data.bin 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 0 78 | 0 79 | 80 | 81 | 82 | Start 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 1024 92 | 93 | 94 | 0 95 | 96 | 97 | 98 | 99 | 100 | 101 | Transfer Log: 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/CurlEasy.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLEASY_H 2 | #define CURLEASY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class CurlMulti; 11 | 12 | class CurlEasy : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | using DataFunction = std::function; 17 | using SeekFunction = std::function; 18 | 19 | explicit CurlEasy(QObject *parent = nullptr); 20 | virtual ~CurlEasy(); 21 | 22 | void perform(); 23 | void abort(); 24 | bool isRunning() { return runningOnMulti_ != nullptr; } 25 | CURLcode result() { return lastResult_; } 26 | 27 | // For the list of available set options and valid parameter types consult curl_easy_setopt manual 28 | template bool set(CURLoption option, T parameter) { return curl_easy_setopt(handle_, option, parameter) == CURLE_OK; } 29 | bool set(CURLoption option, const QString ¶meter); // Convenience override for const char* parameters 30 | bool set(CURLoption option, const QUrl ¶meter); // Convenience override for const char* parameters 31 | void setReadFunction(const DataFunction &function); 32 | void setWriteFunction(const DataFunction &function); 33 | void setHeaderFunction(const DataFunction &function); 34 | void setSeekFunction(const SeekFunction &function); 35 | 36 | // For the list of available get options and valid parameter types consult curl_easy_getinfo manual 37 | template bool get(CURLINFO info, T *pointer) { return curl_easy_getinfo(handle_, info, pointer) == CURLE_OK; } 38 | template T get(CURLINFO info); 39 | 40 | 41 | QString httpHeader(const QString &header) const; 42 | void setHttpHeader(const QString &header, const QString &value); 43 | bool hasHttpHeader(const QString &header) const; 44 | void removeHttpHeader(const QString &header); 45 | 46 | QByteArray httpHeaderRaw(const QString &header) const; 47 | void setHttpHeaderRaw(const QString &header, const QByteArray &encodedValue); 48 | 49 | CURL* handle() { return handle_; } 50 | void setPreferredMulti(CurlMulti *multi) { preferredMulti_ = multi; } 51 | 52 | // Safety hack: substitue QObject's deleteLater whenever possible to make sure 53 | // that no callbacks will be called between deleteLater and curl handle removal. 54 | Q_SLOT void deleteLater(); 55 | 56 | signals: 57 | void aborted(); 58 | void progress(qint64 downloadTotal, qint64 downloadNow, qint64 uploadTotal, qint64 uploadNow); 59 | void done(CURLcode result); 60 | 61 | protected: 62 | void removeFromMulti(); 63 | void onCurlMessage(CURLMsg *message); 64 | void rebuildCurlHttpHeaders(); 65 | 66 | static size_t staticCurlReadFunction(char *data, size_t size, size_t nitems, void *easyPtr); 67 | static size_t staticCurlWriteFunction(char *data, size_t size, size_t nitems, void *easyPtr); 68 | static size_t staticCurlHeaderFunction(char *data, size_t size, size_t nitems, void *easyPtr); 69 | static int staticCurlSeekFunction(void *easyPtr, curl_off_t offset, int origin); 70 | static int staticCurlXferInfoFunction(void *easyPtr, curl_off_t downloadTotal, curl_off_t downloadNow, curl_off_t uploadTotal, curl_off_t uploadNow); 71 | 72 | 73 | CURL *handle_ = nullptr; 74 | CurlMulti *preferredMulti_ = nullptr; 75 | CurlMulti *runningOnMulti_ = nullptr; 76 | CURLcode lastResult_ = CURLE_OK; 77 | DataFunction readFunction_; 78 | DataFunction writeFunction_; 79 | DataFunction headerFunction_; 80 | SeekFunction seekFunction_; 81 | 82 | bool httpHeadersWereSet_ = false; 83 | QMap httpHeaders_; 84 | struct curl_slist* curlHttpHeaders_ = nullptr; 85 | 86 | friend class CurlMulti; 87 | }; 88 | 89 | template T CurlEasy::get(CURLINFO info) 90 | { 91 | T parameter; 92 | get(info, ¶meter); 93 | return parameter; 94 | } 95 | 96 | #endif // CURLTRANSFER_H 97 | -------------------------------------------------------------------------------- /examples/downloader/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "ui_MainWindow.h" 3 | #include "CurlEasy.h" 4 | 5 | MainWindow::MainWindow(QWidget *parent) : 6 | QMainWindow(parent), 7 | ui(new Ui::MainWindow) 8 | { 9 | ui->setupUi(this); 10 | 11 | // Create CurlEasy and connect signals. 12 | // Since curl_easy handles could be reused freely we can do it only once 13 | transfer = new CurlEasy(this); // Parent it so it will be destroyed automatically 14 | 15 | connect(transfer, &CurlEasy::done, this, &MainWindow::onTransferDone); 16 | connect(transfer, &CurlEasy::aborted, this, &MainWindow::onTransferAborted); 17 | connect(transfer, &CurlEasy::progress, this, &MainWindow::onTransferProgress); 18 | } 19 | 20 | MainWindow::~MainWindow() 21 | { 22 | delete ui; 23 | } 24 | 25 | void MainWindow::on_startStopButton_clicked() 26 | { 27 | // Are we "Abort" button? 28 | if (transfer->isRunning()) { 29 | transfer->abort(); 30 | return; 31 | } 32 | 33 | ui->transferLog->clear(); 34 | ui->progressBar->setValue(0); 35 | 36 | // Prepare target file 37 | downloadFile = new QFile(ui->fileNameEdit->text()); 38 | if (!downloadFile->open(QIODevice::WriteOnly)) { 39 | log("Failed to open file for writing."); 40 | delete downloadFile; 41 | downloadFile = nullptr; 42 | return; 43 | } 44 | 45 | // Set a simple file writing function 46 | transfer->setWriteFunction([this](char *data, size_t size)->size_t { 47 | qint64 bytesWritten = downloadFile->write(data, static_cast(size)); 48 | return static_cast(bytesWritten); 49 | }); 50 | 51 | // Print headers to the transfer log box 52 | transfer->setHeaderFunction([this](char *data, size_t size)->size_t { 53 | log(QString::fromUtf8(data, static_cast(size))); 54 | return size; 55 | }); 56 | 57 | transfer->set(CURLOPT_URL, QUrl(ui->urlEdit->text())); 58 | transfer->set(CURLOPT_FOLLOWLOCATION, long(1)); // Follow redirects 59 | transfer->set(CURLOPT_FAILONERROR, long(1)); // Do not return CURL_OK in case valid server responses reporting errors. 60 | 61 | ui->startStopButton->setText("Abort"); 62 | log("Transfer started."); 63 | 64 | transfer->perform(); 65 | } 66 | 67 | void MainWindow::onTransferProgress(qint64 downloadTotal, qint64 downloadNow, qint64 uploadTotal, qint64 uploadNow) 68 | { 69 | Q_UNUSED(uploadTotal); 70 | Q_UNUSED(uploadNow); 71 | 72 | if (downloadTotal > 0) { 73 | if (downloadNow > downloadTotal) downloadNow = downloadTotal; 74 | qint64 progress = (downloadNow * ui->progressBar->maximum())/downloadTotal; 75 | ui->progressBar->setValue(static_cast(progress)); 76 | } else { 77 | ui->progressBar->setValue(0); 78 | } 79 | } 80 | 81 | void MainWindow::onTransferDone() 82 | { 83 | if (transfer->result() != CURLE_OK) { 84 | log(QString("Transfer failed with curl error '%1'") 85 | .arg(curl_easy_strerror(transfer->result()))); 86 | downloadFile->remove(); 87 | } else { 88 | log(QString("Transfer complete. %1 bytes downloaded.") 89 | .arg(downloadFile->size())); 90 | ui->progressBar->setValue(ui->progressBar->maximum()); 91 | } 92 | 93 | delete downloadFile; 94 | downloadFile = nullptr; 95 | 96 | ui->startStopButton->setText("Start"); 97 | } 98 | 99 | void MainWindow::onTransferAborted() 100 | { 101 | log(QString("Transfer aborted. %1 bytes downloaded.") 102 | .arg(downloadFile->size())); 103 | 104 | downloadFile->remove(); 105 | delete downloadFile; 106 | downloadFile = nullptr; 107 | 108 | ui->startStopButton->setText("Start"); 109 | } 110 | 111 | void MainWindow::log(QString text) 112 | { 113 | // Remove extra newlines for headers to be printed neatly 114 | if (text.endsWith("\n")) text.chop(1); 115 | if (text.endsWith('\r')) text.chop(1); 116 | ui->transferLog->appendPlainText(text); 117 | } 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/CurlMulti.cpp: -------------------------------------------------------------------------------- 1 | // This disables min & max macros declaration in windows.h which will be included somewhere there 2 | #define NOMINMAX 3 | 4 | #include "CurlMulti.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "CurlEasy.h" 11 | 12 | struct CurlMultiSocket 13 | { 14 | curl_socket_t socketDescriptor = CURL_SOCKET_BAD; 15 | QSocketNotifier *readNotifier = nullptr; 16 | QSocketNotifier *writeNotifier = nullptr; 17 | QSocketNotifier *errorNotifier = nullptr; 18 | }; 19 | 20 | CurlMulti::CurlMulti(QObject *parent) 21 | : QObject(parent) 22 | , timer_(new QTimer(this)) 23 | { 24 | handle_ = curl_multi_init(); 25 | Q_ASSERT(handle_ != nullptr); 26 | 27 | curl_multi_setopt(handle_, CURLMOPT_SOCKETFUNCTION, staticCurlSocketFunction); 28 | curl_multi_setopt(handle_, CURLMOPT_SOCKETDATA, this); 29 | curl_multi_setopt(handle_, CURLMOPT_TIMERFUNCTION, staticCurlTimerFunction); 30 | curl_multi_setopt(handle_, CURLMOPT_TIMERDATA, this); 31 | 32 | timer_->setSingleShot(true); 33 | connect(timer_, &QTimer::timeout, this, &CurlMulti::curlMultiTimeout); 34 | } 35 | 36 | CurlMulti::~CurlMulti() 37 | { 38 | while (!transfers_.empty()) { 39 | (*transfers_.begin())->abort(); 40 | } 41 | 42 | if (handle_) { 43 | curl_multi_cleanup(handle_); 44 | } 45 | } 46 | 47 | CurlMulti *CurlMulti::threadInstance() 48 | { 49 | static QThreadStorage> instances; 50 | if (!instances.hasLocalData()) { 51 | instances.setLocalData(std::make_shared()); 52 | } 53 | return instances.localData().get(); 54 | } 55 | 56 | void CurlMulti::addTransfer(CurlEasy *transfer) 57 | { 58 | transfers_ << transfer; 59 | curl_multi_add_handle(handle_, transfer->handle()); 60 | } 61 | 62 | void CurlMulti::removeTransfer(CurlEasy *transfer) 63 | { 64 | if (transfers_.contains(transfer)) { 65 | curl_multi_remove_handle(handle_, transfer->handle()); 66 | transfers_.remove(transfer); 67 | } 68 | } 69 | 70 | int CurlMulti::curlSocketFunction(CURL *easyHandle, curl_socket_t socketDescriptor, int action, CurlMultiSocket *socket) 71 | { 72 | Q_UNUSED(easyHandle); 73 | if (!socket) { 74 | if (action == CURL_POLL_REMOVE || action == CURL_POLL_NONE) 75 | return 0; 76 | 77 | socket = new CurlMultiSocket; 78 | socket->socketDescriptor = socketDescriptor; 79 | curl_multi_assign(handle_, socketDescriptor, socket); 80 | } 81 | 82 | if (action == CURL_POLL_REMOVE) { 83 | curl_multi_assign(handle_, socketDescriptor, nullptr); 84 | 85 | // Note: deleteLater will NOT work here since there are 86 | // situations where curl subscribes same sockect descriptor 87 | // until events processing is done and actual delete happen. 88 | // This causes QSocketNotifier not to register notifications again. 89 | if (socket->readNotifier) delete socket->readNotifier; 90 | if (socket->writeNotifier) delete socket->writeNotifier; 91 | if (socket->errorNotifier) delete socket->errorNotifier; 92 | delete socket; 93 | return 0; 94 | } 95 | 96 | if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { 97 | if (!socket->readNotifier) { 98 | socket->readNotifier = new QSocketNotifier(socket->socketDescriptor, QSocketNotifier::Read); 99 | connect(socket->readNotifier, &QSocketNotifier::activated, this, &CurlMulti::socketReadyRead); 100 | } 101 | socket->readNotifier->setEnabled(true); 102 | } else { 103 | if (socket->readNotifier) 104 | socket->readNotifier->setEnabled(false); 105 | } 106 | 107 | if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { 108 | if (!socket->writeNotifier) { 109 | socket->writeNotifier = new QSocketNotifier(socket->socketDescriptor, QSocketNotifier::Write); 110 | connect(socket->writeNotifier, &QSocketNotifier::activated, this, &CurlMulti::socketReadyWrite); 111 | } 112 | socket->writeNotifier->setEnabled(true); 113 | } else { 114 | if (socket->writeNotifier) 115 | socket->writeNotifier->setEnabled(false); 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | int CurlMulti::curlTimerFunction(int timeoutMsec) 122 | { 123 | if (timeoutMsec >= 0) 124 | timer_->start(timeoutMsec); 125 | else 126 | timer_->stop(); 127 | 128 | return 0; 129 | } 130 | 131 | void CurlMulti::curlMultiTimeout() 132 | { curlSocketAction(CURL_SOCKET_TIMEOUT, 0); } 133 | 134 | void CurlMulti::socketReadyRead(int socketDescriptor) 135 | { curlSocketAction(socketDescriptor, CURL_CSELECT_IN); } 136 | 137 | void CurlMulti::socketReadyWrite(int socketDescriptor) 138 | { curlSocketAction(socketDescriptor, CURL_CSELECT_OUT); } 139 | 140 | void CurlMulti::socketException(int socketDescriptor) 141 | { curlSocketAction(socketDescriptor, CURL_CSELECT_ERR); } 142 | 143 | void CurlMulti::curlSocketAction(curl_socket_t socketDescriptor, int eventsBitmask) 144 | { 145 | int runningHandles; 146 | CURLMcode rc = curl_multi_socket_action(handle_, socketDescriptor, eventsBitmask, &runningHandles); 147 | if (rc != 0) { 148 | // TODO: Handle global curl errors 149 | } 150 | 151 | int messagesLeft = 0; 152 | do { 153 | CURLMsg *message = curl_multi_info_read(handle_, &messagesLeft); 154 | 155 | if (message == nullptr) 156 | break; 157 | 158 | if (message->easy_handle == nullptr) 159 | continue; 160 | 161 | CurlEasy *transfer = nullptr; 162 | curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, &transfer); 163 | 164 | if (transfer == nullptr) 165 | continue; 166 | 167 | transfer->onCurlMessage(message); 168 | } while (messagesLeft); 169 | } 170 | 171 | 172 | 173 | int CurlMulti::staticCurlSocketFunction(CURL *easyHandle, curl_socket_t socketDescriptor, int what, void *userp, void *sockp) 174 | { 175 | Q_UNUSED(easyHandle); 176 | CurlMulti *multi = static_cast(userp); 177 | Q_ASSERT(multi != nullptr); 178 | 179 | return multi->curlSocketFunction(easyHandle, socketDescriptor, what, static_cast(sockp)); 180 | } 181 | 182 | int CurlMulti::staticCurlTimerFunction(CURLM *multiHandle, long timeoutMs, void *userp) 183 | { 184 | Q_UNUSED(multiHandle); 185 | CurlMulti *multi = static_cast(userp); 186 | Q_ASSERT(multi != nullptr); 187 | 188 | int intTimeoutMs; 189 | 190 | if (timeoutMs >= std::numeric_limits::max()) 191 | intTimeoutMs = std::numeric_limits::max(); 192 | else if (timeoutMs >= 0) 193 | intTimeoutMs = static_cast(timeoutMs); 194 | else 195 | intTimeoutMs = -1; 196 | 197 | return multi->curlTimerFunction(intTimeoutMs); 198 | } 199 | 200 | -------------------------------------------------------------------------------- /src/CurlEasy.cpp: -------------------------------------------------------------------------------- 1 | #include "CurlEasy.h" 2 | #include "CurlMulti.h" 3 | 4 | CurlEasy::CurlEasy(QObject *parent) 5 | : QObject(parent) 6 | { 7 | handle_ = curl_easy_init(); 8 | Q_ASSERT(handle_ != nullptr); 9 | 10 | set(CURLOPT_PRIVATE, this); 11 | set(CURLOPT_XFERINFOFUNCTION, staticCurlXferInfoFunction); 12 | set(CURLOPT_XFERINFODATA, this); 13 | set(CURLOPT_NOPROGRESS, long(0)); 14 | } 15 | 16 | CurlEasy::~CurlEasy() 17 | { 18 | removeFromMulti(); 19 | 20 | if (handle_) { 21 | curl_easy_cleanup(handle_); 22 | } 23 | 24 | if (curlHttpHeaders_) { 25 | curl_slist_free_all(curlHttpHeaders_); 26 | curlHttpHeaders_ = nullptr; 27 | } 28 | } 29 | 30 | void CurlEasy::deleteLater() 31 | { 32 | removeFromMulti(); 33 | QObject::deleteLater(); 34 | } 35 | 36 | void CurlEasy::perform() 37 | { 38 | if (isRunning()) 39 | return; 40 | 41 | rebuildCurlHttpHeaders(); 42 | 43 | if (preferredMulti_) 44 | runningOnMulti_ = preferredMulti_; 45 | else 46 | runningOnMulti_ = CurlMulti::threadInstance(); 47 | 48 | runningOnMulti_->addTransfer(this); 49 | } 50 | 51 | void CurlEasy::abort() 52 | { 53 | if (!isRunning()) 54 | return; 55 | 56 | removeFromMulti(); 57 | 58 | emit aborted(); 59 | } 60 | 61 | void CurlEasy::removeFromMulti() 62 | { 63 | if (runningOnMulti_ != nullptr) { 64 | runningOnMulti_->removeTransfer(this); 65 | runningOnMulti_ = nullptr; 66 | } 67 | } 68 | 69 | void CurlEasy::onCurlMessage(CURLMsg *message) 70 | { 71 | if (message->msg == CURLMSG_DONE) { 72 | removeFromMulti(); 73 | lastResult_ = message->data.result; 74 | emit done(lastResult_); 75 | } 76 | } 77 | 78 | void CurlEasy::rebuildCurlHttpHeaders() 79 | { 80 | if (!httpHeadersWereSet_) 81 | return; 82 | 83 | if (curlHttpHeaders_) { 84 | curl_slist_free_all(curlHttpHeaders_); 85 | curlHttpHeaders_ = nullptr; 86 | } 87 | 88 | for (auto it = httpHeaders_.begin(); it != httpHeaders_.end(); ++it) { 89 | const QString &header = it.key(); 90 | const QByteArray &value = it.value(); 91 | 92 | QByteArray headerString = header.toUtf8(); 93 | headerString += ": "; 94 | headerString += value; 95 | headerString.append(char(0)); 96 | 97 | curlHttpHeaders_ = curl_slist_append(curlHttpHeaders_, headerString.constData()); 98 | } 99 | 100 | set(CURLOPT_HTTPHEADER, curlHttpHeaders_); 101 | } 102 | 103 | void CurlEasy::setReadFunction(const CurlEasy::DataFunction &function) 104 | { 105 | readFunction_ = function; 106 | if (readFunction_) { 107 | set(CURLOPT_READFUNCTION, staticCurlReadFunction); 108 | set(CURLOPT_READDATA, this); 109 | } else { 110 | set(CURLOPT_READFUNCTION, nullptr); 111 | set(CURLOPT_READDATA, nullptr); 112 | } 113 | } 114 | 115 | void CurlEasy::setWriteFunction(const CurlEasy::DataFunction &function) 116 | { 117 | writeFunction_ = function; 118 | if (writeFunction_) { 119 | set(CURLOPT_WRITEFUNCTION, staticCurlWriteFunction); 120 | set(CURLOPT_WRITEDATA, this); 121 | } else { 122 | set(CURLOPT_WRITEFUNCTION, nullptr); 123 | set(CURLOPT_WRITEDATA, nullptr); 124 | } 125 | } 126 | 127 | void CurlEasy::setHeaderFunction(const CurlEasy::DataFunction &function) 128 | { 129 | headerFunction_ = function; 130 | if (headerFunction_) { 131 | set(CURLOPT_HEADERFUNCTION, staticCurlHeaderFunction); 132 | set(CURLOPT_HEADERDATA, this); 133 | } else { 134 | set(CURLOPT_HEADERFUNCTION, nullptr); 135 | set(CURLOPT_HEADERDATA, nullptr); 136 | } 137 | } 138 | 139 | void CurlEasy::setSeekFunction(const CurlEasy::SeekFunction &function) 140 | { 141 | seekFunction_ = function; 142 | if (seekFunction_) { 143 | set(CURLOPT_SEEKFUNCTION, staticCurlSeekFunction); 144 | set(CURLOPT_SEEKDATA, this); 145 | } else { 146 | set(CURLOPT_SEEKFUNCTION, nullptr); 147 | set(CURLOPT_SEEKDATA, nullptr); 148 | } 149 | } 150 | 151 | size_t CurlEasy::staticCurlWriteFunction(char *data, size_t size, size_t nitems, void *easyPtr) 152 | { 153 | CurlEasy *easy = static_cast(easyPtr); 154 | Q_ASSERT(easy != nullptr); 155 | 156 | if (easy->writeFunction_) 157 | return easy->writeFunction_(data, size*nitems); 158 | else 159 | return size*nitems; 160 | } 161 | 162 | size_t CurlEasy::staticCurlHeaderFunction(char *data, size_t size, size_t nitems, void *easyPtr) 163 | { 164 | CurlEasy *easy = static_cast(easyPtr); 165 | Q_ASSERT(easy != nullptr); 166 | 167 | if (easy->headerFunction_) 168 | return easy->headerFunction_(data, size*nitems); 169 | else 170 | return size*nitems; 171 | } 172 | 173 | int CurlEasy::staticCurlSeekFunction(void *easyPtr, curl_off_t offset, int origin) 174 | { 175 | CurlEasy *easy = static_cast(easyPtr); 176 | Q_ASSERT(easy != nullptr); 177 | 178 | if (easy->seekFunction_) 179 | return easy->seekFunction_(static_cast(offset), origin); 180 | else 181 | return CURL_SEEKFUNC_CANTSEEK; 182 | } 183 | 184 | size_t CurlEasy::staticCurlReadFunction(char *buffer, size_t size, size_t nitems, void *easyPtr) 185 | { 186 | CurlEasy *transfer = static_cast(easyPtr); 187 | Q_ASSERT(transfer != nullptr); 188 | 189 | if (transfer->readFunction_) 190 | return transfer->readFunction_(buffer, size*nitems); 191 | else 192 | return size*nitems; 193 | } 194 | 195 | int CurlEasy::staticCurlXferInfoFunction(void *easyPtr, curl_off_t downloadTotal, curl_off_t downloadNow, curl_off_t uploadTotal, curl_off_t uploadNow) 196 | { 197 | CurlEasy *transfer = static_cast(easyPtr); 198 | Q_ASSERT(transfer != nullptr); 199 | 200 | emit transfer->progress(static_cast(downloadTotal), static_cast(downloadNow), 201 | static_cast(uploadTotal), static_cast(uploadNow)); 202 | 203 | return 0; 204 | } 205 | 206 | void CurlEasy::removeHttpHeader(const QString &header) 207 | { 208 | httpHeaders_.remove(header); 209 | httpHeadersWereSet_ = true; 210 | } 211 | 212 | QByteArray CurlEasy::httpHeaderRaw(const QString &header) const 213 | { 214 | return httpHeaders_[header]; 215 | } 216 | 217 | void CurlEasy::setHttpHeaderRaw(const QString &header, const QByteArray &encodedValue) 218 | { 219 | httpHeaders_[header] = encodedValue; 220 | httpHeadersWereSet_ = true; 221 | } 222 | 223 | bool CurlEasy::set(CURLoption option, const QString ¶meter) 224 | { return set(option, parameter.toUtf8().constData()); } 225 | 226 | bool CurlEasy::set(CURLoption option, const QUrl ¶meter) 227 | { return set(option, parameter.toEncoded().constData()); } 228 | 229 | void CurlEasy::setHttpHeader(const QString &header, const QString &value) 230 | { setHttpHeaderRaw(header, QUrl::toPercentEncoding(value)); } 231 | 232 | QString CurlEasy::httpHeader(const QString &header) const 233 | { return QUrl::fromPercentEncoding(httpHeaders_[header]); } 234 | 235 | bool CurlEasy::hasHttpHeader(const QString &header) const 236 | { return httpHeaders_.contains(header); } 237 | --------------------------------------------------------------------------------