kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/common/net_client.cc (about) 1 /* 2 * Copyright 2015 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "kythe/cxx/common/net_client.h" 18 19 #include <curl/curl.h> 20 #include <curl/easy.h> 21 #include <rapidjson/document.h> 22 23 #include <algorithm> 24 #include <cstddef> 25 #include <cstring> 26 #include <string> 27 28 #include "absl/log/check.h" 29 #include "absl/log/log.h" 30 #include "google/protobuf/io/coded_stream.h" 31 #include "google/protobuf/io/zero_copy_stream_impl_lite.h" 32 #include "google/protobuf/message.h" 33 #include "kythe/cxx/common/json_proto.h" 34 #include "rapidjson/error/en.h" 35 #include "rapidjson/stringbuffer.h" 36 #include "rapidjson/writer.h" 37 38 namespace kythe { 39 40 JsonClient::JsonClient() : curl_(::curl_easy_init()) { 41 CHECK(curl_ != nullptr); 42 } 43 44 JsonClient::~JsonClient() { 45 if (curl_) { 46 ::curl_easy_cleanup(curl_); 47 curl_ = nullptr; 48 } 49 } 50 51 void JsonClient::InitNetwork() { 52 CHECK(::curl_global_init(CURL_GLOBAL_ALL) == 0); 53 } 54 55 size_t JsonClient::CurlWriteCallback(void* data, size_t size, size_t nmemb, 56 void* user) { 57 JsonClient* client = static_cast<JsonClient*>(user); 58 size_t receive_head = client->received_.size(); 59 client->received_.resize(receive_head + size * nmemb); 60 ::memcpy(&client->received_[receive_head], data, size * nmemb); 61 return size * nmemb; 62 } 63 64 size_t JsonClient::CurlReadCallback(void* data, size_t size, size_t nmemb, 65 void* user) { 66 JsonClient* client = static_cast<JsonClient*>(user); 67 if (client->send_head_ >= client->to_send_.size()) { 68 return 0; 69 } 70 size_t bytes_to_send = 71 std::min(size * nmemb, client->to_send_.size() - client->send_head_); 72 ::memcpy(data, client->to_send_.data() + client->send_head_, bytes_to_send); 73 client->send_head_ += bytes_to_send; 74 return bytes_to_send; 75 } 76 77 bool JsonClient::Request(const std::string& uri, bool post, 78 const rapidjson::Document& request, 79 rapidjson::Document* response) { 80 rapidjson::StringBuffer string_buffer; 81 rapidjson::Writer<rapidjson::StringBuffer> writer(string_buffer); 82 request.Accept(writer); 83 return Request(uri, post, string_buffer.GetString(), response); 84 } 85 86 bool JsonClient::Request(const std::string& uri, bool post, 87 const std::string& request, 88 rapidjson::Document* response) { 89 std::string to_decode; 90 if (!Request(uri, post, request, &to_decode)) { 91 return false; 92 } 93 response->Parse(to_decode.c_str()); 94 if (response->HasParseError()) { 95 LOG(ERROR) << "(uri: " << uri << "): bad JSON at offset " 96 << response->GetErrorOffset() << ": " 97 << rapidjson::GetParseError_En(response->GetParseError()); 98 return false; 99 } 100 return true; 101 } 102 103 bool JsonClient::Request(const std::string& uri, bool post, 104 const std::string& request, std::string* response) { 105 to_send_ = request; 106 send_head_ = 0; 107 received_.clear(); 108 109 ::curl_easy_setopt(curl_, CURLOPT_URL, uri.c_str()); 110 ::curl_easy_setopt(curl_, CURLOPT_POST, post ? 1L : 0L); 111 ::curl_easy_setopt(curl_, CURLOPT_READFUNCTION, CurlReadCallback); 112 ::curl_easy_setopt(curl_, CURLOPT_READDATA, this); 113 ::curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, CurlWriteCallback); 114 ::curl_easy_setopt(curl_, CURLOPT_WRITEDATA, this); 115 ::curl_slist* headers = nullptr; 116 if (post) { 117 headers = ::curl_slist_append(headers, "Content-Type: application/json"); 118 ::curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers); 119 ::curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, request.size()); 120 } 121 122 ::CURLcode res = ::curl_easy_perform(curl_); 123 ::curl_slist_free_all(headers); 124 125 if (res) { 126 LOG(ERROR) << "(uri: " << uri << "): " << ::curl_easy_strerror(res); 127 return false; 128 } 129 130 long response_code; 131 res = ::curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code); 132 if (res) { 133 LOG(ERROR) << "(uri: " << uri << "): " << ::curl_easy_strerror(res); 134 return false; 135 } 136 if (response_code != 200) { 137 LOG(ERROR) << "(uri: " << uri << "): response " << response_code; 138 return false; 139 } 140 if (response) { 141 *response = received_; 142 } 143 return true; 144 } 145 146 bool XrefsJsonClient::Roundtrip(const std::string& endpoint, 147 const google::protobuf::Message& request, 148 google::protobuf::Message* response, 149 std::string* error_text) { 150 std::string request_json; 151 if (!WriteMessageAsJsonToString(request, &request_json)) { 152 if (error_text) { 153 *error_text = "Couldn't serialize message."; 154 } 155 return false; 156 } 157 std::string response_buffer; 158 if (!client_->Request(endpoint, true, request_json, &response_buffer)) { 159 if (error_text) { 160 *error_text = "Network client error."; 161 } 162 return false; 163 } 164 if (response) { 165 google::protobuf::io::ArrayInputStream stream(response_buffer.data(), 166 response_buffer.size()); 167 google::protobuf::io::CodedInputStream coded_stream(&stream); 168 if (!response->ParseFromCodedStream(&coded_stream)) { 169 if (error_text) { 170 *error_text = "Error decoding response protobuf."; 171 } 172 return false; 173 } 174 } 175 return true; 176 } 177 } // namespace kythe