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