3
\$\begingroup\$

I have written this sample code to fetch a web page in C++ using libcurl. Please review it.

#include <iostream>
#include <iostream>
#include <string>
#include <exception>

extern "C"
{
#include <curl/curl.h>
#include <stdlib.h>
}

//Exception class for curl exception

class CurlException : std::exception
{
public:
   CurlException(std::string message):m_message(message) {  }
   CurlException(CURLcode error)
   {
      m_message = curl_easy_strerror(error);
   }
   const char* what() throw()
   {
      return m_message.c_str();
   }
   ~CurlException() throw() { }

private:
   std::string m_message;
};


//A tiny wrapper around Curl C Library

class CppCurl
{
public:
   CppCurl(std::string url) throw (CurlException)
   {
      m_handle = curl_easy_init();
      if ( m_handle == NULL )
         throw CurlException("Unable to initialize curl handler");
      if ( url.length() == 0 )
         throw CurlException("URL can't be of zero length");
      m_url = url;
   }

   std::string Fetch() throw (CurlException)
   {
      SetOptions();
      SendGetRequest();
      return m_data;
   }


   ~CppCurl() throw()
   {
      curl_easy_cleanup(m_handle);
   }

private:

   void SetOptions() throw (CurlException)
   {
      CURLcode res;

      //set the url
      res = curl_easy_setopt(m_handle, CURLOPT_URL, m_url.c_str());
      if ( res != CURLE_OK)
         throw CurlException(res);

      //progress bar is not require
      res = curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 1L);
      if ( res != CURLE_OK )
         throw CurlException(res);

      //set the callback function
      res = curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION,
                              CppCurl::WriteDataCallback);
      if ( res != CURLE_OK )
         throw CurlException(res);

      //set pointer in call back function
      res = curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
      if ( res != CURLE_OK )
         throw CurlException(res);

   }

   void SendGetRequest()
   {
      CURLcode res;
      res = curl_easy_perform(m_handle);
      if ( res != CURLE_OK )
         throw CurlException(res);
   }

   static size_t WriteDataCallback(void *ptr, size_t size,
                                    size_t nmemb, void* pInstance)
   {
      return (static_cast<CppCurl*>(pInstance))->write_data(ptr, size, nmemb);
   }

   size_t write_data(void* ptr, size_t size, size_t nmemb)
   {
      size_t numOfBytes = size * nmemb;

      char *iter = (char*)ptr;
      char *iterEnd = iter + numOfBytes;

      //while ( iter != iterEnd )
      //{
      //   cout<<*iter;
      //   iter ++;
      //}

      m_data += std::string(iter, iterEnd);

      return numOfBytes;
   }

   CURL *m_handle;
   std::string m_url;
   std::string m_data;

};


int main()
{
   try
   {
      CppCurl ob("http://kodeyard.blogspot.in/");
      std::cout<<ob.Fetch();
      std::cout<<std::endl;
   }
   catch ( CurlException e)
   {
      std::cerr<<"Exception thrown..."<<std::endl;
      std::cerr<<e.what()<<std::endl;
   }

   return 0;

}

This is also here in the blog.

\$\endgroup\$
3
  • \$\begingroup\$ Good job guys! I put this code on github.com/asashnov/tinycurl So everyone can use the latest version and improve it. \$\endgroup\$
    – asashnov
    Mar 4, 2015 at 11:27
  • 1
    \$\begingroup\$ @asashnov - you cannot take code licensed using the CC-by-SA license, and then put it in the public domain. You need to read up on how copyright works. \$\endgroup\$
    – rolfl
    Mar 4, 2015 at 11:50
  • \$\begingroup\$ @rolfl I think it's ok. I wrote that own(means I own it). So, I am ok with that code being in public domain. \$\endgroup\$
    – asit_dhal
    Mar 5, 2015 at 22:35

1 Answer 1

7
\$\begingroup\$

You should probably derive from std::runtime_error.
It takes a string in the constructor as the message and stored it for use with what() in a way that is safe even in low memory situations:

Your exception class is then simplified too:

class CurlException : public std::runtime_error
{
public:
   CurlException(std::string const& message): std::runtime_error(message) {}
                         //  ^^^^^^^ pass message by const reference.
   CurlException(CURLcode error): std::runtime_error(curl_easy_strerror(error)) {}
};

The use of exception specifications in C++ was an experiment that ultimitely showed it was a bad idea. The only useful specification was the no throw specification (and then only when you made sure it really was no-throw).

void SetOptions() throw (CurlException)
              // ^^^^^^^^^^^^^^^^^^^^^ Get rid of this bit.

In C++11 exception specifications have been deprecated.

The curl library is a C library. It only knows about C calling conventions. Thus you can NOT pass it C++ functions. If it works it is pure luck and definately non portable.

  res = curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION,
                          CppCurl::WriteDataCallback);

Even though CppCurl::WriteDataCallback is a static function there is no guarantee that static methods have the same calling convention as a C function. Future versions of the compiler may break your code.

You should do something like this:

 extern "C" size_t WriteDataCallback(void *ptr, size_t size, size_t nmemb, void* pInstance)
 {
      CppCurl*   obj = reinterpret_cast<CppCurl*>(pInstance);
      return obj->write_data(ptr, size, nmemb);
 }

Not a big deal but I would have used insert rather than +=.

      m_data += std::string(iter, iterEnd);

 // -- or.

      m_data.insert(m_data.begin(), iter, iterEnd);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.