Реализация на C++ используя cURL с небольшим уклоном на Win платформу ( платформа зависимы пару строк )
Понадобился объект, для беспрепятственной скачки файлов с HTTP сервера.
После поиска по данной теме решение свелось к следующему:
— Использовать WinInet или MFC обертки над ним ( CInternetFile, CHttpFile )
— Использовать cURL
— Написать свою реализацию на сокетах
Последний вариант отпал в виду велосипедности.
От использования WinInet пришлось отказаться в виду невозможности докачки файлов при обрыве связи ( объект нужен для реализации программы обновления ).
Остался единственный из выбранных вариантов, который собственно изначально рассматривался приоритетным, но был поставлен в конец в виду ленивости (изучения материала по работе с cURL)
Начал искать готовые реализации подобного на cURL, но не чего толкового не попадалось, гугл выдал даже статью с хабра habrahabr.ru/blogs/cpp/84961 которая оказалась просто инструкцией по установке cURL и описанием обертки на C++ ( В плане реализации сказано там мало, только приведен пример ).
Ну что ж, нет так нет, пишем свою реализацию.
Требования:
— Скачивание непосредственно на диск ( для больших файлов )
— Скачивание в буфер ( для файлов которые нужно обработать, в моем контексте управляющих файлов обновления продукта, инструкции скачать тото, положить туда то, и удалить вот это )
— Возможность докачки больших файлов ( тех что записываются на диск )
— Отслеживание состояния и получение текста ошибки при возникновении
— Скачивание в фонов режиме ( потоке ), наверно одно из важнейших требований
Далее идет прототип объекта, который все описанное выше делает.
Понадобился объект, для беспрепятственной скачки файлов с HTTP сервера.
После поиска по данной теме решение свелось к следующему:
— Использовать WinInet или MFC обертки над ним ( CInternetFile, CHttpFile )
— Использовать cURL
— Написать свою реализацию на сокетах
Последний вариант отпал в виду велосипедности.
От использования WinInet пришлось отказаться в виду невозможности докачки файлов при обрыве связи ( объект нужен для реализации программы обновления ).
Остался единственный из выбранных вариантов, который собственно изначально рассматривался приоритетным, но был поставлен в конец в виду ленивости (изучения материала по работе с cURL)
Начал искать готовые реализации подобного на cURL, но не чего толкового не попадалось, гугл выдал даже статью с хабра habrahabr.ru/blogs/cpp/84961 которая оказалась просто инструкцией по установке cURL и описанием обертки на C++ ( В плане реализации сказано там мало, только приведен пример ).
Ну что ж, нет так нет, пишем свою реализацию.
Требования:
— Скачивание непосредственно на диск ( для больших файлов )
— Скачивание в буфер ( для файлов которые нужно обработать, в моем контексте управляющих файлов обновления продукта, инструкции скачать тото, положить туда то, и удалить вот это )
— Возможность докачки больших файлов ( тех что записываются на диск )
— Отслеживание состояния и получение текста ошибки при возникновении
— Скачивание в фонов режиме ( потоке ), наверно одно из важнейших требований
Далее идет прототип объекта, который все описанное выше делает.
Copy Source | Copy HTML<br/> <br/>class Downloader<br/>{<br/>public:<br/> class DFile<br/> {<br/> public:<br/> enum eDFileState<br/> {<br/> DFILE_STATE_NONE = 0, // Ещё не начали скачку ( не вызывали Work )<br/> DFILE_STATE_DOWNLOADING = 1, // Идет процесс скачки<br/> DFILE_STATE_ERROR = 2, // Произошла ошибка ( GetError подробности )<br/> DFILE_STATE_COMPLETE = 3, // Успешно скачано<br/> };<br/> DFile(); // Конструктор, обнуляет данные<br/> ~DFile(); // Деструктор, чистит за собой<br/> void Clean(); // Очистить данные, что бы можно было использовать объект повторно<br/> <br/> void SetURL( const char* sUrl ); // Установить урл для скачки<br/> void SetToFile( const char* sToFile ); // Установить файл для записи, если не установлен скачка происходит в буффер<br/> void SetUserAgent( const char* sUserAgent ); // Установить строку UserAgent<br/> void SetFileBadAttempts( int iFileBadAttempts ) { _iFileBadAttempts = iFileBadAttempts; } // Установить сколько допустимо ошибок<br/> <br/> // Скачка завершилась ? ВАЖНО! Пока скачка не будет завершена, объект нельзя удалять, GetState == DFILE_STATE_DOWNLOADING<br/> int GetState() { return _iState; }<br/> const char* GetError() { return _sError.Get(); }<br/> const size_t GetLength() { return _iLength; } // Получить длинну скачанных данных<br/> const UCHAR* GetData() { return _pData; } // Получить данные<br/> float GetPercent() { return _fPercentComplete; } // Получить завершенность скачки<br/> <br/> static size_t CallbackWriteMemory( void* pNewData, size_t size, size_t nmemb, DFile* pDFile ); // Готов кусок для записи в буффер<br/> static size_t CallbackWriteStream( void* pNewData, size_t size, size_t nmemb, FILE *stream ); // Готов кусок для записи в файл<br/> static int CallbackProgress( DFile* pDFile, double t, double d, double ultotal, double ulnow ); // Обновился статус готовности скачки<br/> <br/> void DownloadThread(); // Скачать, запускает в отдельном потоке метод Work<br/> void Work(); // Непосредственная обработка скачки<br/> <br/> private:<br/> String _sURL; // Собственно откуда скачиваем<br/> String _sToFile; // Куда сохранить ( на диске )<br/> String _sError; // Получить информацию об ошибке<br/> String _sUserAgent; // Строка HTTP заголовка UserAgent<br/> <br/> UCHAR* _pData; // Куда сохранить ( в памяти )<br/> size_t _iLength; // Сколько байт скачанно<br/> float _fPercentComplete; // Процент завершенности<br/> int _iState; // Состояние закачки<br/> int _iFileBadAttempts; // Макс количество неудачных попыток скачки, используется дляварианта с записью в файл ( обрывы связи например )<br/> };<br/> <br/> static void Init(); // Инициализировать необходиммые данные<br/> static void Destroy(); // Почистить за собой<br/> <br/>}; <br/>
Код хорошо документирован, все методы должны быть понятны.
Теперь реализация callback методов.
Copy Source | Copy HTML<br/>// Методы взяты из примеров кода с CURL http://curl.haxx.se/libcurl/c/example.html<br/>// И подкручены для своих нужд и стиля<br/>static void *myrealloc(void* ptr, size_t size)<br/>{<br/> /* There might be a realloc() out there that doesn't like reallocing<br/> NULL pointers, so we take care of it here */ <br/> if(ptr)<br/> return realloc(ptr, size);<br/> else<br/> return malloc(size);<br/>}<br/> <br/>//////////////////////////////////////////////////////////////////////////<br/>// Готов кусок для записи в буффер<br/>//////////////////////////////////////////////////////////////////////////<br/>size_t Downloader::DFile::CallbackWriteMemory(void* pNewData, size_t size, size_t nmemb, Downloader::DFile *pDFile )<br/>{<br/> size_t realsize = size * nmemb;<br/> <br/> pDFile->_pData = (UCHAR*)myrealloc(pDFile->_pData, pDFile->_iLength + realsize + 1);<br/> if (pDFile->_pData) {<br/> memcpy( &(pDFile->_pData[pDFile->_iLength]), pNewData, realsize );<br/> pDFile->_iLength += realsize;<br/> pDFile->_pData[pDFile->_iLength] = 0; // Запишем ноль байт, нужно для использования данные как C строки<br/> }<br/> return realsize;<br/>}<br/> <br/> <br/>//////////////////////////////////////////////////////////////////////////<br/>// Готов кусок для записи в файл<br/>//////////////////////////////////////////////////////////////////////////<br/>size_t Downloader::DFile::CallbackWriteStream( void* pNewData, size_t size, size_t nmemb, FILE *stream )<br/>{<br/> return fwrite( pNewData, size, nmemb, stream );<br/>}<br/> <br/> <br/>//////////////////////////////////////////////////////////////////////////<br/>// Обновился статус готовности скачки<br/>//////////////////////////////////////////////////////////////////////////<br/>int Downloader::DFile::CallbackProgress( Downloader::DFile* pDFile, double t, double d, double ultotal, double ulnow )<br/>{<br/> pDFile->_fPercentComplete = (float)d/t;<br/> return 0;<br/>} <br/>
Далее реализация основного метода Work который и занимается скачкой, и может вызываться как непосредственно, так и в виде фоновой работы в потоке методом DownloadThread
Copy Source | Copy HTML<br/>//////////////////////////////////////////////////////////////////////////<br/>// Обработка<br/>//////////////////////////////////////////////////////////////////////////<br/>void Downloader::DFile::Work()<br/>{<br/> CURL *curl;<br/> CURLcode res;<br/> <br/> curl = curl_easy_init();<br/> if( curl )<br/> {<br/> <br/> _iState = DFILE_STATE_DOWNLOADING; // Скажем что начали закачку, то есть можно начинать брать GetPercent<br/> <br/> // Если выбрали вариант записи в файл<br/> if( _sToFile.Length() > 0 )<br/> {<br/> int iCountBadPerform = _iFileBadAttempts; // Колчество попыток обработать, перед выходом<br/> FILE *outfile;<br/> outfile = fopen( _sToFile.Get(), "wb");<br/> <br/> if( outfile ) // Файл создать удалось<br/> {<br/> do <br/> {<br/> // Что качать<br/> curl_easy_setopt( curl, CURLOPT_URL, _sURL.Get() );<br/> <br/> // Устанавливаем callback функции и данные для них<br/> curl_easy_setopt( curl, CURLOPT_WRITEDATA, outfile );<br/> curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, CallbackWriteStream );<br/> curl_easy_setopt( curl, CURLOPT_NOPROGRESS, 0 );<br/> curl_easy_setopt( curl, CURLOPT_PROGRESSFUNCTION, CallbackProgress );<br/> curl_easy_setopt( curl, CURLOPT_PROGRESSDATA, this ) ;<br/> <br/> // Тут установим необходимые заголовки и timeout<br/> curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 20 );<br/> curl_easy_setopt( curl, CURLOPT_USERAGENT, _sUserAgent.Get() );<br/> <br/> // Установим смещение для скачиваемого файла, если мы уже часть закачали<br/> curl_easy_setopt( curl, CURLOPT_RESUME_FROM, ftell( outfile ) );<br/> <br/> res = curl_easy_perform(curl);<br/> <br/> } while( CURLE_OK != res && iCountBadPerform-- ); // Пока успешно не скачали или не израсходовали все попытки<br/> <br/> fclose(outfile);<br/> if( CURLE_OK != res )<br/> {<br/> _sError.SetEx( "Ошибка '%s': %s", _sToFile.Get(), curl_easy_strerror( res ) );<br/> _iState = DFILE_STATE_ERROR;<br/> }<br/> else<br/> {<br/> _iState = DFILE_STATE_COMPLETE;<br/> }<br/> }<br/> else<br/> {<br/> _sError.SetEx( "Ошибка записи '%s'", _sToFile.Get() );<br/> _iState = DFILE_STATE_ERROR;<br/> }<br/> }<br/> else // Иначе записываем в буфер<br/> {<br/> // Что качать<br/> curl_easy_setopt( curl, CURLOPT_URL, _sURL.Get() );<br/> <br/> // Устанавливаем callback функции и данные для них<br/> curl_easy_setopt(curl, CURLOPT_WRITEDATA, this );<br/> curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CallbackWriteMemory );<br/> curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0 );<br/> curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, CallbackProgress );<br/> curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this);<br/> <br/> <br/> // Тут установим необходимые заголовки и timeout<br/> curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 20 );<br/> curl_easy_setopt( curl, CURLOPT_USERAGENT, _sUserAgent.Get() );<br/> <br/> res = curl_easy_perform(curl);<br/> <br/> if( CURLE_OK != res )<br/> {<br/> _sError.SetEx( "Ошибка '%s': %s", _sToFile.Get(), curl_easy_strerror( res ) );<br/> _iState = DFILE_STATE_ERROR;<br/> }<br/> else<br/> {<br/> _iState = DFILE_STATE_COMPLETE;<br/> }<br/> }<br/> <br/> // Почистим curl<br/> curl_easy_cleanup(curl);<br/> }<br/> else // curl_easy_init fail<br/> {<br/> _sError = "Ошибка инициализации";<br/> _iState = DFILE_STATE_ERROR;<br/> }<br/>} <br/>
Теперь посмотрим как с ним работать.
Copy Source | Copy HTML<br/>Downloader::Init(); // Инициализируем cURL<br/> <br/>Downloader::DFile* remoteFile = new Downloader::DFile;<br/> <br/>// Что будем скачивать, для того что уведить прогресс надо скачивать большой файл<br/>remoteFile->SetURL( "http://habrahabr.ru/i/bg-multilogo.png" );<br/>remoteFile->SetToFile( "habra-logo.png" );<br/>remoteFile->DownloadThread();<br/> <br/>while( remoteFile->GetState() <= Downloader::DFile::DFILE_STATE_DOWNLOADING ) // Пока идет скачка<br/>{<br/> printf( "\rDownloading... %.2f%%", remoteFile->GetPercent()*100.0f ); // просто отображаем завершенность<br/>}<br/>if( remoteFile->GetState() == Downloader::DFile::DFILE_STATE_COMPLETE )<br/> printf( "\rDownload complete.\n" );<br/>else<br/> printf( "\rDownload error %s\n", remoteFile->GetError() );<br/> <br/>remoteFile->Clean(); // Сбросим все, что бы безболезненно использовать объект снова<br/> <br/>remoteFile->SetURL( "http://habrahabr.ru/js/1270814916/system.js" );<br/>remoteFile->Work(); // Уже без потока скачаем<br/>if( remoteFile->GetState() == Downloader::DFile::DFILE_STATE_COMPLETE )<br/> printf( (const char*)remoteFile->GetData() );<br/> <br/> <br/> <br/>delete remoteFile;<br/>Downloader::Destroy(); // Освободим ресурсы занимаемые cURL<br/> <br/>
Вот и все, получилось довольно просто в использовании, универсальный метод как для записи на диск так и в память.
В коде используется класс String, это своя реализация строки, по своим нуждам, во вложении есть его реализация посредством std::string.
По ссылке ниже вы можете скачать реализацию этого объекта, и пример использования.
Собрал все вместе, с инклудами и либами cURL что бы можно было без проблем протестировать данное решение.
реализация, 250 КБ
Спасибо разработчикам cURL что они для нас сделали такую замечательную библиотеку, реализовать данный объект не должно составить труда например на php и других объектно ориентированных языках поддерживающих cURL.
Спасибо за внимание.