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