libchromeos: Add async http I/O API, phase I.

Add asynchronous HTTP API to libchromeos and updated buffet to use
the appropriate async function for its DoCloudRequest method.

The actual implementation of asynchronous I/O for CURL transport
is deferred to a following CL to limit the amount of modifications
in one change.

BUG=chromium:427963
TEST=FEATURES=test emerge-link libchromeos peerd privetd buffet

Change-Id: If9ba6b4a27d0a99c29feb1cc6d594532f333e0b0
Reviewed-on: https://chromium-review.googlesource.com/238930
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index ed9876a..994f7ad 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -132,7 +132,7 @@
   return chromeos::url::AppendQueryParams(result, params);
 }
 
-void IgnoreCloudError(const chromeos::Error&) {
+void IgnoreCloudError(const chromeos::Error*) {
 }
 
 void IgnoreCloudResult(const base::DictionaryValue&) {
@@ -501,9 +501,9 @@
       from_here, base::Bind(&PostRepeatingTask, from_here, task, delay), delay);
 }
 
-// TODO(antonm): May belong to chromeos/http.
+using ResponsePtr = scoped_ptr<chromeos::http::Response>;
 
-void SendRequestAsync(
+void SendRequestWithRetries(
     const std::string& method,
     const std::string& url,
     const std::string& data,
@@ -511,68 +511,53 @@
     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] () {
+    const chromeos::http::SuccessCallback& success_callback,
+    const chromeos::http::ErrorCallback& error_callback) {
+  auto on_failure =
+      [method, url, data, mime_type, headers, transport, num_retries,
+      success_callback, error_callback](const chromeos::Error* error) {
     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));
+      SendRequestWithRetries(method, url, data, mime_type,
+                             headers, transport, num_retries - 1,
+                             success_callback, error_callback);
     } else {
-      PostToCallback(errorback, std::move(error));
+      error_callback.Run(error);
     }
   };
 
-  chromeos::http::Request request(url, method, transport);
-  request.AddHeaders(headers);
-  if (!data.empty()) {
-    request.SetContentType(mime_type);
-    if (!request.AddRequestBody(data.c_str(), data.size(), &error)) {
-      on_retriable_failure();
+  auto on_success =
+      [on_failure, success_callback, error_callback](ResponsePtr response) {
+    int status_code = response->GetStatusCode();
+    if (status_code >= chromeos::http::status_code::Continue &&
+        status_code < chromeos::http::status_code::BadRequest) {
+      success_callback.Run(response.Pass());
       return;
     }
-  }
 
-  std::unique_ptr<chromeos::http::Response> response{
-    request.GetResponseAndBlock(&error)};
-  if (!response) {
-    on_retriable_failure();
-    return;
-  }
+    // TODO(antonm): Should add some useful information to error.
+    LOG(WARNING) << "Request failed. Response code = " << status_code;
 
-  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 {
+    chromeos::ErrorPtr error;
     chromeos::Error::AddTo(&error, FROM_HERE, chromeos::errors::http::kDomain,
                            std::to_string(status_code),
                            response->GetStatusText());
-    PostToCallback(errorback, std::move(error));
-  }
+    if (status_code >= chromeos::http::status_code::InternalServerError &&
+        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_failure(error.get());
+    } else {
+      error_callback.Run(error.get());
+    }
+  };
+
+  chromeos::http::SendRequest(method, url, data.c_str(), data.size(),
+                              mime_type, headers, transport,
+                              base::Bind(on_success),
+                              base::Bind(on_failure));
 }
 
 }  // namespace
@@ -581,9 +566,9 @@
     const std::string& method,
     const std::string& url,
     const base::DictionaryValue* body,
-    CloudRequestCallback callback,
-    CloudRequestErroback errorback) {
-  // TODO(antonm): Add reauthorisation on access token expiration (do not
+    const CloudRequestCallback& success_callback,
+    const CloudRequestErrorCallback& error_callback) {
+  // TODO(antonm): Add reauthorization on access token expiration (do not
   // forget about 5xx when fetching new access token).
   // TODO(antonm): Add support for device removal.
 
@@ -596,49 +581,49 @@
       chromeos::mime::parameters::kCharset,
       "utf-8")};
 
-  auto request_cb = [callback, errorback] (
-      const chromeos::http::Response& response) {
+  auto request_cb = [success_callback, error_callback](ResponsePtr response) {
     chromeos::ErrorPtr error;
 
     std::unique_ptr<base::DictionaryValue> json_resp{
-        chromeos::http::ParseJsonResponse(&response, nullptr, &error)};
+        chromeos::http::ParseJsonResponse(response.get(), nullptr, &error)};
     if (!json_resp) {
-      PostToCallback(errorback, std::move(error));
+      error_callback.Run(error.get());
       return;
     }
 
-    PostToCallback(callback, std::move(json_resp));
+    success_callback.Run(*json_resp);
   };
 
   auto transport = transport_;
-  auto errorback_with_reauthorization = base::Bind(
-      [method, url, data, mime_type, transport, request_cb, errorback]
-      (DeviceRegistrationInfo* self, const chromeos::Error& error) {
-    if (error.HasError(chromeos::errors::http::kDomain,
-                       std::to_string(chromeos::http::status_code::Denied))) {
+  auto error_callackback_with_reauthorization = base::Bind(
+      [method, url, data, mime_type, transport, request_cb, error_callback]
+      (DeviceRegistrationInfo* self, const chromeos::Error* error) {
+    if (error->HasError(chromeos::errors::http::kDomain,
+                        std::to_string(chromeos::http::status_code::Denied))) {
       chromeos::ErrorPtr reauthorization_error;
       if (!self->ValidateAndRefreshAccessToken(&reauthorization_error)) {
         // TODO(antonm): Check if the device has been actually removed.
-        errorback.Run(*reauthorization_error.get());
+        error_callback.Run(reauthorization_error.get());
         return;
       }
-      SendRequestAsync(method, url,
-                       data, mime_type,
-                       {self->GetAuthorizationHeader()},
-                       transport,
-                       7,
-                       base::Bind(request_cb), errorback);
+      SendRequestWithRetries(method, url,
+                             data, mime_type,
+                             {self->GetAuthorizationHeader()},
+                             transport,
+                             7,
+                             base::Bind(request_cb), error_callback);
     } else {
-      errorback.Run(error);
+      error_callback.Run(error);
     }
   }, base::Unretained(this));
 
-  SendRequestAsync(method, url,
-                   data, mime_type,
-                   {GetAuthorizationHeader()},
-                   transport,
-                   7,
-                   base::Bind(request_cb), errorback_with_reauthorization);
+  SendRequestWithRetries(method, url,
+                         data, mime_type,
+                         {GetAuthorizationHeader()},
+                         transport,
+                         7,
+                         base::Bind(request_cb),
+                         error_callackback_with_reauthorization);
 }
 
 void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) {
@@ -753,8 +738,8 @@
                      base::Unretained(this))),
       base::TimeDelta::FromSeconds(7));
   // TODO(antonm): Use better trigger: when StateManager registers new updates,
-  // it should call closure which will post a task, probabluy with some
-  // throtlling, to publish state updates.
+  // it should call closure which will post a task, probably with some
+  // throttling, to publish state updates.
   PostRepeatingTask(
       FROM_HERE,
       base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,