1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/gcd_rest_client.h"
8 #include "base/json/json_writer.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "base/time/default_clock.h"
12 #include "base/values.h"
13 #include "net/url_request/url_fetcher.h"
14 #include "remoting/base/logging.h"
18 // Only 'patchState' requests are supported at the moment, but other
19 // types of requests may be added in the future.
20 struct GcdRestClient::PatchStateRequest
{
21 GcdRestClient::PatchStateCallback callback
;
22 scoped_ptr
<net::URLFetcher
> url_fetcher
;
25 GcdRestClient::GcdRestClient(const std::string
& gcd_base_url
,
26 const std::string
& gcd_device_id
,
27 const scoped_refptr
<net::URLRequestContextGetter
>&
28 url_request_context_getter
,
29 OAuthTokenGetter
* token_getter
)
30 : gcd_base_url_(gcd_base_url
),
31 gcd_device_id_(gcd_device_id
),
32 url_request_context_getter_(url_request_context_getter
),
33 token_getter_(token_getter
),
34 clock_(new base::DefaultClock
),
38 GcdRestClient::~GcdRestClient() {
39 while (!pending_requests_
.empty()) {
40 delete pending_requests_
.front();
41 pending_requests_
.pop();
45 void GcdRestClient::PatchState(
46 scoped_ptr
<base::DictionaryValue
> patch_details
,
47 const GcdRestClient::PatchStateCallback
& callback
) {
48 // Construct a status update message in the format GCD expects. The
49 // message looks like this, where "..." is filled in from
60 // Note that |now| is deliberately using a double to hold an integer
61 // value because |DictionaryValue| doesn't support int64 values, and
62 // GCD doesn't accept fractional values.
63 double now
= clock_
->Now().ToJavaTime();
64 scoped_ptr
<base::DictionaryValue
> patch_dict(new base::DictionaryValue
);
65 patch_dict
->SetDouble("requestTimeMs", now
);
66 scoped_ptr
<base::ListValue
> patch_list(new base::ListValue
);
67 base::DictionaryValue
* patch_item
= new base::DictionaryValue
;
68 patch_list
->Append(patch_item
);
69 patch_item
->Set("patch", patch_details
.Pass());
70 patch_item
->SetDouble("timeMs", now
);
71 patch_dict
->Set("patches", patch_list
.Pass());
73 // Stringify the message.
74 std::string patch_string
;
75 if (!base::JSONWriter::Write(*patch_dict
, &patch_string
)) {
76 LOG(ERROR
) << "Error building GCD device state patch.";
77 callback
.Run(OTHER_ERROR
);
80 DLOG(INFO
) << "sending state patch: " << patch_string
;
83 gcd_base_url_
+ "/devices/" + gcd_device_id_
+ "/patchState";
85 // Enqueue an HTTP request to issue once an auth token is available.
86 scoped_ptr
<PatchStateRequest
> request(new PatchStateRequest
);
87 request
->callback
= callback
;
88 request
->url_fetcher
=
89 net::URLFetcher::Create(GURL(url
), net::URLFetcher::POST
, this);
90 request
->url_fetcher
->SetUploadData("application/json", patch_string
);
91 if (url_request_context_getter_
) {
92 request
->url_fetcher
->SetRequestContext(url_request_context_getter_
.get());
95 if (current_request_
) {
96 // New request will start when the current request finishes.
97 DCHECK(pending_requests_
.empty());
98 pending_requests_
.push(request
.release());
100 current_request_
= request
.Pass();
105 void GcdRestClient::StartNextRequest() {
106 DCHECK(current_request_
);
107 token_getter_
->CallWithToken(
108 base::Bind(&GcdRestClient::OnTokenReceived
, base::Unretained(this)));
111 void GcdRestClient::OnTokenReceived(OAuthTokenGetter::Status status
,
112 const std::string
& user_email
,
113 const std::string
& access_token
) {
114 DCHECK(current_request_
);
115 if (status
!= OAuthTokenGetter::SUCCESS
) {
116 LOG(ERROR
) << "Error getting OAuth token for GCD request: "
117 << current_request_
->url_fetcher
->GetOriginalURL();
118 if (status
== OAuthTokenGetter::NETWORK_ERROR
) {
119 FinishCurrentRequest(NETWORK_ERROR
);
121 FinishCurrentRequest(OTHER_ERROR
);
126 current_request_
->url_fetcher
->SetExtraRequestHeaders(
127 "Authorization: Bearer " + access_token
);
128 current_request_
->url_fetcher
->Start();
131 void GcdRestClient::FinishCurrentRequest(Status result
) {
132 DCHECK(current_request_
);
133 scoped_ptr
<PatchStateRequest
> request
= current_request_
.Pass();
134 if (!pending_requests_
.empty()) {
135 current_request_
.reset(pending_requests_
.front());
136 pending_requests_
.pop();
138 // This object may be destroyed by the call to request->callback
139 // below, so instead of calling StartNextRequest() at the end of
140 // this method, schedule a call using a weak pointer.
141 base::ThreadTaskRunnerHandle::Get()->PostTask(
142 FROM_HERE
, base::Bind(&GcdRestClient::StartNextRequest
,
143 weak_factory_
.GetWeakPtr()));
146 request
->callback
.Run(result
);
149 void GcdRestClient::OnURLFetchComplete(const net::URLFetcher
* source
) {
150 DCHECK(current_request_
);
152 const GURL
& request_url
= current_request_
->url_fetcher
->GetOriginalURL();
153 Status status
= OTHER_ERROR
;
154 int response
= source
->GetResponseCode();
155 if (response
>= 200 && response
< 300) {
156 DLOG(INFO
) << "GCD request succeeded:" << request_url
;
158 } else if (response
== 404) {
159 LOG(WARNING
) << "Host not found (" << response
160 << ") fetching URL: " << request_url
;
161 status
= NO_SUCH_HOST
;
162 } else if (response
== 0) {
163 LOG(ERROR
) << "Network error (" << response
164 << ") fetching URL: " << request_url
;
165 status
= NETWORK_ERROR
;
167 LOG(ERROR
) << "Error (" << response
<< ") fetching URL: " << request_url
;
170 FinishCurrentRequest(status
);
173 } // namespace remoting