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