xref: /aosp_15_r20/external/tensorflow/tensorflow/core/platform/cloud/curl_http_request.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow/core/platform/cloud/curl_http_request.h"
17 
18 #include <algorithm>
19 
20 #include "tensorflow/core/lib/core/errors.h"
21 #include "tensorflow/core/lib/gtl/map_util.h"
22 #include "tensorflow/core/platform/errors.h"
23 #include "tensorflow/core/platform/macros.h"
24 #include "tensorflow/core/platform/scanner.h"
25 #include "tensorflow/core/platform/str_util.h"
26 #include "tensorflow/core/platform/types.h"
27 #include "tensorflow/core/public/version.h"
28 #include "tensorflow/core/util/env_var.h"
29 
30 #define CHECK_CURL_OK(expr) CHECK_EQ(expr, CURLE_OK)
31 
32 namespace tensorflow {
33 
34 namespace {
35 
36 // Set to 1 to enable verbose debug output from curl.
37 constexpr uint64 kVerboseOutput = 0;
38 
39 // Proxy to the real libcurl implementation.
40 class LibCurlProxy : public LibCurl {
41  public:
Load()42   static LibCurlProxy* Load() {
43     static LibCurlProxy* libcurl = []() -> LibCurlProxy* {
44       curl_global_init(CURL_GLOBAL_ALL);
45       return new LibCurlProxy;
46     }();
47     return libcurl;
48   }
49 
curl_easy_init()50   CURL* curl_easy_init() override { return ::curl_easy_init(); }
51 
curl_easy_setopt(CURL * curl,CURLoption option,uint64 param)52   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
53                             uint64 param) override {
54     return ::curl_easy_setopt(curl, option, param);
55   }
56 
curl_easy_setopt(CURL * curl,CURLoption option,const char * param)57   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
58                             const char* param) override {
59     return ::curl_easy_setopt(curl, option, param);
60   }
61 
curl_easy_setopt(CURL * curl,CURLoption option,void * param)62   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
63                             void* param) override {
64     return ::curl_easy_setopt(curl, option, param);
65   }
66 
curl_easy_setopt(CURL * curl,CURLoption option,size_t (* param)(void *,size_t,size_t,FILE *))67   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
68                             size_t (*param)(void*, size_t, size_t,
69                                             FILE*)) override {
70     return ::curl_easy_setopt(curl, option, param);
71   }
72 
curl_easy_setopt(CURL * curl,CURLoption option,size_t (* param)(const void *,size_t,size_t,void *))73   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
74                             size_t (*param)(const void*, size_t, size_t,
75                                             void*)) override {
76     return ::curl_easy_setopt(curl, option, param);
77   }
78 
curl_easy_setopt(CURL * curl,CURLoption option,int (* param)(void * clientp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow))79   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
80                             int (*param)(void* clientp, curl_off_t dltotal,
81                                          curl_off_t dlnow, curl_off_t ultotal,
82                                          curl_off_t ulnow)) override {
83     return ::curl_easy_setopt(curl, option, param);
84   }
85 
curl_easy_perform(CURL * curl)86   CURLcode curl_easy_perform(CURL* curl) override {
87     return ::curl_easy_perform(curl);
88   }
89 
curl_easy_getinfo(CURL * curl,CURLINFO info,uint64 * value)90   CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info,
91                              uint64* value) override {
92     return ::curl_easy_getinfo(curl, info, value);
93   }
94 
curl_easy_getinfo(CURL * curl,CURLINFO info,double * value)95   CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info,
96                              double* value) override {
97     return ::curl_easy_getinfo(curl, info, value);
98   }
99 
curl_easy_cleanup(CURL * curl)100   void curl_easy_cleanup(CURL* curl) override {
101     return ::curl_easy_cleanup(curl);
102   }
103 
curl_easy_escape(CURL * curl,const char * str,int length)104   char* curl_easy_escape(CURL* curl, const char* str, int length) override {
105     return ::curl_easy_escape(curl, str, length);
106   }
107 
curl_slist_append(curl_slist * list,const char * str)108   curl_slist* curl_slist_append(curl_slist* list, const char* str) override {
109     return ::curl_slist_append(list, str);
110   }
111 
curl_slist_free_all(curl_slist * list)112   void curl_slist_free_all(curl_slist* list) override {
113     return ::curl_slist_free_all(list);
114   }
115 
curl_free(void * p)116   void curl_free(void* p) override { ::curl_free(p); }
117 };
118 }  // namespace
119 
CurlHttpRequest()120 CurlHttpRequest::CurlHttpRequest() : CurlHttpRequest(LibCurlProxy::Load()) {}
121 
CurlHttpRequest(LibCurl * libcurl,Env * env)122 CurlHttpRequest::CurlHttpRequest(LibCurl* libcurl, Env* env)
123     : libcurl_(libcurl), env_(env) {
124   default_response_buffer_.reserve(CURL_MAX_WRITE_SIZE);
125 
126   curl_ = libcurl_->curl_easy_init();
127   CHECK(curl_ != nullptr) << "Couldn't initialize a curl session.";
128 
129   // NOTE: The cURL CA bundle path is, by default, set to
130   //   etc/ssl/certs/ca-certificates.crt in tensorflow/third_party/curl.BUILD.
131   //   It can be customized with the CURL_CA_BUNDLE environment variable.
132   //   See also: https://curl.haxx.se/libcurl/c/CURLOPT_CAINFO.html.
133   std::string value = "";
134   TF_CHECK_OK(ReadStringFromEnvVar("CURL_CA_BUNDLE", "", &value));
135   if (!value.empty()) {
136     CHECK_CURL_OK(
137         libcurl_->curl_easy_setopt(curl_, CURLOPT_CAINFO, value.c_str()));
138   }
139   CHECK_CURL_OK(
140       libcurl_->curl_easy_setopt(curl_, CURLOPT_VERBOSE, kVerboseOutput));
141   CHECK_CURL_OK(libcurl_->curl_easy_setopt(
142       curl_, CURLOPT_USERAGENT,
143       strings::StrCat("TensorFlow/", TF_VERSION_STRING).c_str()));
144   // Do not use signals for timeouts - does not work in multi-threaded programs.
145   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L));
146 
147   // TODO(b/74351157): Enable HTTP/2.
148   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTP_VERSION,
149                                            CURL_HTTP_VERSION_1_1));
150 
151   // Set up the progress meter.
152   CHECK_CURL_OK(
153       libcurl_->curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, uint64{0}));
154   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFODATA, this));
155   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFOFUNCTION,
156                                            &CurlHttpRequest::ProgressCallback));
157 
158   // If response buffer is not set, libcurl will print results to stdout,
159   // so we always set it.
160   SetResultBuffer(&default_response_buffer_);
161 }
162 
~CurlHttpRequest()163 CurlHttpRequest::~CurlHttpRequest() {
164   if (curl_headers_) {
165     libcurl_->curl_slist_free_all(curl_headers_);
166   }
167   if (resolve_list_) {
168     libcurl_->curl_slist_free_all(resolve_list_);
169   }
170   if (put_body_) {
171     if (fclose(put_body_) != 0) {
172       LOG(ERROR) << "fclose() failed: " << strerror(errno);
173     }
174   }
175   if (curl_) {
176     libcurl_->curl_easy_cleanup(curl_);
177   }
178 }
179 
EscapeString(const string & str)180 string CurlHttpRequest::EscapeString(const string& str) {
181   char* out_char_str = libcurl_->curl_easy_escape(curl_, str.c_str(), 0);
182   string out_str(out_char_str);
183   libcurl_->curl_free(out_char_str);
184   return out_str;
185 }
186 
SetUri(const string & uri)187 void CurlHttpRequest::SetUri(const string& uri) {
188   CheckNotSent();
189   is_uri_set_ = true;
190   uri_ = uri;
191   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_URL, uri.c_str()));
192 }
193 
SetRange(uint64 start,uint64 end)194 void CurlHttpRequest::SetRange(uint64 start, uint64 end) {
195   CheckNotSent();
196   CHECK_CURL_OK(libcurl_->curl_easy_setopt(
197       curl_, CURLOPT_RANGE, strings::StrCat(start, "-", end).c_str()));
198 }
199 
AddHeader(const string & name,const string & value)200 void CurlHttpRequest::AddHeader(const string& name, const string& value) {
201   CheckNotSent();
202   curl_headers_ = libcurl_->curl_slist_append(
203       curl_headers_, strings::StrCat(name, ": ", value).c_str());
204 }
205 
AddResolveOverride(const string & hostname,int64_t port,const string & ip_addr)206 void CurlHttpRequest::AddResolveOverride(const string& hostname, int64_t port,
207                                          const string& ip_addr) {
208   CheckNotSent();
209   // Resolve values are hostname:port:IP.add.ress
210   resolve_list_ = libcurl_->curl_slist_append(
211       resolve_list_,
212       strings::StrCat(hostname, ":", port, ":", ip_addr).c_str());
213 }
214 
AddAuthBearerHeader(const string & auth_token)215 void CurlHttpRequest::AddAuthBearerHeader(const string& auth_token) {
216   CheckNotSent();
217   if (!auth_token.empty()) {
218     AddHeader("Authorization", strings::StrCat("Bearer ", auth_token));
219   }
220 }
221 
SetRequestStats(RequestStats * stats)222 void CurlHttpRequest::SetRequestStats(RequestStats* stats) {
223   CheckNotSent();
224   CHECK(stats_ == nullptr) << "SetRequestStats already called";
225   stats_ = stats;
226 }
227 
SetDeleteRequest()228 void CurlHttpRequest::SetDeleteRequest() {
229   CheckNotSent();
230   CheckMethodNotSet();
231   is_method_set_ = true;
232   method_ = RequestMethod::kDelete;
233   CHECK_CURL_OK(
234       libcurl_->curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
235 }
236 
SetPutFromFile(const string & body_filepath,size_t offset)237 Status CurlHttpRequest::SetPutFromFile(const string& body_filepath,
238                                        size_t offset) {
239   CheckNotSent();
240   CheckMethodNotSet();
241   is_method_set_ = true;
242   method_ = RequestMethod::kPut;
243   if (put_body_) {
244     if (fclose(put_body_) != 0) {
245       LOG(ERROR) << "fclose() failed: " << strerror(errno);
246     }
247   }
248   put_body_ = fopen(body_filepath.c_str(), "r");
249   if (!put_body_) {
250     return errors::InvalidArgument("Couldn't open the specified file: " +
251                                    body_filepath);
252   }
253   fseek(put_body_, 0, SEEK_END);
254   const auto size = ftell(put_body_) - offset;
255   fseek(put_body_, offset, SEEK_SET);
256 
257   curl_headers_ = libcurl_->curl_slist_append(
258       curl_headers_, strings::StrCat("Content-Length: ", size).c_str());
259   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1));
260   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
261                                            reinterpret_cast<void*>(put_body_)));
262   // Using the default CURLOPT_READFUNCTION, which is doing an fread() on the
263   // FILE * userdata set with CURLOPT_READDATA.
264   return OkStatus();
265 }
266 
SetPutEmptyBody()267 void CurlHttpRequest::SetPutEmptyBody() {
268   CheckNotSent();
269   CheckMethodNotSet();
270   is_method_set_ = true;
271   method_ = RequestMethod::kPut;
272   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1));
273   AddHeader("Content-Length", "0");
274   AddHeader("Transfer-Encoding", "identity");
275   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
276                                            reinterpret_cast<void*>(this)));
277   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
278                                            &CurlHttpRequest::ReadCallback));
279 }
280 
SetPostFromBuffer(const char * buffer,size_t size)281 void CurlHttpRequest::SetPostFromBuffer(const char* buffer, size_t size) {
282   CheckNotSent();
283   CheckMethodNotSet();
284   is_method_set_ = true;
285   method_ = RequestMethod::kPost;
286   curl_headers_ = libcurl_->curl_slist_append(
287       curl_headers_, strings::StrCat("Content-Length: ", size).c_str());
288   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1));
289   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
290                                            reinterpret_cast<void*>(this)));
291   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
292                                            &CurlHttpRequest::ReadCallback));
293   post_body_buffer_ = StringPiece(buffer, size);
294 }
295 
SetPostEmptyBody()296 void CurlHttpRequest::SetPostEmptyBody() {
297   CheckNotSent();
298   CheckMethodNotSet();
299   is_method_set_ = true;
300   method_ = RequestMethod::kPost;
301   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1));
302   AddHeader("Content-Length", "0");
303   AddHeader("Transfer-Encoding", "identity");
304   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
305                                            reinterpret_cast<void*>(this)));
306   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
307                                            &CurlHttpRequest::ReadCallback));
308 }
309 
SetResultBuffer(std::vector<char> * out_buffer)310 void CurlHttpRequest::SetResultBuffer(std::vector<char>* out_buffer) {
311   CheckNotSent();
312   CHECK(out_buffer != nullptr);
313 
314   out_buffer->clear();
315   response_buffer_ = out_buffer;
316 
317   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA,
318                                            reinterpret_cast<void*>(this)));
319   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION,
320                                            &CurlHttpRequest::WriteCallback));
321 }
322 
SetResultBufferDirect(char * buffer,size_t size)323 void CurlHttpRequest::SetResultBufferDirect(char* buffer, size_t size) {
324   CHECK(buffer != nullptr);
325   CheckNotSent();
326 
327   direct_response_ = DirectResponseState{buffer, size, 0, 0};
328   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA,
329                                            reinterpret_cast<void*>(this)));
330   CHECK_CURL_OK(libcurl_->curl_easy_setopt(
331       curl_, CURLOPT_WRITEFUNCTION, &CurlHttpRequest::WriteCallbackDirect));
332 }
333 
IsDirectResponse() const334 bool CurlHttpRequest::IsDirectResponse() const {
335   return direct_response_.buffer_ != nullptr;
336 }
337 
WriteCallbackDirect(const void * ptr,size_t size,size_t nmemb,void * userdata)338 size_t CurlHttpRequest::WriteCallbackDirect(const void* ptr, size_t size,
339                                             size_t nmemb, void* userdata) {
340   CHECK(ptr != nullptr);
341   auto that = reinterpret_cast<CurlHttpRequest*>(userdata);
342   DirectResponseState* state = &that->direct_response_;
343   CHECK(state->buffer_ != nullptr);
344   CHECK(state->bytes_transferred_ <= state->buffer_size_);
345 
346   size_t curl_bytes_received = size * nmemb;
347   size_t user_buffer_bytes_available =
348       state->buffer_size_ - state->bytes_transferred_;
349   size_t bytes_to_copy =
350       std::min<size_t>(curl_bytes_received, user_buffer_bytes_available);
351   memcpy(&state->buffer_[state->bytes_transferred_], ptr, bytes_to_copy);
352   state->bytes_transferred_ += bytes_to_copy;
353   state->bytes_received_ += curl_bytes_received;
354   // If we didn't have room to store the full response, returning less than
355   // curl_bytes_received here will abort the transfer and curl_easy_perform()
356   // will return CURLE_WRITE_ERROR. We will detect and handle this error there,
357   // and can use state->bytes_received_ as stored above for logging purposes.
358   return bytes_to_copy;
359 }
360 
GetResultBufferDirectBytesTransferred()361 size_t CurlHttpRequest::GetResultBufferDirectBytesTransferred() {
362   CHECK(direct_response_.buffer_ != nullptr);
363   return direct_response_.bytes_transferred_;
364 }
365 
SetTimeouts(uint32 connection,uint32 inactivity,uint32 total)366 void CurlHttpRequest::SetTimeouts(uint32 connection, uint32 inactivity,
367                                   uint32 total) {
368   CheckNotSent();
369   connect_timeout_secs_ = connection;
370   inactivity_timeout_secs_ = inactivity;
371   request_timeout_secs_ = total;
372 }
373 
WriteCallback(const void * ptr,size_t size,size_t nmemb,void * this_object)374 size_t CurlHttpRequest::WriteCallback(const void* ptr, size_t size,
375                                       size_t nmemb, void* this_object) {
376   CHECK(ptr);
377   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
378   CHECK(that->response_buffer_);
379   const size_t bytes_to_copy = size * nmemb;
380   that->response_buffer_->insert(
381       that->response_buffer_->end(), reinterpret_cast<const char*>(ptr),
382       reinterpret_cast<const char*>(ptr) + bytes_to_copy);
383 
384   return bytes_to_copy;
385 }
386 
ReadCallback(void * ptr,size_t size,size_t nmemb,FILE * this_object)387 size_t CurlHttpRequest::ReadCallback(void* ptr, size_t size, size_t nmemb,
388                                      FILE* this_object) {
389   CHECK(ptr);
390   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
391   CHECK(that->post_body_read_ <= that->post_body_buffer_.size());
392   const size_t bytes_to_copy = std::min(
393       size * nmemb, that->post_body_buffer_.size() - that->post_body_read_);
394   memcpy(ptr, that->post_body_buffer_.data() + that->post_body_read_,
395          bytes_to_copy);
396   that->post_body_read_ += bytes_to_copy;
397   return bytes_to_copy;
398 }
399 
HeaderCallback(const void * ptr,size_t size,size_t nmemb,void * this_object)400 size_t CurlHttpRequest::HeaderCallback(const void* ptr, size_t size,
401                                        size_t nmemb, void* this_object) {
402   CHECK(ptr);
403   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
404   StringPiece header(reinterpret_cast<const char*>(ptr), size * nmemb);
405   StringPiece name, value;
406   // The supplied header has the form "<name>: <value>", parse it.
407   if (strings::Scanner(header)
408           .ScanEscapedUntil(':')
409           .StopCapture()
410           .OneLiteral(": ")
411           .GetResult(&value, &name)) {
412     string str_value(value);
413     absl::StripTrailingAsciiWhitespace(&str_value);
414     that->response_headers_[string(name)] = str_value;
415   }
416   return size * nmemb;
417 }
418 
Send()419 Status CurlHttpRequest::Send() {
420   CheckNotSent();
421   CHECK(is_uri_set_) << "URI has not been set.";
422 
423   is_sent_ = true;
424 
425   if (curl_headers_) {
426     CHECK_CURL_OK(
427         libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers_));
428   }
429   if (resolve_list_) {
430     CHECK_CURL_OK(
431         libcurl_->curl_easy_setopt(curl_, CURLOPT_RESOLVE, resolve_list_));
432   }
433   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERDATA,
434                                            reinterpret_cast<void*>(this)));
435   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION,
436                                            &CurlHttpRequest::HeaderCallback));
437 
438   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_TIMEOUT,
439                                            request_timeout_secs_));
440   CHECK_CURL_OK(libcurl_->curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT,
441                                            connect_timeout_secs_));
442 
443   char error_buffer[CURL_ERROR_SIZE] = {0};
444   CHECK_CURL_OK(
445       libcurl_->curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buffer));
446 
447   if (stats_ != nullptr) {
448     stats_->RecordRequest(this, uri_, method_);
449   }
450 
451   const CURLcode curl_result = libcurl_->curl_easy_perform(curl_);
452   TF_RETURN_IF_ERROR(CURLcodeToStatus(curl_result, error_buffer));
453 
454   double written_size = 0;
455   CHECK_CURL_OK(libcurl_->curl_easy_getinfo(curl_, CURLINFO_SIZE_DOWNLOAD,
456                                             &written_size));
457 
458   CHECK_CURL_OK(libcurl_->curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE,
459                                             &response_code_));
460 
461   auto get_error_message = [this]() -> string {
462     string error_message = strings::StrCat(
463         "Error executing an HTTP request: HTTP response code ", response_code_);
464     StringPiece body = GetResponse();
465     if (!body.empty()) {
466       return strings::StrCat(
467           error_message, " with body '",
468           body.substr(0, std::min(body.size(), response_to_error_limit_)), "'");
469     }
470     return error_message;
471   };
472 
473   Status result;
474   switch (response_code_) {
475     // The group of response codes indicating that the request achieved
476     // the expected goal.
477     case 200:  // OK
478     case 201:  // Created
479     case 204:  // No Content
480     case 206:  // Partial Content
481       result = OkStatus();
482       break;
483 
484     case 416:  // Requested Range Not Satisfiable
485       // The requested range had no overlap with the available range.
486       // This doesn't indicate an error, but we should produce an empty response
487       // body. (Not all servers do; GCS returns a short error message body.)
488       response_buffer_->clear();
489       if (IsDirectResponse()) {
490         direct_response_.bytes_transferred_ = 0;
491       }
492       result = OkStatus();
493       break;
494 
495     // INVALID_ARGUMENT indicates a problem with how the request is constructed.
496     case 400:  // Bad Request
497     case 406:  // Not Acceptable
498     case 411:  // Length Required
499     case 414:  // URI Too Long
500       result = errors::InvalidArgument(get_error_message());
501       break;
502 
503     // PERMISSION_DENIED indicates an authentication or an authorization issue.
504     case 401:  // Unauthorized
505     case 403:  // Forbidden
506     case 407:  // Proxy Authorization Required
507       result = errors::PermissionDenied(get_error_message());
508       break;
509 
510     // NOT_FOUND indicates that the requested resource does not exist.
511     case 404:  // Not found
512     case 410:  // Gone
513       result = errors::NotFound(get_error_message());
514       break;
515 
516     // FAILED_PRECONDITION indicates that the request failed because some
517     // of the underlying assumptions were not satisfied. The request
518     // shouldn't be retried unless the external context has changed.
519     case 302:  // Found
520     case 303:  // See Other
521     case 304:  // Not Modified
522     case 307:  // Temporary Redirect
523     case 412:  // Precondition Failed
524     case 413:  // Payload Too Large
525       result = errors::FailedPrecondition(get_error_message());
526       break;
527 
528     // UNAVAILABLE indicates a problem that can go away if the request
529     // is just retried without any modification. 308 return codes are intended
530     // for write requests that can be retried. See the documentation and the
531     // official library:
532     // https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
533     // https://github.com/google/apitools/blob/master/apitools/base/py/transfer.py
534     case 308:  // Resume Incomplete
535     case 409:  // Conflict
536     case 429:  // Too Many Requests
537     case 500:  // Internal Server Error
538     case 502:  // Bad Gateway
539     case 503:  // Service Unavailable
540     default:   // All other HTTP response codes also should be retried.
541       result = errors::Unavailable(get_error_message());
542       break;
543   }
544   if (!result.ok()) {
545     response_buffer_->clear();
546   }
547 
548   if (stats_ != nullptr) {
549     stats_->RecordResponse(this, uri_, method_, result);
550   }
551 
552   return result;
553 }
554 
CheckMethodNotSet() const555 void CurlHttpRequest::CheckMethodNotSet() const {
556   CHECK(!is_method_set_) << "HTTP method has been already set.";
557 }
558 
CheckNotSent() const559 void CurlHttpRequest::CheckNotSent() const {
560   CHECK(!is_sent_) << "The request has already been sent.";
561 }
562 
GetResponse() const563 StringPiece CurlHttpRequest::GetResponse() const {
564   StringPiece response;
565   if (IsDirectResponse()) {
566     response = StringPiece(direct_response_.buffer_,
567                            direct_response_.bytes_transferred_);
568   } else {
569     response = StringPiece(response_buffer_->data(), response_buffer_->size());
570   }
571   return response;
572 }
573 
GetResponseHeader(const string & name) const574 string CurlHttpRequest::GetResponseHeader(const string& name) const {
575   const auto& header = response_headers_.find(name);
576   return header != response_headers_.end() ? header->second : "";
577 }
578 
GetResponseCode() const579 uint64 CurlHttpRequest::GetResponseCode() const { return response_code_; }
580 
581 // Cancels the transmission if no progress has been made for too long.
ProgressCallback(void * this_object,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)582 int CurlHttpRequest::ProgressCallback(void* this_object, curl_off_t dltotal,
583                                       curl_off_t dlnow, curl_off_t ultotal,
584                                       curl_off_t ulnow) {
585   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
586   const auto now = that->env_->NowSeconds();
587   const auto current_progress = dlnow + ulnow;
588   if (that->last_progress_timestamp_ == 0 ||
589       current_progress > that->last_progress_bytes_) {
590     // This is the first time the callback is called or some progress
591     // was made since the last tick.
592     that->last_progress_timestamp_ = now;
593     that->last_progress_bytes_ = current_progress;
594     return 0;
595   }
596 
597   if (now - that->last_progress_timestamp_ > that->inactivity_timeout_secs_) {
598     double lookup_time = -1;
599     const auto lookup_time_status = that->libcurl_->curl_easy_getinfo(
600         that->curl_, CURLINFO_NAMELOOKUP_TIME, &lookup_time);
601 
602     double connect_time = -1;
603     const auto connect_time_status = that->libcurl_->curl_easy_getinfo(
604         that->curl_, CURLINFO_CONNECT_TIME, &connect_time);
605 
606     double pretransfer_time = -1;
607     const auto pretransfer_time_status = that->libcurl_->curl_easy_getinfo(
608         that->curl_, CURLINFO_PRETRANSFER_TIME, &pretransfer_time);
609 
610     double starttransfer_time = -1;
611     const auto starttransfer_time_status = that->libcurl_->curl_easy_getinfo(
612         that->curl_, CURLINFO_STARTTRANSFER_TIME, &starttransfer_time);
613 
614     LOG(ERROR) << "The transmission  of request " << this_object
615                << " (URI: " << that->uri_ << ") has been stuck at "
616                << current_progress << " of " << dltotal + ultotal
617                << " bytes for " << now - that->last_progress_timestamp_
618                << " seconds and will be aborted. CURL timing information: "
619                << "lookup time: " << lookup_time << " ("
620                << curl_easy_strerror(lookup_time_status)
621                << "), connect time: " << connect_time << " ("
622                << curl_easy_strerror(connect_time_status)
623                << "), pre-transfer time: " << pretransfer_time << " ("
624                << curl_easy_strerror(pretransfer_time_status)
625                << "), start-transfer time: " << starttransfer_time << " ("
626                << curl_easy_strerror(starttransfer_time_status) << ")";
627     return 1;  // Will abort the request.
628   }
629 
630   // No progress was made since the last call, but we should wait a bit longer.
631   return 0;
632 }
633 
CURLcodeToStatus(CURLcode code,const char * error_buffer)634 Status CurlHttpRequest::CURLcodeToStatus(CURLcode code,
635                                          const char* error_buffer) {
636   if (code == CURLE_OK) {
637     return OkStatus();
638   }
639   string error_message = strings::StrCat(
640       "Error executing an HTTP request: libcurl code ", code, " meaning '",
641       curl_easy_strerror(code), "', error details: ");
642   // Special-case response-too-large errors as FAILED_PRECONDITION.
643   if (code == CURLE_WRITE_ERROR && IsDirectResponse() &&
644       direct_response_.bytes_received_ > direct_response_.buffer_size_) {
645     string overflow_message = strings::StrCat(
646         "Received ", direct_response_.bytes_received_, " response bytes ",
647         "for a ", direct_response_.buffer_size_, "-byte buffer");
648     uint64 response_code = 0;
649     const CURLcode get_response_result = libcurl_->curl_easy_getinfo(
650         curl_, CURLINFO_RESPONSE_CODE, &response_code);
651     // Special-case 416 Range Not Satisfied responses; they sometimes have
652     // a response body (e.g. GCS sends one with an error message) but we
653     // pretend as though they don't, so actually ignore this error.
654     if (get_response_result == CURLE_OK && response_code == 416) {
655       return OkStatus();
656     }
657     return errors::FailedPrecondition(
658         strings::StrCat(error_message, overflow_message));
659   }
660   // Domain resolution errors and certificate problems aren't going to improve
661   // on retry, so we return a FailedPrecondition (as the caller must take action
662   // before this can succeed).
663   if (code == CURLE_COULDNT_RESOLVE_HOST || code == CURLE_SSL_CACERT_BADFILE) {
664     return errors::FailedPrecondition(
665         strings::StrCat(error_message, error_buffer));
666   }
667   // Return Unavailable to retry by default. There may be other permanent
668   // failures that should be distinguished.
669   return errors::Unavailable(
670       strings::StrCat(error_message, *error_buffer ? error_buffer : "(none)"));
671 }
672 
673 }  // namespace tensorflow
674