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 17namespace weave { 18namespace examples { 19 20namespace { 21 22struct ResponseImpl : public provider::HttpClient::Response { 23 int GetStatusCode() const override { return status; } 24 std::string GetContentType() const override { return content_type; } 25 std::string GetData() const override { return data; } 26 27 long status{0}; 28 std::string content_type; 29 std::string data; 30}; 31 32size_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 38size_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 59std::pair<std::unique_ptr<CurlHttpClient::Response>, ErrorPtr> 60SendRequestBlocking(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 130CurlHttpClient::CurlHttpClient(provider::TaskRunner* task_runner) 131 : task_runner_{task_runner} {} 132 133void 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 146void 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