buffet: Support retries for 5xx errors.
5xx should be generally retried a couple of times
as they usaully mean some issues on server-side.
BUG=chromium:420580
TEST=cros_workon_make buffet --test && manual testing.
Change-Id: Iada5961a77acd203ce55ba5ec914e382f2681df0
Reviewed-on: https://chromium-review.googlesource.com/223310
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Tested-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Anton Muhin <antonm@chromium.org>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index c67dce0..bb7c29d 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -465,27 +465,102 @@
return device_id_;
}
+namespace {
+
+template <class T>
+void PostToCallback(base::Callback<void(const T&)> callback,
+ std::unique_ptr<T> value) {
+ auto cb = [callback] (T* result) {
+ callback.Run(*result);
+ };
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(cb, base::Owned(value.release())));
+}
+
+// TODO(antonm): May belong to chromeos/http.
+
+void SendRequestAsync(
+ const std::string& method,
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const chromeos::http::HeaderList& headers,
+ std::shared_ptr<chromeos::http::Transport> transport,
+ int num_retries,
+ base::Callback<void(const chromeos::http::Response&)> callback,
+ base::Callback<void(const chromeos::Error&)> errorback) {
+ chromeos::ErrorPtr error;
+
+ auto on_retriable_failure = [&error, method, url, data, mime_type,
+ headers, transport, num_retries, callback, errorback] () {
+ if (num_retries > 0) {
+ auto c = [method, url, data, mime_type, headers, transport,
+ num_retries, callback, errorback] () {
+ SendRequestAsync(method, url,
+ data, mime_type,
+ headers,
+ transport,
+ num_retries - 1,
+ callback, errorback);
+ };
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(c));
+ } else {
+ PostToCallback(errorback, std::move(error));
+ }
+ };
+
+ chromeos::http::Request request(url, method.c_str(), transport);
+ request.AddHeaders(headers);
+ if (!data.empty()) {
+ request.SetContentType(mime_type.c_str());
+ if (!request.AddRequestBody(data.c_str(), data.size(), &error)) {
+ on_retriable_failure();
+ return;
+ }
+ }
+
+ std::unique_ptr<chromeos::http::Response> response{
+ request.GetResponse(&error)};
+ if (!response) {
+ on_retriable_failure();
+ return;
+ }
+
+ int status_code{response->GetStatusCode()};
+ if (status_code >= chromeos::http::status_code::Continue &&
+ status_code < chromeos::http::status_code::BadRequest) {
+ PostToCallback(callback, std::move(response));
+ return;
+ }
+
+ // TODO(antonm): Should add some useful information to error.
+ LOG(WARNING) << "Request failed. Response code = " << status_code;
+
+ if (status_code >= 500 && status_code < 600) {
+ // Request was valid, but server failed, retry.
+ // TODO(antonm): Implement exponential backoff.
+ // TODO(antonm): Reconsider status codes, maybe only some require
+ // retry.
+ // TODO(antonm): Support Retry-After header.
+ on_retriable_failure();
+ } else {
+ PostToCallback(errorback, std::move(error));
+ }
+}
+
+} // namespace
+
void DeviceRegistrationInfo::DoCloudRequest(
const char* method,
const std::string& url,
const base::DictionaryValue* body,
CloudRequestCallback callback,
CloudRequestErroback errorback) {
- // TODO(antonm): Add retries for 5xx.
// TODO(antonm): Add reauthorisation on access token expiration (do not
// forget about 5xx when fetching new access token).
// TODO(antonm): Add support for device removal.
- chromeos::ErrorPtr error;
-
- auto report_error = [errorback, &error] () {
- auto cb = [errorback] (chromeos::Error* error_ptr) {
- errorback.Run(*error_ptr);
- };
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(cb, base::Owned(error.release())));
- };
-
std::string data;
if (body)
base::JSONWriter::Write(body, &data);
@@ -495,40 +570,26 @@
chromeos::mime::parameters::kCharset,
"utf-8")};
- const std::unique_ptr<const chromeos::http::Response> response{
- chromeos::http::SendRequest(
- method,
- url,
- data.c_str(), data.size(),
- mime_type.c_str(),
- {GetAuthorizationHeader()},
- transport_,
- &error)};
- if (!response) {
- report_error();
- return;
- }
+ auto request_cb = [callback, errorback] (
+ const chromeos::http::Response& response) {
+ chromeos::ErrorPtr error;
- int status_code{0};
- std::unique_ptr<base::DictionaryValue> json_resp{
- chromeos::http::ParseJsonResponse(response.get(), &status_code, &error)};
- if (!json_resp) {
- report_error();
- return;
- }
+ std::unique_ptr<base::DictionaryValue> json_resp{
+ chromeos::http::ParseJsonResponse(&response, nullptr, &error)};
+ if (!json_resp) {
+ PostToCallback(errorback, std::move(error));
+ return;
+ }
- if (status_code >= chromeos::http::status_code::BadRequest) {
- LOG(WARNING) << "Cloud request failed. Response code = " << status_code;
- ParseGCDError(json_resp.get(), &error);
- report_error();
- return;
- }
-
- auto cb = [callback] (base::DictionaryValue* result) {
- callback.Run(*result);
+ PostToCallback(callback, std::move(json_resp));
};
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(cb, base::Owned(json_resp.release())));
+
+ SendRequestAsync(method, url,
+ data, mime_type,
+ {GetAuthorizationHeader()},
+ transport_,
+ 7,
+ base::Bind(request_cb), errorback);
}
void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {