| // Copyright 2015 The Weave Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "examples/provider/curl_http_client.h" |
| |
| #include <algorithm> |
| #include <future> |
| #include <thread> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <curl/curl.h> |
| #include <weave/enum_to_string.h> |
| #include <weave/provider/task_runner.h> |
| |
| namespace weave { |
| namespace examples { |
| |
| namespace { |
| |
| struct ResponseImpl : public provider::HttpClient::Response { |
| int GetStatusCode() const override { return status; } |
| std::string GetContentType() const override { return content_type; } |
| std::string GetData() const override { return data; } |
| |
| long status{0}; |
| std::string content_type; |
| std::string data; |
| }; |
| |
| size_t WriteFunction(void* contents, size_t size, size_t nmemb, void* userp) { |
| static_cast<std::string*>(userp)->append(static_cast<const char*>(contents), |
| size * nmemb); |
| return size * nmemb; |
| } |
| |
| size_t HeaderFunction(void* contents, size_t size, size_t nmemb, void* userp) { |
| std::string header(static_cast<const char*>(contents), size * nmemb); |
| auto pos = header.find(':'); |
| if (pos != std::string::npos) { |
| std::pair<std::string, std::string> header_pair; |
| |
| static const char kSpaces[] = " \t\r\n"; |
| header_pair.first = header.substr(0, pos); |
| pos = header.find_first_not_of(kSpaces, pos + 1); |
| if (pos != std::string::npos) { |
| auto last_non_space = header.find_last_not_of(kSpaces); |
| if (last_non_space >= pos) |
| header_pair.second = header.substr(pos, last_non_space - pos + 1); |
| } |
| |
| static_cast<provider::HttpClient::Headers*>(userp)->emplace_back( |
| std::move(header_pair)); |
| } |
| return size * nmemb; |
| } |
| |
| std::pair<std::unique_ptr<CurlHttpClient::Response>, ErrorPtr> |
| SendRequestBlocking(CurlHttpClient::Method method, |
| const std::string& url, |
| const CurlHttpClient::Headers& headers, |
| const std::string& data) { |
| std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl{curl_easy_init(), |
| &curl_easy_cleanup}; |
| CHECK(curl); |
| |
| switch (method) { |
| case CurlHttpClient::Method::kGet: |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L)); |
| break; |
| case CurlHttpClient::Method::kPost: |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPPOST, 1L)); |
| break; |
| case CurlHttpClient::Method::kPatch: |
| case CurlHttpClient::Method::kPut: |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST, |
| weave::EnumToString(method).c_str())); |
| break; |
| } |
| |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str())); |
| |
| curl_slist* chunk = nullptr; |
| for (const auto& h : headers) |
| chunk = curl_slist_append(chunk, (h.first + ": " + h.second).c_str()); |
| |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, chunk)); |
| |
| if (!data.empty() || method == CurlHttpClient::Method::kPost) { |
| CHECK_EQ(CURLE_OK, |
| curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, data.c_str())); |
| } |
| |
| std::unique_ptr<ResponseImpl> response{new ResponseImpl}; |
| CHECK_EQ(CURLE_OK, |
| curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, &WriteFunction)); |
| CHECK_EQ(CURLE_OK, |
| curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response->data)); |
| CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION, |
| &HeaderFunction)); |
| provider::HttpClient::Headers response_headers; |
| CHECK_EQ(CURLE_OK, |
| curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response_headers)); |
| |
| CURLcode res = curl_easy_perform(curl.get()); |
| if (chunk) |
| curl_slist_free_all(chunk); |
| |
| ErrorPtr error; |
| if (res != CURLE_OK) { |
| Error::AddTo(&error, FROM_HERE, "curl_easy_perform_error", |
| curl_easy_strerror(res)); |
| return {nullptr, std::move(error)}; |
| } |
| |
| for (const auto& header : response_headers) { |
| if (header.first == "Content-Type") |
| response->content_type = header.second; |
| } |
| |
| CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, |
| &response->status)); |
| |
| return {std::move(response), nullptr}; |
| } |
| |
| } // namespace |
| |
| CurlHttpClient::CurlHttpClient(provider::TaskRunner* task_runner) |
| : task_runner_{task_runner} {} |
| |
| void CurlHttpClient::SendRequest(Method method, |
| const std::string& url, |
| const Headers& headers, |
| const std::string& data, |
| const SendRequestCallback& callback) { |
| pending_tasks_.emplace_back( |
| std::async(std::launch::async, SendRequestBlocking, method, url, headers, |
| data), |
| callback); |
| if (pending_tasks_.size() == 1) // More means check is scheduled. |
| CheckTasks(); |
| } |
| |
| void CurlHttpClient::CheckTasks() { |
| VLOG(4) << "CurlHttpClient::CheckTasks, size=" << pending_tasks_.size(); |
| auto ready_begin = |
| std::partition(pending_tasks_.begin(), pending_tasks_.end(), |
| [](const decltype(pending_tasks_)::value_type& value) { |
| return value.first.wait_for(std::chrono::seconds(0)) != |
| std::future_status::ready; |
| }); |
| |
| for (auto it = ready_begin; it != pending_tasks_.end(); ++it) { |
| CHECK(it->first.valid()); |
| auto result = it->first.get(); |
| VLOG(2) << "CurlHttpClient::CheckTasks done"; |
| task_runner_->PostDelayedTask( |
| FROM_HERE, base::Bind(it->second, base::Passed(&result.first), |
| base::Passed(&result.second)), |
| {}); |
| } |
| |
| pending_tasks_.erase(ready_begin, pending_tasks_.end()); |
| |
| if (pending_tasks_.empty()) { |
| VLOG(2) << "No more CurlHttpClient tasks"; |
| return; |
| } |
| |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&CurlHttpClient::CheckTasks, weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(100)); |
| } |
| |
| } // namespace examples |
| } // namespace weave |