【问题标题】:Save cURL content result into a string in C++将 cURL 内容结果保存到 C++ 中的字符串中
【发布时间】:2012-04-04 21:09:33
【问题描述】:
int main(void)
{
  CURL *curl;
  CURLcode res;

  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }
  _getch();
  return 0;
}

string contents = "";

我想将 curl html 内容的结果保存在一个字符串中,我该怎么做? 这是一个愚蠢的问题,但不幸的是,我在 C++ 的 cURL 示例中找不到任何地方 谢谢!

【问题讨论】:

    标签: c++ curl


    【解决方案1】:

    我将 Joachim Isaksson 的答案与 CURLOPT_WRITEFUNCTION 的现代 C++ 改编一起使用。

    编译器不会对 C 风格的转换进行唠叨。

    static auto WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
      static_cast<string*>(userdata)->append(ptr, size * nmemb);
      return size * nmemb;
    }
    

    【讨论】:

      【解决方案2】:

      使用“新的”C++11 lambda 功能,只需几行代码即可完成。

      #ifndef WIN32 #define __stdcall "" #endif //For compatibility with both Linux and Windows
      std::string resultBody { };
      curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resultBody);
      curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast<size_t (__stdcall *)(char*, size_t, size_t, void*)>(
          [](char* ptr, size_t size, size_t nmemb, void* resultBody){
              *(static_cast<std::string*>(resultBody)) += std::string {ptr, size * nmemb};
              return size * nmemb;
          }
      ));
      
      CURLcode curlResult = curl_easy_perform(curl);
      std::cout << "RESULT BODY:\n" << resultBody << std::endl;
      // Cleanup etc
      

      注意 __stdcall 强制转换需要遵守 C 调用约定(cURL 是一个 C 库)

      【讨论】:

      • 据我所知,__stdcall 仅适用于 Windows,您可以在 Linux 领域安全地省略它:static_cast&lt;size_t (*)(char*, size_t, size_t, void*)&gt; '担心跨平台:#ifndef WIN32 #define __stdcall "" #endif
      • 你完全正确,完全忽略了这一点。我更新了答案。
      • 抱歉,格式在 cmets 中搞砸了。 #define#endif 应该有新的行。
      • 你不能这样做。 lambda 只存在于curl_easy_setopt() 的持续时间内,并将在curl_easy_perform() 之前被销毁。它会随机爆炸。
      • 我错了。一个类似的构造对我来说崩溃了,但这只是因为我没有转换 lambda。应该足以将其转换为void*,因为 curl 不关心类型,我们只需要发生 lambda 到函数指针的转换。
      【解决方案3】:

      在我的博客上,我有 published 一个简单的包装类来执行此任务。

      使用示例:

      #include "HTTPDownloader.hpp"
      
      int main(int argc, char** argv) {
          HTTPDownloader downloader;
          std::string content = downloader.download("https://stackoverflow.com");
          std::cout << content << std::endl;
      }
      

      这是头文件:

      /**
       * HTTPDownloader.hpp
       *
       * A simple C++ wrapper for the libcurl easy API.
       *
       * Written by Uli Köhler (techoverflow.net)
       * Published under CC0 1.0 Universal (public domain)
       */
      #ifndef HTTPDOWNLOADER_HPP
      #define HTTPDOWNLOADER_HPP
      
      #include <string>
      
      /**
       * A non-threadsafe simple libcURL-easy based HTTP downloader
       */
      class HTTPDownloader {
      public:
          HTTPDownloader();
          ~HTTPDownloader();
          /**
           * Download a file using HTTP GET and store in in a std::string
           * @param url The URL to download
           * @return The download result
           */
          std::string download(const std::string& url);
      private:
          void* curl;
      };
      
      #endif  /* HTTPDOWNLOADER_HPP */
      

      这里是源代码:

      /**
       * HTTPDownloader.cpp
       *
       * A simple C++ wrapper for the libcurl easy API.
       *
       * Written by Uli Köhler (techoverflow.net)
       * Published under CC0 1.0 Universal (public domain)
       */
      #include "HTTPDownloader.hpp"
      #include <curl/curl.h>
      #include <curl/easy.h>
      #include <curl/curlbuild.h>
      #include <sstream>
      #include <iostream>
      using namespace std;
      
      size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
          string data((const char*) ptr, (size_t) size * nmemb);
          *((stringstream*) stream) << data;
          return size * nmemb;
      }
      
      HTTPDownloader::HTTPDownloader() {
          curl = curl_easy_init();
      }
      
      HTTPDownloader::~HTTPDownloader() {
          curl_easy_cleanup(curl);
      }
      
      string HTTPDownloader::download(const std::string& url) {
          curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
          /* example.com is redirected, so we tell libcurl to follow redirection */
          curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
          curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
          curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "deflate");
          std::stringstream out;
          curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
          curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out);
          /* Perform the request, res will get the return code */
          CURLcode res = curl_easy_perform(curl);
          /* Check for errors */
          if (res != CURLE_OK) {
              fprintf(stderr, "curl_easy_perform() failed: %s\n",
                      curl_easy_strerror(res));
          }
          return out.str();
      }
      

      【讨论】:

        【解决方案4】:

        提出了有用但简单的解决方案,它重载了 std::ostream::operator

        #include <ostream>
        
        #include <curl/curl.h>
        
        size_t curlCbToStream (
            char * buffer,
            size_t nitems,
            size_t size,
            std::ostream * sout
        )
        {
            *sout << buffer;
        
            return nitems * size;
        }
        
        std::ostream & operator<< (
            std::ostream & sout,
            CURL * request
        )
        {
            ::curl_easy_setopt(request, CURLOPT_WRITEDATA, & sout);
            ::curl_easy_setopt(request, CURLOPT_WRITEFUNCTION, curlCbToStream);
            ::curl_easy_perform(request);
        
            return sout;
        }
        

        采取的方法的可能缺点是:

        typedef void CURL;
        

        这意味着它涵盖了所有已知的指针类型。

        【讨论】:

          【解决方案5】:

          基于@JoachimIsaksson 的回答,这里有一个更详细的输出,它处理内存不足并且对 curl 的最大输出有限制(因为 CURLOPT_MAXFILESIZE 限制仅基于标头信息,而不是基于传输的实际大小)。

          #DEFINE MAX_FILE_SIZE = 10485760 //10 MiB
          
          size_t curl_to_string(void *ptr, size_t size, size_t count, void *stream)
          {
              if(((string*)stream)->size() + (size * count) > MAX_FILE_SIZE)
              {
                  cerr<<endl<<"Could not allocate curl to string, output size (current_size:"<<((string*)stream)->size()<<"bytes + buffer:"<<(size * count) << "bytes) would exceed the MAX_FILE_SIZE ("<<MAX_FILE_SIZE<<"bytes)";
                  return 0;
              }
              int retry=0;
              while(true)
              {
                  try{
                      ((string*)stream)->append((char*)ptr, 0, size*count);
                      break;// successful
                  }catch (const std::bad_alloc&) {
                      retry++;
                      if(retry>100)
                      {
                          cerr<<endl<<"Could not allocate curl to string, probably not enough memory, aborting after : "<<retry<<" tries at 10s apart";
                          return 0;
                      }
                      cerr<<endl<<"Could not allocate curl to string, probably not enough memory, sleeping 10s, try:"<<retry;
                      sleep(10);
                  }
              }
            return size*count;
          }
          

          【讨论】:

            【解决方案6】:

            您必须使用CURLOPT_WRITEFUNCTION 设置回调以进行写入。我现在无法测试编译它,但函数看起来应该很接近;

            static std::string readBuffer;
            
            static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
            { 
                size_t realsize = size * nmemb;
                readBuffer.append(contents, realsize);
                return realsize;
            }
            

            然后通过doing调用它;

            readBuffer.clear();
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
            // ...other curl options
            res = curl_easy_perform(curl);
            

            通话结束后,readBuffer应该有你的内容了。

            编辑:您可以使用CURLOPT_WRITEDATA 传递缓冲区字符串,而不是使其成为静态。在这种情况下,为了简单起见,我只是将其设为静态。一个不错的页面(除了上面的链接示例)是here,用于解释选项。

            Edit2:根据要求,这是一个没有静态字符串缓冲区的完整工作示例;

            #include <iostream>
            #include <string>
            #include <curl/curl.h>
            
            
            static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
            {
                ((std::string*)userp)->append((char*)contents, size * nmemb);
                return size * nmemb;
            }
            
            int main(void)
            {
              CURL *curl;
              CURLcode res;
              std::string readBuffer;
            
              curl = curl_easy_init();
              if(curl) {
                curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
                curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
                curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
                res = curl_easy_perform(curl);
                curl_easy_cleanup(curl);
            
                std::cout << readBuffer << std::endl;
              }
              return 0;
            }
            

            【讨论】:

            • 嗨 Joachim,你能发布一个完整的代码吗?因为我无法让它工作。我在哪里可以做类似“cout
            • @Grego 添加了一个(在 linux 上编译)示例。
            • 重要提示:如果您在应用程序的任何位置使用多个线程,则必须添加 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);否则你会得到没有解释的段错误。
            • 真的很奇怪。此行导致程序锁定。 curl = curl_easy_init();即使它没有被调用。怎么可能?
            • 不要忘记检查 curl_easy_perform 中 res 的值。如果没有遇到错误,则返回 CURLE_OK。
            【解决方案7】:

            这可能不会马上起作用,但应该会给你一个想法:

            #include <string>
            #include <curl.h>
            #include <stdio.h>
            size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
                size_t written;
                written = fwrite(ptr, size, nmemb, stream);
                return written;
            }
            
            int main() {
                std::string tempname = "temp";
                CURL *curl;
                CURLcode res;
                curl = curl_easy_init();
                if(curl) {
                  FILE *fp = fopen(tempname.c_str(),"wb");
                  curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com");
                  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); 
                  curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
                  res = curl_easy_perform(curl);
                  curl_easy_cleanup(curl);
                  fclose(fp);
                  fp = fopen(tempname.c_str(),"rb");
                  fseek (fp , 0 , SEEK_END);
                  long lSize = ftell (fp);
                  rewind(fp);
                  char *buffer = new char[lSize+1];
                  fread (buffer, 1, lSize, fp);
                  buffer[lSize] = 0;
                  fclose(fp);
                  std::string content(buffer);
                  delete [] buffer;
                }
            }
            

            【讨论】:

            • 您会编辑并发布完整的代码吗?包装甚至包括?因为你没有定义缓冲区或 fp,我假设 fp 是 FILE *fp,但我不知道缓冲区是什么。我还是 C++ 的初学者
            • @Grego,目前我无法编译,添加了这些类型和包含。希望这会有所帮助
            • 嘿伙计,你忘了声明“缓冲区”变量,这实际上是我的问题。 :D 在缓冲区中声明什么?谢谢!
            • @Grego,声明为:char *buffer = new char[lSize+1];
            • 您的代码工作得很好,嗯,哦,是的,在您添加的编辑之后,我的错。 :/ 保存到文件也是我正在考虑的事情,非常感谢。 :) 我会 +1 你所有的 cmets 并发布
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-02-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多