libcyberradio  22.01.24
HttpsSession.cpp
1 /***************************************************************************
2  * \file HttpsSession.cpp
3  * \brief Implements an interface for establishing an HTTPS session.
4  * \author DA
5  * \copyright (c) 2017 CyberRadio Solutions, Inc. All rights reserved.
6  *
7  * \note Requires C++11 compiler support.
8  *
9  ***************************************************************************/
10 
11 #include "LibCyberRadio/Common/HttpsSession.h"
12 #include "LibCyberRadio/Common/Debuggable.h"
13 #include "LibCyberRadio/Common/Pythonesque.h"
14 #include <curl/curl.h>
15 #include <memory>
16 #include <cstring>
17 
18 
19 namespace LibCyberRadio
20 {
21 
22  // Singleton-pattern object whose entire purpose is to call
23  // curl_global_init() once, when the first HttpsSession object
24  // is instantiated.
25  class CurlGlobalInitializer : public Debuggable
26  {
27  public:
28  CurlGlobalInitializer(CurlGlobalInitializer const&) = delete;
29  CurlGlobalInitializer& operator=(CurlGlobalInitializer const&) = delete;
30 
31  static std::shared_ptr<CurlGlobalInitializer> instance()
32  {
33  static std::shared_ptr<CurlGlobalInitializer> s{new CurlGlobalInitializer};
34  return s;
35  }
36 
37  private:
38  CurlGlobalInitializer() :
39  Debuggable(false, "CurlGlobalInitializer")
40  {
41  this->debug("Initializing CURL\n");
42  curl_global_init(CURL_GLOBAL_ALL);
43  }
44  };
45 
46 
48  bool debug
49  ) :
50  Debuggable(debug, "HttpsSession"),
51  _session(curl_easy_init()),
52  _responseCode(0),
53  //_postHeaders(NULL),
54  //_postBuffer(NULL),
55  _lastReqErrInfo("")
56  {
57  CurlGlobalInitializer::instance();
58  this->debug("CONSTRUCTED\n");
59  }
60 
62  {
63  // Clear post header and data buffers
64  //clearPostBuffers();
65  // Clean up the session object
66  curl_easy_cleanup(_session);
67  this->debug("DESTROYED\n");
68  }
69 
71  const std::string& url,
72  bool verify
73  )
74  {
75  this->debug("[get] Called; URL=%s, verify=%s\n",
76  url.c_str(), this->debugBool(verify));
77  CURLcode res;
78  // Clear HTTPS request stuff
79  res = initializeRequest();
80  // Set request options
81  this->debug("[get] Setting request options\n");
82  curl_easy_setopt(_session, CURLOPT_URL, url.c_str());
83  curl_easy_setopt(_session, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L);
84  curl_easy_setopt(_session, CURLOPT_SSL_VERIFYHOST, verify ? 1L : 0L);
85  curl_easy_setopt(_session, CURLOPT_HTTPGET, 1L);
86  // Execute the request
87  this->debug("[get] Executing request\n");
88  res = curl_easy_perform(_session);
89  this->debug("[get] Request status: %d (%s)\n", res, this->debugCurl(res));
90  _lastReqErrInfo = this->debugCurl(res);
91  bool ret = (res == CURLE_OK);
92  if ( ret )
93  {
94  curl_easy_getinfo(_session, CURLINFO_RESPONSE_CODE, &_responseCode);
95  this->debug("[get] Response code: %ld\n", getResponseCode());
96  this->debug("[get] Header:\n%s\n", getResponseHeader().c_str());
97  this->debug("[get] Response:\n%s\n", getResponseBody().c_str());
98  // Handle HTTPS response codes that indicate error conditions
99  // The first line of the header will provide the reason why
100  if (_responseCode >= 400)
101  {
102  ret = false;
104  _lastReqErrInfo = vec[0];
105  }
106  }
107  this->debug("[get] Returning %s\n", this->debugBool(ret));
108  return ret;
109  }
110 
112  const std::string& url,
113  void* data,
114  size_t length,
115  const char* contentType,
116  bool verify
117  )
118  {
119  this->debug("[post] Called; URL=%s, verify=%s\n",
120  url.c_str(), this->debugBool(verify));
121  CURLcode res;
122  // Clear HTTPS request stuff
124  // Set request options
125  this->debug("[post] Setting request options\n");
126  curl_easy_setopt(_session, CURLOPT_VERBOSE, 0L);
127  curl_easy_setopt(_session, CURLOPT_URL, url.c_str());
128  curl_easy_setopt(_session, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L);
129  curl_easy_setopt(_session, CURLOPT_SSL_VERIFYHOST, verify ? 1L : 0L);
130  struct curl_slist* headers = NULL;
131  char ctheader[80];
132  memset(ctheader, 0, sizeof(ctheader));
133  strcpy(ctheader, "Content-type: ");
134  strcat(ctheader, contentType);
135  headers = curl_slist_append(headers, ctheader);
136  curl_easy_setopt(_session, CURLOPT_HTTPHEADER, headers);
137  curl_easy_setopt(_session, CURLOPT_POSTFIELDS, data);
138  curl_easy_setopt(_session, CURLOPT_POSTFIELDSIZE, (long)length);
139  curl_easy_setopt(_session, CURLOPT_POST, 1L);
140  // Execute the request
141  this->debug("[post] Executing request\n");
142  res = curl_easy_perform(_session);
143  this->debug("[post] Request status: %d (%s)\n", res, this->debugCurl(res));
144  _lastReqErrInfo = this->debugCurl(res);
145  bool ret = (res == CURLE_OK);
146  if ( ret )
147  {
148  curl_easy_getinfo(_session, CURLINFO_RESPONSE_CODE, &_responseCode);
149  this->debug("[post] Response code: %ld\n", getResponseCode());
150  this->debug("[post] Header:\n%s\n", getResponseHeader().c_str());
151  this->debug("[post] Response:\n%s\n", getResponseBody().c_str());
152  // Handle HTTPS response codes that indicate error conditions
153  // The first line of the header will provide the reason why
154  if (_responseCode >= 400)
155  {
156  ret = false;
158  _lastReqErrInfo = vec[0];
159  }
160  }
161  curl_slist_free_all(headers);
162  this->debug("[post] Returning %s\n", this->debugBool(ret));
163  return ret;
164  }
165 
167  {
168  return _responseCode;
169  }
170 
172  {
173  return _header.str();
174  }
175 
176  std::string HttpsSession::getResponseBody() const
177  {
178  return _response.str();
179  }
180 
182  {
183  return _lastReqErrInfo;
184  }
185 
187  char *ptr,
188  size_t size
189  )
190  {
191  //this->debug("[writeHeader] Called; size=%u\n", size);
192  _header.write(ptr, size);
193  //this->debug("[writeHeader] Returning\n");
194  return size;
195  }
196 
198  char *ptr,
199  size_t size
200  )
201  {
202  //this->debug("[writeResponseData] Called; size=%u\n", size);
203  _response.write(ptr, size);
204  //this->debug("[writeResponseData] Returning\n");
205  return size;
206  }
207 
209  {
210  this->debug("[initializeRequest] Called\n");
211  CURLcode res;
212  _header.clear();
213  _header.str("");
214  _response.clear();
215  _response.str("");
216  _responseCode = 0;
217  //clearPostBuffers();
218  curl_easy_reset(_session);
219  // Setting up the header buffer takes two steps:
220  // -- Set option CURLOPT_HEADERFUNCTION to the static
221  // headerCallback() method
222  // -- Set option CURLOPT_HEADERDATA to the "this" pointer
223  // for this object
224  res = curl_easy_setopt(_session, CURLOPT_HEADERFUNCTION,
226  if ( res == CURLE_OK )
227  {
228  res = curl_easy_setopt(_session, CURLOPT_HEADERDATA, this);
229  }
230  // Setting up the response buffer takes two steps:
231  // -- Set option CURLOPT_WRITEFUNCTION to the static
232  // writeDataCallback() method
233  // -- Set option CURLOPT_WRITEDATA to the "this" pointer
234  // for this object
235  if ( res == CURLE_OK )
236  {
237  res = curl_easy_setopt(_session, CURLOPT_WRITEFUNCTION,
239  }
240  if ( res == CURLE_OK )
241  {
242  res = curl_easy_setopt(_session, CURLOPT_WRITEDATA, this);
243  }
244  this->debug("[initializeRequest] Returning %d (%s)\n", res, this->debugCurl(res));
245  return res;
246  }
247 
249  CURLcode x
250  )
251  {
252  return curl_easy_strerror(x);
253  }
254 
256  char *buffer,
257  size_t size,
258  size_t nitems,
259  void *userdata
260  )
261  {
262  // "userdata" contains the "this" pointer to the session object
263  HttpsSession *session = (HttpsSession *)userdata;
264  size_t bytes = size * nitems;
265  return session->writeHeader(buffer, bytes);
266  }
267 
269  char *ptr,
270  size_t size,
271  size_t nmemb,
272  void *userdata
273  )
274  {
275  // "userdata" contains the "this" pointer to the session object
276  HttpsSession *session = (HttpsSession *)userdata;
277  size_t bytes = size * nmemb;
278  return session->writeResponseData(ptr, bytes);
279  }
280 
281 } /* namespace LibCyberRadio */
virtual CURLcode initializeRequest()
Initializes the session object to handle a new request.
virtual long getResponseCode() const
Gets the HTTPS response code from the last request.
static size_t writeDataCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
Callback function that libcurl "writes" data to when retrieving an HTTPS response body...
Debuggable & operator=(const Debuggable &other)
Assignment operator for Debuggable objects.
Definition: Debuggable.cpp:59
virtual size_t writeResponseData(char *ptr, size_t size)
Write data into the response buffer.
virtual std::string getResponseHeader() const
Gets the HTTPS header from the last request.
virtual ~HttpsSession()
Destroys an HttpsSession object.
Class that supports debug output.
Definition: Debuggable.h:38
static BasicStringList Split(const std::string &str, const std::string &sep, int maxsplit=INT_MAX)
Splits the given string into a list of string tokens.
Definition: Pythonesque.cpp:77
virtual int debug(const char *format,...)
Outputs debug information.
Definition: Debuggable.cpp:95
BASIC_LIST_CONTAINER< std::string > BasicStringList
Type representing a list of strings.
Definition: BasicList.h:25
virtual std::string getResponseBody() const
Gets the HTTPS response body from the last request.
static size_t headerCallback(char *buffer, size_t size, size_t nitems, void *userdata)
Callback function that libcurl "writes" data to when retrieving an HTTPS response header...
Defines functionality for LibCyberRadio applications.
Definition: App.h:23
virtual bool get(const std::string &url, bool verify=true)
Gets data from a given URL over HTTPS.
Debuggable(bool debug=false, const std::string &debug_name="", FILE *debug_fp=DEBUG_FP, const std::string &debug_timefmt=DEBUG_TIME_FMT)
Constructs a Debuggable object.
Definition: Debuggable.cpp:28
virtual const char * debugBool(bool x)
Gets a debug output string for a Boolean value.
Definition: Debuggable.cpp:126
virtual size_t writeHeader(char *ptr, size_t size)
Write data into the header buffer.
HttpsSession(bool debug=false)
Constructs an HttpsSession object.
virtual bool post(const std::string &url, void *data, size_t length, const char *contentType="text/plain", bool verify=true)
Posts data to a given URL over HTTPS.
Class that encapsulates an HTTPS session.
Definition: HttpsSession.h:29
virtual const char * debugCurl(CURLcode x)
Returns a string corresponding to a libcurl error code.
virtual std::string getLastRequestErrorInfo() const
Gets the error information for the last request.