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