1 #include <cassert>
2 #include <stdexcept>
3 #include <curl/curl.h>
4 #include "http/CurlHttpClient.h"
5 #include "string_format.h"
6 #include "PtrStream.h"
7
8 using namespace Framework;
9 using namespace Framework::Http;
10
readCallback(char * buffer,size_t size,size_t nmemb,void * userdata)11 static size_t readCallback(char* buffer, size_t size, size_t nmemb, void* userdata)
12 {
13 auto totalSize = size * nmemb;
14 auto inputStream = reinterpret_cast<Framework::CPtrStream*>(userdata);
15 return inputStream->Read(buffer, totalSize);
16 }
17
writeCallback(char * ptr,size_t size,size_t nmemb,void * userdata)18 static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
19 {
20 auto totalSize = size * nmemb;
21 auto outputStream = reinterpret_cast<decltype(Framework::Http::RequestResult::data)*>(userdata);
22 outputStream->Write(ptr, totalSize);
23 return totalSize;
24 }
25
headerCallback(char * buffer,size_t size,size_t nitems,void * userdata)26 static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userdata)
27 {
28 auto totalSize = size * nitems;
29 auto outputStream = reinterpret_cast<Framework::CMemStream*>(userdata);
30 outputStream->Write(buffer, totalSize);
31 return totalSize;
32 }
33
34 class CCurlRequest
35 {
36 public:
CCurlRequest()37 CCurlRequest()
38 {
39 m_curl = curl_easy_init();
40 }
41
~CCurlRequest()42 ~CCurlRequest()
43 {
44 curl_easy_cleanup(m_curl);
45 }
46
operator CURL*() const47 operator CURL*() const
48 {
49 return m_curl;
50 }
51
52 CCurlRequest(const CCurlRequest&) = delete;
53 CCurlRequest& operator =(const CCurlRequest&) = delete;
54
55 private:
56 CURL* m_curl = nullptr;
57 };
58
SendRequest()59 RequestResult CCurlHttpClient::SendRequest()
60 {
61 RequestResult result;
62 CCurlRequest curl;
63 assert(curl != nullptr);
64 {
65 Framework::CPtrStream bodyInputStream(m_requestBody.data(), m_requestBody.size());
66 Framework::CMemStream responseHeadersStream;
67
68 curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str());
69 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
70 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCallback);
71 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result.data);
72 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &readCallback);
73 curl_easy_setopt(curl, CURLOPT_READDATA, &bodyInputStream);
74 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &headerCallback);
75 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseHeadersStream);
76
77 switch(m_verb)
78 {
79 case HTTP_VERB::DELETE:
80 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
81 break;
82 case HTTP_VERB::HEAD:
83 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "HEAD");
84 break;
85 case HTTP_VERB::GET:
86 break;
87 case HTTP_VERB::PUT:
88 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
89 curl_easy_setopt(curl, CURLOPT_INFILESIZE, m_requestBody.size());
90 break;
91 default:
92 throw std::runtime_error("Unsupported HTTP verb.");
93 }
94
95 curl_slist* headerList = nullptr;
96 if(!m_headers.empty())
97 {
98 for(const auto& headerPair : m_headers)
99 {
100 auto headerString = headerPair.first + ": " + headerPair.second;
101 headerList = curl_slist_append(headerList, headerString.c_str());
102 }
103 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
104 }
105
106 auto performResult = curl_easy_perform(curl);
107
108 if(headerList)
109 {
110 curl_slist_free_all(headerList);
111 headerList = nullptr;
112 }
113
114 if(!(
115 (performResult == CURLE_OK) ||
116 ((performResult == CURLE_PARTIAL_FILE) && (m_verb == Framework::Http::HTTP_VERB::HEAD))
117 ))
118 {
119 auto errorMessage = string_format("Failed to execute request: %s.", curl_easy_strerror(performResult));
120 throw std::runtime_error(errorMessage);
121 }
122
123 long responseCode = 0;
124 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
125 result.statusCode = static_cast<Framework::Http::HTTP_STATUS_CODE>(responseCode);
126 result.data.Seek(0, Framework::STREAM_SEEK_SET);
127
128 responseHeadersStream.Seek(0, Framework::STREAM_SEEK_SET);
129 result.headers = ReadHeaderMap(responseHeadersStream);
130 }
131 return result;
132 }
133