Index: blimp/client/session/assignment_source.cc |
diff --git a/blimp/client/session/assignment_source.cc b/blimp/client/session/assignment_source.cc |
index 271569cb04b2916017a905b2910cfb6dbb55ec35..b6136e8615d7453cfe01facb1bf534efeeaac480 100644 |
--- a/blimp/client/session/assignment_source.cc |
+++ b/blimp/client/session/assignment_source.cc |
@@ -6,22 +6,52 @@ |
#include "base/bind.h" |
#include "base/command_line.h" |
+#include "base/json/json_reader.h" |
+#include "base/json/json_writer.h" |
#include "base/location.h" |
#include "base/numerics/safe_conversions.h" |
#include "base/strings/string_number_conversions.h" |
+#include "base/values.h" |
#include "blimp/client/app/blimp_client_switches.h" |
+#include "blimp/client/session/blimp_client_url_request_context_getter.h" |
+#include "blimp/common/protocol_version.h" |
#include "net/base/ip_address.h" |
#include "net/base/ip_endpoint.h" |
+#include "net/base/load_flags.h" |
+#include "net/base/url_util.h" |
+#include "net/http/http_status_code.h" |
+#include "net/url_request/url_fetcher.h" |
namespace blimp { |
namespace { |
// TODO(kmarshall): Take values from configuration data. |
const char kDummyClientToken[] = "MyVoiceIsMyPassport"; |
-const std::string kDefaultBlimpletIPAddress = "127.0.0.1"; |
+const char kDefaultBlimpletIPAddress[] = "127.0.0.1"; |
const uint16_t kDefaultBlimpletTCPPort = 25467; |
-net::IPAddress GetBlimpletIPAddress() { |
+// Potential assigner URLs. |
+const char kProdBlimpAssignerURL[] = |
Kevin M
2016/02/12 19:45:26
kDefaultAssignerURL?
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ "https://blimp-pa.googleapis.com/v1/assignment"; |
+ |
+// Assignment request JSON keys. |
+const char kProtocolVersionKey[] = "protocol_version"; |
+ |
+// Assignment response JSON keys. |
+const char kClientTokenKey[] = "clientToken"; |
+const char kHostKey[] = "host"; |
+const char kPortKey[] = "port"; |
+const char kCertificateFingerprintKey[] = "certificateFingerprint"; |
+const char kCertificateKey[] = "certificate"; |
+ |
+bool HasCustomBlimpletEndpoint() { |
+ return base::CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kBlimpletHost) && |
+ base::CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kBlimpletTCPPort); |
+} |
+ |
+net::IPAddress GetCustomBlimpletIPAddress() { |
std::string host; |
if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
switches::kBlimpletHost)) { |
@@ -36,7 +66,7 @@ net::IPAddress GetBlimpletIPAddress() { |
return ip_address; |
} |
-uint16_t GetBlimpletTCPPort() { |
+uint16_t GetCustomBlimpletTCPPort() { |
if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
switches::kBlimpletTCPPort)) { |
std::string port_str = |
@@ -53,23 +83,166 @@ uint16_t GetBlimpletTCPPort() { |
} |
} |
+GURL GetBlimpAssignerURL() { |
+ // TODO(dtrainor): Add a way to specify another assigner. |
+ return GURL(kProdBlimpAssignerURL); |
+} |
+ |
} // namespace |
namespace client { |
AssignmentSource::AssignmentSource( |
- const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner) |
- : main_task_runner_(main_task_runner) {} |
+ const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner, |
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) |
+ : main_task_runner_(main_task_runner), |
+ url_request_context_( |
+ new BlimpClientURLRequestContextGetter(io_task_runner)) {} |
AssignmentSource::~AssignmentSource() {} |
-void AssignmentSource::GetAssignment(const AssignmentCallback& callback) { |
+void AssignmentSource::GetAssignment(const std::string& token, |
+ const AssignmentCallback& callback) { |
DCHECK(main_task_runner_->BelongsToCurrentThread()); |
- Assignment assignment; |
- assignment.ip_endpoint = |
- net::IPEndPoint(GetBlimpletIPAddress(), GetBlimpletTCPPort()); |
- assignment.client_token = kDummyClientToken; |
- main_task_runner_->PostTask(FROM_HERE, base::Bind(callback, assignment)); |
+ |
+ // Cancel any outstanding callback. |
+ if (callback_) |
Kevin M
2016/02/12 19:45:26
Use !callback_.is_null() and base::ResetAndReturn
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_SERVER_INTERRUPTED); |
+ callback_.reset(new AssignmentCallback(callback)); |
Kevin M
2016/02/12 19:45:26
You can just use =
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ |
+ // Attempt to build a custom assignment from command line arguments. |
+ if (HasCustomBlimpletEndpoint()) { |
Kevin M
2016/02/12 19:45:26
Can you turn HasCustom...() into GetCustom...() an
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ Assignment assignment; |
+ assignment.ip_endpoint = net::IPEndPoint(GetCustomBlimpletIPAddress(), |
+ GetCustomBlimpletTCPPort()); |
+ assignment.client_token = kDummyClientToken; |
+ |
+ // Post the result so that the behavior of this function is consistent. |
+ main_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(*callback_, ASSIGNMENT_SOURCE_RESULT_OKAY, assignment)); |
+ callback_.reset(); |
+ return; |
+ } |
+ |
+ // Call out to the network for a real assignment. Build the network request |
+ // to hit the assigner. |
+ url_fetcher_ = net::URLFetcher::Create(GetBlimpAssignerURL(), |
+ net::URLFetcher::POST, this); |
+ url_fetcher_->SetRequestContext(url_request_context_.get()); |
+ url_fetcher_->SetAutomaticallyRetryOn5xx(false); |
+ url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(0); |
+ url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | |
+ net::LOAD_DO_NOT_SAVE_COOKIES | |
+ net::LOAD_DO_NOT_SEND_COOKIES); |
+ url_fetcher_->AddExtraRequestHeader("Authorization: Bearer " + token); |
+ |
+ // Write the JSON for the request data. |
+ base::DictionaryValue dictionary; |
+ dictionary.SetString(kProtocolVersionKey, blimp::kEngineVersion); |
+ std::string json; |
+ base::JSONWriter::Write(dictionary, &json); |
+ url_fetcher_->SetUploadData("application/json", json); |
+ |
+ url_fetcher_->Start(); |
+} |
+ |
+void AssignmentSource::OnURLFetchComplete(const net::URLFetcher* source) { |
+ DCHECK(callback_.get()); |
Kevin M
2016/02/12 19:45:26
Could there be two concurrent URL fetches underway
David Trainor- moved to gerrit
2016/02/17 21:09:47
Luckily the URLFetcher won't notify it's delegate
|
+ |
+ if (!source->GetStatus().is_success()) { |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_NETWORK_FAILURE); |
+ return; |
+ } |
+ |
+ switch (source->GetResponseCode()) { |
+ case net::HTTP_OK: { |
+ // Grab the response from the assigner request. |
+ std::string response; |
+ if (!source->GetResponseAsString(&response)) { |
Kevin M
2016/02/12 19:45:26
Since the outcome is the same for all these cases,
David Trainor- moved to gerrit
2016/02/17 21:09:47
I like idea I'll try it out. If we have unit test
|
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ // Attempt to interpret the response as JSON and treat it as a dictionary. |
+ scoped_ptr<base::Value> json = base::JSONReader::Read(response); |
+ if (!json) { |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ const base::DictionaryValue* dict; |
+ if (!json->GetAsDictionary(&dict)) { |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ // Validate that all the expected fields are present. |
+ std::string client_token; |
+ std::string host; |
+ int port; |
+ std::string cert_fingerprint; |
+ std::string cert; |
+ if (!dict->GetString(kClientTokenKey, &client_token) || |
Kevin M
2016/02/12 19:45:26
!(x && y && z...)?
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ !dict->GetString(kHostKey, &host) || |
+ !dict->GetInteger(kPortKey, &port) || |
+ !dict->GetString(kCertificateFingerprintKey, &cert_fingerprint) || |
+ !dict->GetString(kCertificateKey, &cert)) { |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ net::IPAddress ip_address; |
+ if (!net::IPAddress::FromIPLiteral(host, &ip_address)) { |
Kevin M
2016/02/12 19:45:26
Rebase; this method was just renamed!
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ if (!base::IsValueInRangeForNumericType<uint16_t>(port)) { |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ return; |
+ } |
+ |
+ Assignment assignment; |
+ assignment.ip_endpoint = net::IPEndPoint(ip_address, port); |
+ assignment.client_token = client_token; |
+ assignment.certificate = cert; |
+ assignment.certificate_fingerprint = cert_fingerprint; |
+ |
+ RespondWithAssignment(assignment); |
+ } break; |
Kevin M
2016/02/12 19:45:26
This is a huge switch case. I think it muddles the
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ case net::HTTP_BAD_REQUEST: |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_REQUEST); |
+ break; |
+ case net::HTTP_UNAUTHORIZED: |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_EXPIRED_ACCESS_TOKEN); |
+ break; |
+ case net::HTTP_FORBIDDEN: |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_USER_INVALID); |
+ break; |
+ case 429: // Too Many Requests |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_OUT_OF_VMS); |
+ break; |
+ case net::HTTP_INTERNAL_SERVER_ERROR: |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_SERVER_ERROR); |
+ break; |
+ default: |
+ RespondWithError(ASSIGNMENT_SOURCE_RESULT_BAD_RESPONSE); |
+ break; |
+ } |
+} |
+ |
+void AssignmentSource::RespondWithAssignment(const Assignment& assignment) { |
Kevin M
2016/02/12 19:45:27
Only one call site; is it necessary to put this in
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ DCHECK(callback_.get()); |
+ callback_->Run(ASSIGNMENT_SOURCE_RESULT_OKAY, assignment); |
Kevin M
2016/02/12 19:45:26
ASSIGNMENT_SOURCE_OK for consistency with the spel
Kevin M
2016/02/12 19:45:26
Use base::ResetAndReturn().Run() instead of Run an
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ callback_.reset(); |
+} |
+ |
+void AssignmentSource::RespondWithError(AssignmentSourceResult result) { |
Kevin M
2016/02/12 19:45:26
I think the logic here is trivial enough to warran
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ DCHECK(result != ASSIGNMENT_SOURCE_RESULT_OKAY); |
Kevin M
2016/02/12 19:45:26
DCHECK_NE
David Trainor- moved to gerrit
2016/02/17 21:09:47
Acknowledged.
|
+ DCHECK(callback_.get()); |
+ callback_->Run(result, Assignment()); |
Kevin M
2016/02/12 19:45:26
Ditto for ResetAndReturn
David Trainor- moved to gerrit
2016/02/17 21:09:47
Done.
|
+ callback_.reset(); |
} |
} // namespace client |