1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "examples/provider/curl_http_client.h"
6
7 #include <algorithm>
8 #include <future>
9 #include <thread>
10
11 #include <base/bind.h>
12 #include <base/logging.h>
13 #include <curl/curl.h>
14 #include <weave/enum_to_string.h>
15 #include <weave/provider/task_runner.h>
16
17 namespace weave {
18 namespace examples {
19
20 namespace {
21
22 struct ResponseImpl : public provider::HttpClient::Response {
GetStatusCodeweave::examples::__anon2657179e0111::ResponseImpl23 int GetStatusCode() const override { return status; }
GetContentTypeweave::examples::__anon2657179e0111::ResponseImpl24 std::string GetContentType() const override { return content_type; }
GetDataweave::examples::__anon2657179e0111::ResponseImpl25 std::string GetData() const override { return data; }
26
27 long status{0};
28 std::string content_type;
29 std::string data;
30 };
31
WriteFunction(void * contents,size_t size,size_t nmemb,void * userp)32 size_t WriteFunction(void* contents, size_t size, size_t nmemb, void* userp) {
33 static_cast<std::string*>(userp)->append(static_cast<const char*>(contents),
34 size * nmemb);
35 return size * nmemb;
36 }
37
HeaderFunction(void * contents,size_t size,size_t nmemb,void * userp)38 size_t HeaderFunction(void* contents, size_t size, size_t nmemb, void* userp) {
39 std::string header(static_cast<const char*>(contents), size * nmemb);
40 auto pos = header.find(':');
41 if (pos != std::string::npos) {
42 std::pair<std::string, std::string> header_pair;
43
44 static const char kSpaces[] = " \t\r\n";
45 header_pair.first = header.substr(0, pos);
46 pos = header.find_first_not_of(kSpaces, pos + 1);
47 if (pos != std::string::npos) {
48 auto last_non_space = header.find_last_not_of(kSpaces);
49 if (last_non_space >= pos)
50 header_pair.second = header.substr(pos, last_non_space - pos + 1);
51 }
52
53 static_cast<provider::HttpClient::Headers*>(userp)->emplace_back(
54 std::move(header_pair));
55 }
56 return size * nmemb;
57 }
58
59 std::pair<std::unique_ptr<CurlHttpClient::Response>, ErrorPtr>
SendRequestBlocking(CurlHttpClient::Method method,const std::string & url,const CurlHttpClient::Headers & headers,const std::string & data)60 SendRequestBlocking(CurlHttpClient::Method method,
61 const std::string& url,
62 const CurlHttpClient::Headers& headers,
63 const std::string& data) {
64 std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl{curl_easy_init(),
65 &curl_easy_cleanup};
66 CHECK(curl);
67
68 switch (method) {
69 case CurlHttpClient::Method::kGet:
70 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L));
71 break;
72 case CurlHttpClient::Method::kPost:
73 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPPOST, 1L));
74 break;
75 case CurlHttpClient::Method::kPatch:
76 case CurlHttpClient::Method::kPut:
77 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST,
78 weave::EnumToString(method).c_str()));
79 break;
80 }
81
82 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()));
83
84 curl_slist* chunk = nullptr;
85 for (const auto& h : headers)
86 chunk = curl_slist_append(chunk, (h.first + ": " + h.second).c_str());
87
88 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, chunk));
89
90 if (!data.empty() || method == CurlHttpClient::Method::kPost) {
91 CHECK_EQ(CURLE_OK,
92 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, data.c_str()));
93 }
94
95 std::unique_ptr<ResponseImpl> response{new ResponseImpl};
96 CHECK_EQ(CURLE_OK,
97 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, &WriteFunction));
98 CHECK_EQ(CURLE_OK,
99 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response->data));
100 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION,
101 &HeaderFunction));
102 provider::HttpClient::Headers response_headers;
103 CHECK_EQ(CURLE_OK,
104 curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response_headers));
105
106 CURLcode res = curl_easy_perform(curl.get());
107 if (chunk)
108 curl_slist_free_all(chunk);
109
110 ErrorPtr error;
111 if (res != CURLE_OK) {
112 Error::AddTo(&error, FROM_HERE, "curl_easy_perform_error",
113 curl_easy_strerror(res));
114 return {nullptr, std::move(error)};
115 }
116
117 for (const auto& header : response_headers) {
118 if (header.first == "Content-Type")
119 response->content_type = header.second;
120 }
121
122 CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE,
123 &response->status));
124
125 return {std::move(response), nullptr};
126 }
127
128 } // namespace
129
CurlHttpClient(provider::TaskRunner * task_runner)130 CurlHttpClient::CurlHttpClient(provider::TaskRunner* task_runner)
131 : task_runner_{task_runner} {}
132
SendRequest(Method method,const std::string & url,const Headers & headers,const std::string & data,const SendRequestCallback & callback)133 void CurlHttpClient::SendRequest(Method method,
134 const std::string& url,
135 const Headers& headers,
136 const std::string& data,
137 const SendRequestCallback& callback) {
138 pending_tasks_.emplace_back(
139 std::async(std::launch::async, SendRequestBlocking, method, url, headers,
140 data),
141 callback);
142 if (pending_tasks_.size() == 1) // More means check is scheduled.
143 CheckTasks();
144 }
145
CheckTasks()146 void CurlHttpClient::CheckTasks() {
147 VLOG(4) << "CurlHttpClient::CheckTasks, size=" << pending_tasks_.size();
148 auto ready_begin =
149 std::partition(pending_tasks_.begin(), pending_tasks_.end(),
150 [](const decltype(pending_tasks_)::value_type& value) {
151 return value.first.wait_for(std::chrono::seconds(0)) !=
152 std::future_status::ready;
153 });
154
155 for (auto it = ready_begin; it != pending_tasks_.end(); ++it) {
156 CHECK(it->first.valid());
157 auto result = it->first.get();
158 VLOG(2) << "CurlHttpClient::CheckTasks done";
159 task_runner_->PostDelayedTask(
160 FROM_HERE, base::Bind(it->second, base::Passed(&result.first),
161 base::Passed(&result.second)),
162 {});
163 }
164
165 pending_tasks_.erase(ready_begin, pending_tasks_.end());
166
167 if (pending_tasks_.empty()) {
168 VLOG(2) << "No more CurlHttpClient tasks";
169 return;
170 }
171
172 task_runner_->PostDelayedTask(
173 FROM_HERE,
174 base::Bind(&CurlHttpClient::CheckTasks, weak_ptr_factory_.GetWeakPtr()),
175 base::TimeDelta::FromMilliseconds(100));
176 }
177
178 } // namespace examples
179 } // namespace weave
180