【问题标题】:libcurl unable to properly download image filelibcurl 无法正确下载图像文件
【发布时间】:2020-04-20 08:52:30
【问题描述】:

我已经创建了这个非常基本的 curl 包装器,我可以用它下载 html 页面,但我遇到的问题是当我尝试获取图像时(没有尝试其他文件)。

class BasicCurlWrapper
{
    CURL* m_curlHandle{ nullptr };
    std::string m_current_url{};
    std::string m_destinationFilePath{};
    std::ofstream m_outputFile{};
    std::ios_base::openmode m_fileOpenMode{ std::ios::out };
    bool m_verbose{ false };

public:
    BasicCurlWrapper()
    {
        m_curlHandle = curl_easy_init();
    }

    ~BasicCurlWrapper()
    {
        curl_easy_cleanup(m_curlHandle);
        //curl_global_cleanup();
    }

    void downloadUrl(const std::string& url, const std::string& destination, std::ios_base::openmode openmode = std::ios::out) 
    {
        if (m_outputFile.is_open()) {
            m_outputFile.close();
        }

        m_current_url = url;
        m_destinationFilePath = destination;
        m_fileOpenMode = openmode;
        char errbuf[CURL_ERROR_SIZE] = { 0 };

        curl_easy_setopt(m_curlHandle, CURLOPT_URL, url.data());        
        curl_easy_setopt(m_curlHandle, CURLOPT_VERBOSE, m_verbose ? 1L : 0L); //Switch on full protocol/debug output while testing        
        curl_easy_setopt(m_curlHandle, CURLOPT_NOPROGRESS, 1L); //disable progress meter, set to 0L to enable it
        curl_easy_setopt(m_curlHandle, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(m_curlHandle, CURLOPT_USERAGENT, "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36");
        curl_easy_setopt(m_curlHandle, CURLOPT_WRITEFUNCTION, BasicCurlWrapper::write_data);
        curl_easy_setopt(m_curlHandle, CURLOPT_WRITEDATA, this);
        curl_easy_setopt(m_curlHandle, CURLOPT_FAILONERROR, 1L);
        curl_easy_setopt(m_curlHandle, CURLOPT_ERRORBUFFER, errbuf);
        //curl_easy_setopt(m_curlHandle, CURLOPT_ACCEPT_ENCODING, "");
        //curl_easy_setopt(m_curlHandle, CURLOPT_SSLCERT, "C:/msys64/mingw64/ssl/certs/ca-bundle.crt");

        auto res = curl_easy_perform(m_curlHandle);

        if (m_outputFile.is_open()) {
            m_outputFile.close();
        }

        if (res == CURLE_OK) {
            std::cout << "Downloaded file\n";
        } else {
            std::cout << "ERROR: " << curl_easy_strerror(res) << '\n' << errbuf << '\n';
        }
    }


    void setVerbose(bool cond)
    {
        m_verbose = cond;
    }

    //https://curl.haxx.se/mail/lib-2008-09/0250.html
    static std::size_t write_data(const char* ptr, const std::size_t size, const std::size_t nmemb, void* classIntance)
    {

        if (nmemb > 0) {
            static_cast<BasicCurlWrapper*>(classIntance)->writeToFile(ptr, nmemb);
        }
        return nmemb;
    }

private:

    void writeToFile(const char* ptr, const std::size_t nmemb)
    {
        if (!m_outputFile.is_open()) {
            m_outputFile.open(m_destinationFilePath, m_fileOpenMode);
        }        

        if (m_outputFile.is_open()) {
            std::cout << "Writing data amount: " << nmemb << '\n';
            m_outputFile.write(ptr, nmemb);
        } else {
            auto errorMsg{ std::string{"Unable to open file: " + m_destinationFilePath } };
            throw std::runtime_error{ errorMsg };
        }
    }
};

所以我是这样使用它的:

