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