Home | History | Annotate | Download | only in cloud
      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 <algorithm>
     17 
     18 #include "tensorflow/core/platform/cloud/curl_http_request.h"
     19 
     20 #include "tensorflow/core/lib/core/errors.h"
     21 #include "tensorflow/core/lib/gtl/map_util.h"
     22 #include "tensorflow/core/lib/strings/scanner.h"
     23 #include "tensorflow/core/lib/strings/str_util.h"
     24 #include "tensorflow/core/platform/types.h"
     25 #include "tensorflow/core/public/version.h"
     26 
     27 namespace tensorflow {
     28 
     29 namespace {
     30 
     31 // Set to 1 to enable verbose debug output from curl.
     32 constexpr uint64 kVerboseOutput = 0;
     33 
     34 // Proxy to the real libcurl implementation.
     35 class LibCurlProxy : public LibCurl {
     36  public:
     37   static LibCurlProxy* Load() {
     38     static LibCurlProxy* libcurl = []() -> LibCurlProxy* {
     39       curl_global_init(CURL_GLOBAL_ALL);
     40       return new LibCurlProxy;
     41     }();
     42     return libcurl;
     43   }
     44 
     45   CURL* curl_easy_init() override { return ::curl_easy_init(); }
     46 
     47   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     48                             uint64 param) override {
     49     return ::curl_easy_setopt(curl, option, param);
     50   }
     51 
     52   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     53                             const char* param) override {
     54     return ::curl_easy_setopt(curl, option, param);
     55   }
     56 
     57   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     58                             void* param) override {
     59     return ::curl_easy_setopt(curl, option, param);
     60   }
     61 
     62   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     63                             size_t (*param)(void*, size_t, size_t,
     64                                             FILE*)) override {
     65     return ::curl_easy_setopt(curl, option, param);
     66   }
     67 
     68   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     69                             size_t (*param)(const void*, size_t, size_t,
     70                                             void*)) override {
     71     return ::curl_easy_setopt(curl, option, param);
     72   }
     73 
     74   CURLcode curl_easy_setopt(CURL* curl, CURLoption option,
     75                             int (*param)(void* clientp, curl_off_t dltotal,
     76                                          curl_off_t dlnow, curl_off_t ultotal,
     77                                          curl_off_t ulnow)) override {
     78     return ::curl_easy_setopt(curl, option, param);
     79   }
     80 
     81   CURLcode curl_easy_perform(CURL* curl) override {
     82     return ::curl_easy_perform(curl);
     83   }
     84 
     85   CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info,
     86                              uint64* value) override {
     87     return ::curl_easy_getinfo(curl, info, value);
     88   }
     89 
     90   CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info,
     91                              double* value) override {
     92     return ::curl_easy_getinfo(curl, info, value);
     93   }
     94 
     95   void curl_easy_cleanup(CURL* curl) override {
     96     return ::curl_easy_cleanup(curl);
     97   }
     98 
     99   char* curl_easy_escape(CURL* curl, const char* str, int length) override {
    100     return ::curl_easy_escape(curl, str, length);
    101   }
    102 
    103   curl_slist* curl_slist_append(curl_slist* list, const char* str) override {
    104     return ::curl_slist_append(list, str);
    105   }
    106 
    107   void curl_slist_free_all(curl_slist* list) override {
    108     return ::curl_slist_free_all(list);
    109   }
    110 
    111   void curl_free(void* p) override { ::curl_free(p); }
    112 
    113   const char* curl_easy_strerror(CURLcode errornum) override {
    114     return ::curl_easy_strerror(errornum);
    115   }
    116 };
    117 }  // namespace
    118 
    119 CurlHttpRequest::CurlHttpRequest() : CurlHttpRequest(LibCurlProxy::Load()) {}
    120 
    121 CurlHttpRequest::CurlHttpRequest(LibCurl* libcurl, Env* env)
    122     : libcurl_(libcurl), env_(env) {
    123   default_response_buffer_.reserve(CURL_MAX_WRITE_SIZE);
    124 
    125   curl_ = libcurl_->curl_easy_init();
    126   CHECK(curl_ != nullptr) << "Couldn't initialize a curl session.";
    127 
    128   // NOTE: CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt is configured by
    129   //       default in //third_party:curl.BUILD and can be customized via an
    130   //       environment variable.
    131 
    132   libcurl_->curl_easy_setopt(curl_, CURLOPT_VERBOSE, kVerboseOutput);
    133   libcurl_->curl_easy_setopt(
    134       curl_, CURLOPT_USERAGENT,
    135       strings::StrCat("TensorFlow/", TF_VERSION_STRING).c_str());
    136   // Do not use signals for timeouts - does not work in multi-threaded programs.
    137   libcurl_->curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
    138   libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTP_VERSION,
    139                              CURL_HTTP_VERSION_2_0);
    140 
    141   // Set up the progress meter.
    142   libcurl_->curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 0ULL);
    143   libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFODATA, this);
    144   libcurl_->curl_easy_setopt(curl_, CURLOPT_XFERINFOFUNCTION,
    145                              &CurlHttpRequest::ProgressCallback);
    146 
    147   // If response buffer is not set, libcurl will print results to stdout,
    148   // so we always set it.
    149   SetResultBuffer(&default_response_buffer_);
    150 }
    151 
    152 CurlHttpRequest::~CurlHttpRequest() {
    153   if (curl_headers_) {
    154     libcurl_->curl_slist_free_all(curl_headers_);
    155   }
    156   if (resolve_list_) {
    157     libcurl_->curl_slist_free_all(resolve_list_);
    158   }
    159   if (put_body_) {
    160     fclose(put_body_);
    161   }
    162   if (curl_) {
    163     libcurl_->curl_easy_cleanup(curl_);
    164   }
    165 }
    166 
    167 string CurlHttpRequest::EscapeString(const string& str) {
    168   char* out_char_str = libcurl_->curl_easy_escape(curl_, str.c_str(), 0);
    169   string out_str(out_char_str);
    170   libcurl_->curl_free(out_char_str);
    171   return out_str;
    172 }
    173 
    174 void CurlHttpRequest::SetUri(const string& uri) {
    175   CheckNotSent();
    176   is_uri_set_ = true;
    177   uri_ = uri;
    178   libcurl_->curl_easy_setopt(curl_, CURLOPT_URL, uri.c_str());
    179 }
    180 
    181 void CurlHttpRequest::SetRange(uint64 start, uint64 end) {
    182   CheckNotSent();
    183   libcurl_->curl_easy_setopt(curl_, CURLOPT_RANGE,
    184                              strings::StrCat(start, "-", end).c_str());
    185 }
    186 
    187 void CurlHttpRequest::AddHeader(const string& name, const string& value) {
    188   CheckNotSent();
    189   curl_headers_ = libcurl_->curl_slist_append(
    190       curl_headers_, strings::StrCat(name, ": ", value).c_str());
    191 }
    192 
    193 void CurlHttpRequest::AddResolveOverride(const string& hostname, int64 port,
    194                                          const string& ip_addr) {
    195   CheckNotSent();
    196   // Resolve values are hostname:port:IP.add.ress
    197   resolve_list_ = libcurl_->curl_slist_append(
    198       resolve_list_,
    199       strings::StrCat(hostname, ":", port, ":", ip_addr).c_str());
    200 }
    201 
    202 void CurlHttpRequest::AddAuthBearerHeader(const string& auth_token) {
    203   CheckNotSent();
    204   if (!auth_token.empty()) {
    205     AddHeader("Authorization", strings::StrCat("Bearer ", auth_token));
    206   }
    207 }
    208 
    209 void CurlHttpRequest::SetDeleteRequest() {
    210   CheckNotSent();
    211   CheckMethodNotSet();
    212   is_method_set_ = true;
    213   libcurl_->curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
    214 }
    215 
    216 Status CurlHttpRequest::SetPutFromFile(const string& body_filepath,
    217                                        size_t offset) {
    218   CheckNotSent();
    219   CheckMethodNotSet();
    220   is_method_set_ = true;
    221   if (put_body_) {
    222     fclose(put_body_);
    223   }
    224   put_body_ = fopen(body_filepath.c_str(), "r");
    225   if (!put_body_) {
    226     return errors::InvalidArgument("Couldn't open the specified file: " +
    227                                    body_filepath);
    228   }
    229   fseek(put_body_, 0, SEEK_END);
    230   const auto size = ftell(put_body_) - offset;
    231   fseek(put_body_, offset, SEEK_SET);
    232 
    233   curl_headers_ = libcurl_->curl_slist_append(
    234       curl_headers_, strings::StrCat("Content-Length: ", size).c_str());
    235   libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1);
    236   libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
    237                              reinterpret_cast<void*>(put_body_));
    238   // Using the default CURLOPT_READFUNCTION, which is doing an fread() on the
    239   // FILE * userdata set with CURLOPT_READDATA.
    240   return Status::OK();
    241 }
    242 
    243 void CurlHttpRequest::SetPutEmptyBody() {
    244   CheckNotSent();
    245   CheckMethodNotSet();
    246   is_method_set_ = true;
    247   libcurl_->curl_easy_setopt(curl_, CURLOPT_PUT, 1);
    248   curl_headers_ =
    249       libcurl_->curl_slist_append(curl_headers_, "Content-Length: 0");
    250   libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
    251                              reinterpret_cast<void*>(this));
    252   libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
    253                              &CurlHttpRequest::ReadCallback);
    254 }
    255 
    256 void CurlHttpRequest::SetPostFromBuffer(const char* buffer, size_t size) {
    257   CheckNotSent();
    258   CheckMethodNotSet();
    259   is_method_set_ = true;
    260   curl_headers_ = libcurl_->curl_slist_append(
    261       curl_headers_, strings::StrCat("Content-Length: ", size).c_str());
    262   libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1);
    263   libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
    264                              reinterpret_cast<void*>(this));
    265   libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
    266                              &CurlHttpRequest::ReadCallback);
    267   post_body_buffer_ = StringPiece(buffer, size);
    268 }
    269 
    270 void CurlHttpRequest::SetPostEmptyBody() {
    271   CheckNotSent();
    272   CheckMethodNotSet();
    273   is_method_set_ = true;
    274   libcurl_->curl_easy_setopt(curl_, CURLOPT_POST, 1);
    275   curl_headers_ =
    276       libcurl_->curl_slist_append(curl_headers_, "Content-Length: 0");
    277   libcurl_->curl_easy_setopt(curl_, CURLOPT_READDATA,
    278                              reinterpret_cast<void*>(this));
    279   libcurl_->curl_easy_setopt(curl_, CURLOPT_READFUNCTION,
    280                              &CurlHttpRequest::ReadCallback);
    281 }
    282 
    283 void CurlHttpRequest::SetResultBuffer(std::vector<char>* out_buffer) {
    284   CheckNotSent();
    285   CHECK(out_buffer != nullptr);
    286 
    287   out_buffer->clear();
    288   response_buffer_ = out_buffer;
    289 
    290   libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA,
    291                              reinterpret_cast<void*>(this));
    292   libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION,
    293                              &CurlHttpRequest::WriteCallback);
    294 }
    295 
    296 void CurlHttpRequest::SetResultBufferDirect(char* buffer, size_t size) {
    297   CHECK(buffer != nullptr);
    298   CheckNotSent();
    299 
    300   direct_response_ = DirectResponseState{buffer, size, 0};
    301 
    302   libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEDATA,
    303                              reinterpret_cast<void*>(this));
    304   libcurl_->curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION,
    305                              &CurlHttpRequest::WriteCallbackDirect);
    306 }
    307 
    308 bool CurlHttpRequest::IsDirectResponse() const {
    309   return direct_response_.buffer_ != nullptr;
    310 }
    311 
    312 size_t CurlHttpRequest::WriteCallbackDirect(const void* ptr, size_t size,
    313                                             size_t nmemb, void* userdata) {
    314   CHECK(ptr != nullptr);
    315   auto that = reinterpret_cast<CurlHttpRequest*>(userdata);
    316   DirectResponseState* state = &that->direct_response_;
    317   CHECK(state->buffer_ != nullptr);
    318   CHECK(state->bytes_transferred_ <= state->buffer_size_);
    319 
    320   size_t curl_bytes_received = size * nmemb;
    321   size_t user_buffer_bytes_available =
    322       state->buffer_size_ - state->bytes_transferred_;
    323 
    324   // The HTTP server may send a response body that is longer than what we
    325   // expected. We must not use CHECK() for this situation, because that would
    326   // imply a code bug (in this client code) where none exists; the violation of
    327   // expectations would have been caused by the server, not the client. So we
    328   // report a log warning, if an HTTP server is misbehaving.
    329   if (curl_bytes_received > user_buffer_bytes_available) {
    330     LOG(WARNING) << "The HTTP response body that we received is longer than we "
    331                     "requested or expected. "
    332                  << "Total bytes requested: " << state->buffer_size_
    333                  << " Bytes received (so far) in HTTP response body: "
    334                  << (state->bytes_transferred_ + curl_bytes_received);
    335   }
    336 
    337   size_t bytes_to_copy =
    338       std::min<size_t>(curl_bytes_received, user_buffer_bytes_available);
    339   memcpy(&state->buffer_[state->bytes_transferred_], ptr, bytes_to_copy);
    340   state->bytes_transferred_ += bytes_to_copy;
    341   return bytes_to_copy;
    342 }
    343 
    344 size_t CurlHttpRequest::GetResultBufferDirectBytesTransferred() {
    345   CHECK(direct_response_.buffer_ != nullptr);
    346   return direct_response_.bytes_transferred_;
    347 }
    348 
    349 void CurlHttpRequest::SetTimeouts(uint32 connection, uint32 inactivity,
    350                                   uint32 total) {
    351   CheckNotSent();
    352   connect_timeout_secs_ = connection;
    353   inactivity_timeout_secs_ = inactivity;
    354   request_timeout_secs_ = total;
    355 }
    356 
    357 size_t CurlHttpRequest::WriteCallback(const void* ptr, size_t size,
    358                                       size_t nmemb, void* this_object) {
    359   CHECK(ptr);
    360   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
    361   CHECK(that->response_buffer_);
    362   const size_t bytes_to_copy = size * nmemb;
    363   that->response_buffer_->insert(
    364       that->response_buffer_->end(), reinterpret_cast<const char*>(ptr),
    365       reinterpret_cast<const char*>(ptr) + bytes_to_copy);
    366 
    367   return bytes_to_copy;
    368 }
    369 
    370 size_t CurlHttpRequest::ReadCallback(void* ptr, size_t size, size_t nmemb,
    371                                      FILE* this_object) {
    372   CHECK(ptr);
    373   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
    374   CHECK(that->post_body_read_ <= that->post_body_buffer_.size());
    375   const size_t bytes_to_copy = std::min(
    376       size * nmemb, that->post_body_buffer_.size() - that->post_body_read_);
    377   memcpy(ptr, that->post_body_buffer_.data() + that->post_body_read_,
    378          bytes_to_copy);
    379   that->post_body_read_ += bytes_to_copy;
    380   return bytes_to_copy;
    381 }
    382 
    383 size_t CurlHttpRequest::HeaderCallback(const void* ptr, size_t size,
    384                                        size_t nmemb, void* this_object) {
    385   CHECK(ptr);
    386   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
    387   StringPiece header(reinterpret_cast<const char*>(ptr), size * nmemb);
    388   StringPiece name, value;
    389   // The supplied header has the form "<name>: <value>", parse it.
    390   if (strings::Scanner(header)
    391           .ScanEscapedUntil(':')
    392           .StopCapture()
    393           .OneLiteral(": ")
    394           .GetResult(&value, &name)) {
    395     string str_value = value.ToString();
    396     str_util::StripTrailingWhitespace(&str_value);
    397     that->response_headers_[name.ToString()] = str_value;
    398   }
    399   return size * nmemb;
    400 }
    401 
    402 Status CurlHttpRequest::Send() {
    403   CheckNotSent();
    404   CHECK(is_uri_set_) << "URI has not been set.";
    405 
    406   is_sent_ = true;
    407 
    408   if (curl_headers_) {
    409     libcurl_->curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers_);
    410   }
    411   if (resolve_list_) {
    412     libcurl_->curl_easy_setopt(curl_, CURLOPT_RESOLVE, resolve_list_);
    413   }
    414   libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERDATA,
    415                              reinterpret_cast<void*>(this));
    416   libcurl_->curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION,
    417                              &CurlHttpRequest::HeaderCallback);
    418 
    419   libcurl_->curl_easy_setopt(curl_, CURLOPT_TIMEOUT, request_timeout_secs_);
    420   libcurl_->curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT,
    421                              connect_timeout_secs_);
    422 
    423   char error_buffer[CURL_ERROR_SIZE] = {0};
    424   libcurl_->curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buffer);
    425 
    426   const auto curl_result = libcurl_->curl_easy_perform(curl_);
    427 
    428   double written_size = 0;
    429   libcurl_->curl_easy_getinfo(curl_, CURLINFO_SIZE_DOWNLOAD, &written_size);
    430 
    431   libcurl_->curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code_);
    432 
    433   const auto& error_message = strings::StrCat(
    434       "Error executing an HTTP request (HTTP response code ", response_code_,
    435       ", error code ", curl_result, ", error message '", error_buffer, "')");
    436 
    437   Status result;
    438   StringPiece response = GetResponse();
    439   string extended_error_message;
    440   switch (response_code_) {
    441     // The group of response codes indicating that the request achieved
    442     // the expected goal.
    443     case 200:  // OK
    444     case 201:  // Created
    445     case 204:  // No Content
    446     case 206:  // Partial Content
    447       if (curl_result != CURLE_OK) {
    448         // This means the server executed the request successfully, but then
    449         // something went wrong during the transmission of the response.
    450         result = errors::Unavailable(error_message);
    451       } else {
    452         result = Status::OK();
    453       }
    454       break;
    455     case 416:  // Requested Range Not Satisfiable
    456       // The requested range had no overlap with the available range.
    457       // This doesn't indicate an error, but this does mean an empty response
    458       // body.
    459       response_buffer_->clear();
    460       result = Status::OK();
    461       break;
    462 
    463     // INVALID_ARGUMENT indicates a problem with how the request is constructed.
    464     case 400:  // Bad Request
    465     case 411:  // Length Required
    466       result = errors::InvalidArgument(error_message);
    467       break;
    468 
    469     // PERMISSION_DENIED indicates an authentication or an authorization issue.
    470     case 401:  // Unauthorized
    471     case 403:  // Forbidden
    472       if (!response.empty()) {
    473         extended_error_message = strings::StrCat(
    474             error_message, ", response ",
    475             response.substr(
    476                 0, std::min(response.size(), response_to_error_limit_)));
    477         result = errors::PermissionDenied(extended_error_message);
    478       } else {
    479         result = errors::PermissionDenied(error_message);
    480       }
    481       break;
    482 
    483     // NOT_FOUND indicates that the requested resource does not exist.
    484     case 404:  // Not found
    485     case 410:  // Gone
    486       result = errors::NotFound(error_message);
    487       break;
    488 
    489     // FAILED_PRECONDITION indicates that the request failed because some
    490     // of the underlying assumptions were not satisfied. The request
    491     // shouldn't be retried unless the external context has changed.
    492     case 302:  // Found
    493     case 303:  // See Other
    494     case 304:  // Not Modified
    495     case 307:  // Temporary Redirect
    496     case 308:  // Resume Incomplete
    497     case 412:  // Precondition Failed
    498     case 413:  // Payload Too Large
    499       result = errors::FailedPrecondition(error_message);
    500       break;
    501 
    502     // UNAVAILABLE indicates a problem that can go away if the request
    503     // is just retried without any modification.
    504     case 409:  // Conflict
    505     case 429:  // Too Many Requests
    506     case 500:  // Internal Server Error
    507     case 502:  // Bad Gateway
    508     case 503:  // Service Unavailable
    509     default:   // All other HTTP response codes also should be retried.
    510       result = errors::Unavailable(error_message);
    511       break;
    512   }
    513   if (!result.ok()) {
    514     response_buffer_->clear();
    515   }
    516   return result;
    517 }
    518 
    519 void CurlHttpRequest::CheckMethodNotSet() const {
    520   CHECK(!is_method_set_) << "HTTP method has been already set.";
    521 }
    522 
    523 void CurlHttpRequest::CheckNotSent() const {
    524   CHECK(!is_sent_) << "The request has already been sent.";
    525 }
    526 
    527 StringPiece CurlHttpRequest::GetResponse() const {
    528   StringPiece response;
    529   if (IsDirectResponse()) {
    530     response = StringPiece(direct_response_.buffer_,
    531                            direct_response_.bytes_transferred_);
    532   } else {
    533     response = StringPiece(response_buffer_->data(), response_buffer_->size());
    534   }
    535   return response;
    536 }
    537 
    538 string CurlHttpRequest::GetResponseHeader(const string& name) const {
    539   const auto& header = response_headers_.find(name);
    540   return header != response_headers_.end() ? header->second : "";
    541 }
    542 
    543 uint64 CurlHttpRequest::GetResponseCode() const { return response_code_; }
    544 
    545 // Cancels the transmission if no progress has been made for too long.
    546 int CurlHttpRequest::ProgressCallback(void* this_object, curl_off_t dltotal,
    547                                       curl_off_t dlnow, curl_off_t ultotal,
    548                                       curl_off_t ulnow) {
    549   auto that = reinterpret_cast<CurlHttpRequest*>(this_object);
    550   const auto now = that->env_->NowSeconds();
    551   const auto current_progress = dlnow + ulnow;
    552   if (that->last_progress_timestamp_ == 0 ||
    553       current_progress > that->last_progress_bytes_) {
    554     // This is the first time the callback is called or some progress
    555     // was made since the last tick.
    556     that->last_progress_timestamp_ = now;
    557     that->last_progress_bytes_ = current_progress;
    558     return 0;
    559   }
    560 
    561   if (now - that->last_progress_timestamp_ > that->inactivity_timeout_secs_) {
    562     double lookup_time = -1;
    563     const auto lookup_time_status = that->libcurl_->curl_easy_getinfo(
    564         that->curl_, CURLINFO_NAMELOOKUP_TIME, &lookup_time);
    565 
    566     double connect_time = -1;
    567     const auto connect_time_status = that->libcurl_->curl_easy_getinfo(
    568         that->curl_, CURLINFO_CONNECT_TIME, &connect_time);
    569 
    570     double pretransfer_time = -1;
    571     const auto pretransfer_time_status = that->libcurl_->curl_easy_getinfo(
    572         that->curl_, CURLINFO_PRETRANSFER_TIME, &pretransfer_time);
    573 
    574     double starttransfer_time = -1;
    575     const auto starttransfer_time_status = that->libcurl_->curl_easy_getinfo(
    576         that->curl_, CURLINFO_PRETRANSFER_TIME, &starttransfer_time);
    577 
    578     LOG(ERROR) << "The transmission  of request " << this_object
    579                << " (URI: " << that->uri_ << ") has been stuck at "
    580                << current_progress << " of " << dltotal + ultotal
    581                << " bytes for " << now - that->last_progress_timestamp_
    582                << " seconds and will be aborted. CURL timing information: "
    583                << "lookup time: " << lookup_time << " ("
    584                << that->libcurl_->curl_easy_strerror(lookup_time_status)
    585                << "), connect time: " << connect_time << " ("
    586                << that->libcurl_->curl_easy_strerror(connect_time_status)
    587                << "), pre-transfer time: " << pretransfer_time << " ("
    588                << that->libcurl_->curl_easy_strerror(pretransfer_time_status)
    589                << "), start-transfer time: " << starttransfer_time << " ("
    590                << that->libcurl_->curl_easy_strerror(starttransfer_time_status)
    591                << ")";
    592     return 1;  // Will abort the request.
    593   }
    594 
    595   // No progress was made since the last call, but we should wait a bit longer.
    596   return 0;
    597 }
    598 
    599 }  // namespace tensorflow
    600