 BasicCurlWrapper cr;
 cr.setVerbose(true);
 cr.downloadUrl("https://icons.iconarchive.com/icons/google/noto-emoji-activities/512/52730-soccer-ball-icon.png", "ball.png", std::ios::out | std::ios::binary);

这确实会下载一些东西:

‰PNG

¾M&S»Á€>öÝÀKþé§ŸªC²²²Ð½{wÕ5–-[†…*7Þx½zõ¢C˜ž––L›6
555ŠÛŽ1þ³ºÂr­­­'­Å·Íê>ð^ùpAmèÀŽãœ.—«–@èEÀŒ±yJÛ)©éâàÔóÚÄ™ÄA]]¦NЦæfÅ÷uÍ5Tò—+Ö­[‡¾òŠªúÕ×^CvŸ>gtò'­É·ý›œü¹QYñÇÝér¹þmöçpÁð^¯w€AJÛFâR€–tîܹ=Ï cä`íÚµX»v­âëÙív,X°€ªþa…$I¸ë®»T•¾ðÂqß}÷µÏàÛÖä:„ŠŠ
Šbª$€Ðÿ.

虽然它以 PNG 开头,但这不是一个有效的 png,原始文件也是 39kb。 我是否必须发送一些额外的标题或其他东西?我希望能够下载任何指定的文件。

我使用vcpkg获取libcurl:

curl:x64-windows                                   7.68.0

编辑:

我已经更新了代码以反映@Some程序员老兄的答案 我现在使用write 将数据输出到文件中。 这已针对我使用的示例图像进行了修复。

我现在遇到的问题是我尝试下载的另一张图片。

cr.downloadUrl("https://v217.mangabeast.com/manga/Onepunch-Man/0130-007.png", "image.png", std::ios::out | std::ios::binary);

文件image.png 现在包含文本:

error code: 1010

我可以通过以下命令下载这张图片:

curl -O <url>

所以我没有通过 curl 命令传递任何东西,那么我需要在 libcurl 中传递什么??

这是请求的输出:

 * STATE: INIT => CONNECT handle 0x24781b66728; line 1605 (connection #-5000)
 * Added connection 0. The cache now contains 1 members
 * STATE: CONNECT => WAITRESOLVE handle 0x24781b66728; line 1646 (connection #0)
 *   Trying 104.31.15.158:443...
 * TCP_NODELAY set
 * STATE: WAITRESOLVE => WAITCONNECT handle 0x24781b66728; line 1725 (connection #0)
 * Connected to v217.mangabeast.com (104.31.15.158) port 443 (#0)
 * STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x24781b66728; line 1781 (connection #0)
 * Marked for [keep alive]: HTTP default
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 1/3)
 * schannel: checking server certificate revocation
 * schannel: sending initial handshake data: sending 184 bytes...
 * schannel: sent initial handshake data: sent 184 bytes
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: failed to receive handshake, need more data
 * STATE: SENDPROTOCONNECT => PROTOCONNECT handle 0x24781b66728; line 1796 (connection #0)
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: encrypted data got 2709
 * schannel: encrypted data buffer: offset 2709 length 4096
 * schannel: sending next handshake data: sending 93 bytes...
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 2/3)
 * schannel: encrypted data got 258
 * schannel: encrypted data buffer: offset 258 length 4096
 * schannel: SSL/TLS handshake complete
 * schannel: SSL/TLS connection with v217.mangabeast.com port 443 (step 3/3)
 * schannel: stored credential handle in session cache
 * STATE: PROTOCONNECT => DO handle 0x24781b66728; line 1815 (connection #0)
> GET /manga/Onepunch-Man/0130-007.png HTTP/1.1
Host: v217.mangabeast.com
User-Agent: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36
Accept: */*

 * STATE: DO => DO_DONE handle 0x24781b66728; line 1870 (connection #0)
 * STATE: DO_DONE => PERFORM handle 0x24781b66728; line 1991 (connection #0)
 * schannel: client wants to read 16384 bytes
 * schannel: encdata_buffer resized 17408
 * schannel: encrypted data buffer: offset 0 length 17408
 * schannel: encrypted data got 674
 * schannel: encrypted data buffer: offset 674 length 17408
 * schannel: decrypted data length: 611
 * schannel: decrypted data added: 611
 * schannel: decrypted cached: offset 611 length 16384
 * schannel: encrypted data length: 34
 * schannel: encrypted cached: offset 34 length 17408
 * schannel: decrypted data length: 5

EDIT2:

我现在添加了一些错误检查和错误失败。我得到了以下信息:

ERROR: HTTP response code said error
The requested URL returned error: 403 Forbidden

我不明白我是如何获得403 的,因为通过命令行使用 cURL 可以获得图像。

编辑 3:

刚刚注意到用户代理字符串有User-Agent:,在放入有效的用户代理后,我得到了文件!

【问题讨论】:

    标签: c++ http libcurl


    【解决方案1】:

    您有两个问题,都源于您将收到的数据视为文本。

    第一个问题是您以文本模式打开文件,这可能意味着某些字节被转换为其他字节(甚至是多个其他字节)。最常见的此类翻译是换行符 '\n',在 Windows 上通常会被翻译为两个字符序列 '\r''\n'

    第二个问题是您的writeToFile 函数假定数据是一个以空字符结尾的字符串,但事实并非如此。用于字符串的空终止符只是一个值为0 的字节。任意二进制数据(如 PNG 图像)将包含零字节。您需要使用 write 函数写入数据,将数据的实际长度(以字节为单位)传递给 cURL“写入数据”函数回调,通过 size 参数获得。

    要解决您的第一个问题,您需要在打开文件时添加std::ios::bin 标志以二进制模式打开文件。第二个问题可以通过前面提到的write函数来解决。

    【讨论】:

    • 嘿,所以我将std::ios::out | std::ios::binary 作为第三个参数传递,我认为它会以二进制形式打开,但我将使用write 函数。谢谢!
    • 所以你对 write 函数是正确的,但它不是 size 我应该使用但 nmemb 因为 size 始终为 1。如前所述,文件以二进制模式打开.谢谢
    • @AntonioCS 对,我记错了。 sizeelement 的大小,对于面向字节的文件,它始终是 1nmemb 是元素的数量。所以应该真正使用size * nmemb 来获得正确的尺寸。
    • 这将始终是 nmemb 的值,因为 size 始终为 1。write 函数确实解决了我用作示例的图像的问题,但对其他人来说似乎失败了.我会更新我的问题。
    • @AntonioCS Size 现在可能总是 1,但将来会一直如此吗?该值已传递给您的函数,因此您应该使用它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-31
    • 1970-01-01
    • 2016-09-30
    • 2013-02-19
    • 1970-01-01
    相关资源
    最近更新 更多