diff options
author | hirono <hirono@chromium.org> | 2015-04-15 01:27:03 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-15 08:27:28 +0000 |
commit | 289a1e6dc3bde6c00496a3ce6d19e4258dcfd073 (patch) | |
tree | 380a355d4a8e183c1b8cd57b0a981e0263ba38de /google_apis | |
parent | 9e30e307e27f6ce8bfa373a29a19ac4fad431925 (diff) | |
download | chromium_src-289a1e6dc3bde6c00496a3ce6d19e4258dcfd073.zip chromium_src-289a1e6dc3bde6c00496a3ce6d19e4258dcfd073.tar.gz chromium_src-289a1e6dc3bde6c00496a3ce6d19e4258dcfd073.tar.bz2 |
Files.app: Implement sending batch requests.
* Wait for preparation of each child request.
* Then generate multipart request that consists each child request.
BUG=451917
TEST=DriveApiRequestsTest.BatchUploadRequest
Review URL: https://codereview.chromium.org/1084523002
Cr-Commit-Position: refs/heads/master@{#325209}
Diffstat (limited to 'google_apis')
-rw-r--r-- | google_apis/drive/drive_api_requests.cc | 167 | ||||
-rw-r--r-- | google_apis/drive/drive_api_requests.h | 50 | ||||
-rw-r--r-- | google_apis/drive/drive_api_requests_unittest.cc | 112 |
3 files changed, 315 insertions, 14 deletions
diff --git a/google_apis/drive/drive_api_requests.cc b/google_apis/drive/drive_api_requests.cc index 089f2b9..8c16a03 100644 --- a/google_apis/drive/drive_api_requests.cc +++ b/google_apis/drive/drive_api_requests.cc @@ -9,6 +9,7 @@ #include "base/json/json_writer.h" #include "base/location.h" #include "base/sequenced_task_runner.h" +#include "base/strings/stringprintf.h" #include "base/task_runner_util.h" #include "base/values.h" #include "google_apis/drive/request_sender.h" @@ -20,6 +21,21 @@ namespace google_apis { namespace drive { namespace { +// Format of one request in batch uploading request. +const char kBatchUploadRequestFormat[] = + "%s %s HTTP/1.1\n" + "Host: %s\n" + "X-Goog-Upload-Protocol: multipart\n" + "Content-Type: %s\n" + "\n" + "%s"; + +// Request header for specifying batch upload. +const char kBatchUploadHeader[] = "X-Goog-Upload-Protocol: batch"; + +// Content type of HTTP request. +const char kHttpContentType[] = "application/http"; + // Parses the JSON value to FileResource instance and runs |callback| on the // UI thread once parsing is done. // This is customized version of ParseJsonAndRun defined above to adapt the @@ -977,23 +993,134 @@ BatchUploadRequest::BatchUploadRequest( : UrlFetchRequestBase(sender), sender_(sender), url_generator_(url_generator), + committed_(false), weak_ptr_factory_(this) { } BatchUploadRequest::~BatchUploadRequest() { for (const auto& child : child_requests_) { - // Request will be deleted in RequestFinished method. + // Request will be deleted in |RequestFinished| method. sender_->RequestFinished(child.request); } } -void BatchUploadRequest::AddRequest(UrlFetchRequestBase* request) { +void BatchUploadRequest::SetBoundaryForTesting(const std::string& boundary) { + boundary_ = boundary; +} + +void BatchUploadRequest::AddRequest(BatchableRequestBase* request) { + DCHECK(CalledOnValidThread()); DCHECK(request); + DCHECK(GetChildEntry(request) == child_requests_.end()); + DCHECK(!committed_); child_requests_.push_back(BatchUploadChildEntry(request)); + request->Prepare( + base::Bind(&BatchUploadRequest::OnChildRequestPrepared, + weak_ptr_factory_.GetWeakPtr(), + request)); +} + +void BatchUploadRequest::OnChildRequestPrepared( + RequestID request_id, DriveApiErrorCode result) { + DCHECK(CalledOnValidThread()); + auto const child = GetChildEntry(request_id); + DCHECK(child != child_requests_.end()); + if (IsSuccessfulDriveApiErrorCode(result)) { + child->prepared = true; + } else { + child->request->RunCallbackOnPrematureFailure(result); + sender_->RequestFinished(child->request); + child_requests_.erase(child); + } + MayCompletePrepare(); } void BatchUploadRequest::Commit() { - NOTIMPLEMENTED(); + DCHECK(CalledOnValidThread()); + DCHECK(!committed_); + CHECK(!child_requests_.empty()); + committed_ = true; + MayCompletePrepare(); +} + +void BatchUploadRequest::Prepare(const PrepareCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK(!callback.is_null()); + prepare_callback_ = callback; + MayCompletePrepare(); +} + +void BatchUploadRequest::Cancel() { + for (auto& child : child_requests_) { + // Request cancel should delete the request instance. + child.request->Cancel(); + } + child_requests_.clear(); + UrlFetchRequestBase::Cancel(); +} + +// Obtains corresponding child entry of |request_id|. Returns NULL if the +// entry is not found. +std::vector<BatchUploadChildEntry>::iterator +BatchUploadRequest::GetChildEntry(RequestID request_id) { + for (auto it = child_requests_.begin(); it != child_requests_.end(); ++it) { + if (it->request == request_id) + return it; + } + return child_requests_.end(); +} + +void BatchUploadRequest::MayCompletePrepare() { + if (!committed_ || prepare_callback_.is_null()) + return; + for (const auto& child : child_requests_) { + if (!child.prepared) + return; + } + + // Build multipart body here. + std::vector<ContentTypeAndData> parts; + for (const auto& child : child_requests_) { + std::string type; + std::string data; + const bool result = child.request->GetContentData(&type, &data); + // Upload request must have content data. + DCHECK(result); + + const GURL url = child.request->GetURL(); + std::string method; + switch (child.request->GetRequestType()) { + case net::URLFetcher::POST: + method = "POST"; + break; + case net::URLFetcher::PUT: + method = "PUT"; + break; + default: + NOTREACHED(); + break; + } + + parts.push_back(ContentTypeAndData()); + parts.back().type = kHttpContentType; + parts.back().data = base::StringPrintf( + kBatchUploadRequestFormat, + method.c_str(), + url.path().c_str(), + url_generator_.GetBatchUploadUrl().host().c_str(), + type.c_str(), + data.c_str()); + } + + GenerateMultipartBody(MULTIPART_MIXED, boundary_, parts, &upload_content_); + prepare_callback_.Run(HTTP_SUCCESS); +} + +bool BatchUploadRequest::GetContentData(std::string* upload_content_type, + std::string* upload_content_data) { + upload_content_type->assign(upload_content_.type); + upload_content_data->assign(upload_content_.data); + return true; } base::WeakPtr<BatchUploadRequest> @@ -1002,16 +1129,42 @@ BatchUploadRequest::GetWeakPtrAsBatchUploadRequest() { } GURL BatchUploadRequest::GetURL() const { - NOTIMPLEMENTED(); - return GURL(); + return url_generator_.GetBatchUploadUrl(); +} + +net::URLFetcher::RequestType BatchUploadRequest::GetRequestType() const { + return net::URLFetcher::PUT; +} + +std::vector<std::string> BatchUploadRequest::GetExtraRequestHeaders() const { + std::vector<std::string> headers; + headers.push_back(kBatchUploadHeader); + return headers; } void BatchUploadRequest::ProcessURLFetchResults(const net::URLFetcher* source) { - NOTIMPLEMENTED(); + if (!IsSuccessfulDriveApiErrorCode(GetErrorCode())) { + RunCallbackOnPrematureFailure(GetErrorCode()); + sender_->RequestFinished(this); + return; + } + + for (auto& child : child_requests_) { + // TODO(hirono): Split the mutlipart result and return the correct code and + // body. + child.request->ProcessURLFetchResults(HTTP_SERVICE_UNAVAILABLE, ""); + // Request deletes itself after processing. + } + + child_requests_.clear(); } void BatchUploadRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) { - NOTIMPLEMENTED(); + for (auto child : child_requests_) { + child.request->RunCallbackOnPrematureFailure(code); + sender_->RequestFinished(child.request); + } + child_requests_.clear(); } } // namespace drive diff --git a/google_apis/drive/drive_api_requests.h b/google_apis/drive/drive_api_requests.h index 2af587b..0c587ec 100644 --- a/google_apis/drive/drive_api_requests.h +++ b/google_apis/drive/drive_api_requests.h @@ -1097,9 +1097,10 @@ class PermissionsInsertRequest : public EntryActionRequest { //========================== BatchUploadRequest ========================== struct BatchUploadChildEntry { - explicit BatchUploadChildEntry(UrlFetchRequestBase* request) + BatchUploadChildEntry() : request(NULL), prepared(false) {} + explicit BatchUploadChildEntry(BatchableRequestBase* request) : request(request), prepared(false) {} - UrlFetchRequestBase* request; + BatchableRequestBase* request; bool prepared; }; @@ -1110,30 +1111,65 @@ class BatchUploadRequest : public UrlFetchRequestBase { ~BatchUploadRequest() override; // Adds request to the batch request. - void AddRequest(UrlFetchRequestBase* request); + void AddRequest(BatchableRequestBase* request); // Completes building batch upload request, and starts to send the request to - // server. + // server. Must add at least one request before calling |Commit|. void Commit(); // Obtains weak pointer of this. base::WeakPtr<BatchUploadRequest> GetWeakPtrAsBatchUploadRequest(); + // Set boundary. Only tests can use this method. + void SetBoundaryForTesting(const std::string& boundary); + // Obtains reference to RequestSender that owns the request. RequestSender* sender() const { return sender_; } - DriveApiUrlGenerator url_generator() const { return url_generator_; } - // Returns URL of this request. - GURL GetURL() const override; + // Obtains URLGenerator. + const DriveApiUrlGenerator& url_generator() const { return url_generator_; } + // UrlFetchRequestBase overrides. + void Prepare(const PrepareCallback& callback) override; + void Cancel() override; + GURL GetURL() const override; + net::URLFetcher::RequestType GetRequestType() const override; + std::vector<std::string> GetExtraRequestHeaders() const override; + bool GetContentData( + std::string* upload_content_type, + std::string* upload_content) override; void ProcessURLFetchResults(const net::URLFetcher* source) override; void RunCallbackOnPrematureFailure(DriveApiErrorCode code) override; private: + typedef void* RequestID; + // Obtains corresponding child entry of |request_id|. Returns NULL if the + // entry is not found. + std::vector<BatchUploadChildEntry>::iterator + GetChildEntry(RequestID request_id); + + // Called after child requests' |Prepare| method. + void OnChildRequestPrepared(RequestID request_id, DriveApiErrorCode result); + + // Complete |Prepare| if possible. + void MayCompletePrepare(); + + // Process result for each child. + void ProcessURLFetchResultsForChild(RequestID id, const std::string& body); + RequestSender* const sender_; const DriveApiUrlGenerator url_generator_; std::vector<BatchUploadChildEntry> child_requests_; + PrepareCallback prepare_callback_; + bool committed_; + + // Boundary of multipart body. + std::string boundary_; + + // Multipart of child requests. + ContentTypeAndData upload_content_; + // Note: This should remain the last member so it'll be destroyed and // invalidate its weak pointers before any other members are destroyed. base::WeakPtrFactory<BatchUploadRequest> weak_ptr_factory_; diff --git a/google_apis/drive/drive_api_requests_unittest.cc b/google_apis/drive/drive_api_requests_unittest.cc index 1acbf2f..b872710 100644 --- a/google_apis/drive/drive_api_requests_unittest.cc +++ b/google_apis/drive/drive_api_requests_unittest.cc @@ -10,6 +10,7 @@ #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" #include "base/values.h" #include "google_apis/drive/drive_api_parser.h" #include "google_apis/drive/drive_api_requests.h" @@ -100,6 +101,9 @@ class DriveApiRequestsTest : public testing::Test { test_server_.RegisterRequestHandler( base::Bind(&DriveApiRequestsTest::HandleDownloadRequest, base::Unretained(this))); + test_server_.RegisterRequestHandler( + base::Bind(&DriveApiRequestsTest::HandleBatchUploadRequest, + base::Unretained(this))); GURL test_base_url = test_util::GetBaseUrlForTesting(test_server_.port()); url_generator_.reset( @@ -394,6 +398,21 @@ class DriveApiRequestsTest : public testing::Test { return response.Pass(); } + scoped_ptr<net::test_server::HttpResponse> HandleBatchUploadRequest( + const net::test_server::HttpRequest& request) { + http_request_ = request; + + const GURL absolute_url = test_server_.GetURL(request.relative_url); + std::string id; + if (absolute_url.path() != "/upload/drive") + return scoped_ptr<net::test_server::HttpResponse>(); + + scoped_ptr<net::test_server::BasicHttpResponse> response( + new net::test_server::BasicHttpResponse); + response->set_code(net::HTTP_OK); + return response.Pass(); + } + // These are for the current upload file status. int64 received_bytes_; int64 content_length_; @@ -1888,4 +1907,97 @@ TEST_F(DriveApiRequestsTest, PermissionsInsertRequest) { EXPECT_TRUE(base::Value::Equals(expected.get(), result.get())); } +TEST_F(DriveApiRequestsTest, BatchUploadRequest) { + // Preapre constants. + const char kTestContentType[] = "text/plain"; + const std::string kTestContent(10, 'a'); + const base::FilePath kTestFilePath = + temp_dir_.path().AppendASCII("upload_file.txt"); + ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kTestContent)); + + // Create batch request. + drive::BatchUploadRequest* const request = new drive::BatchUploadRequest( + request_sender_.get(), *url_generator_); + request->SetBoundaryForTesting("OUTERBOUNDARY"); + request_sender_->StartRequestWithRetry(request); + + // Create child request. + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> file_resource; + base::RunLoop run_loop[2]; + for (int i = 0; i < 2; ++i) { + const FileResourceCallback callback = test_util::CreateQuitCallback( + &run_loop[i], + test_util::CreateCopyResultCallback(&error, &file_resource)); + drive::MultipartUploadNewFileRequest* const child_request = + new drive::MultipartUploadNewFileRequest( + request_sender_.get(), + base::StringPrintf("new file title %d", i), + "parent_resource_id", + kTestContentType, + kTestContent.size(), + base::Time(), + base::Time(), + kTestFilePath, + drive::Properties(), + *url_generator_, + callback, + ProgressCallback()); + child_request->SetBoundaryForTesting("INNERBOUNDARY"); + request->AddRequest(child_request); + } + request->Commit(); + run_loop[0].Run(); + run_loop[1].Run(); + + EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); + EXPECT_EQ("batch", http_request_.headers["X-Goog-Upload-Protocol"]); + EXPECT_EQ("multipart/mixed; boundary=OUTERBOUNDARY", + http_request_.headers["Content-Type"]); + EXPECT_EQ("--OUTERBOUNDARY\n" + "Content-Type: application/http\n" + "\n" + "POST /upload/drive/v2/files HTTP/1.1\n" + "Host: 127.0.0.1\n" + "X-Goog-Upload-Protocol: multipart\n" + "Content-Type: multipart/related; boundary=INNERBOUNDARY\n" + "\n" + "--INNERBOUNDARY\n" + "Content-Type: application/json\n" + "\n" + "{\"parents\":[{\"id\":\"parent_resource_id\"," + "\"kind\":\"drive#fileLink\"}],\"title\":\"new file title 0\"}\n" + "--INNERBOUNDARY\n" + "Content-Type: text/plain\n" + "\n" + "aaaaaaaaaa\n" + "--INNERBOUNDARY--\n" + "--OUTERBOUNDARY\n" + "Content-Type: application/http\n" + "\n" + "POST /upload/drive/v2/files HTTP/1.1\n" + "Host: 127.0.0.1\n" + "X-Goog-Upload-Protocol: multipart\n" + "Content-Type: multipart/related; boundary=INNERBOUNDARY\n" + "\n" + "--INNERBOUNDARY\n" + "Content-Type: application/json\n" + "\n" + "{\"parents\":[{\"id\":\"parent_resource_id\"," + "\"kind\":\"drive#fileLink\"}],\"title\":\"new file title 1\"}\n" + "--INNERBOUNDARY\n" + "Content-Type: text/plain\n" + "\n" + "aaaaaaaaaa\n" + "--INNERBOUNDARY--\n" + "--OUTERBOUNDARY--", + http_request_.content); +} + +TEST_F(DriveApiRequestsTest, EmptyBatchUploadRequest) { + scoped_ptr<drive::BatchUploadRequest> request(new drive::BatchUploadRequest( + request_sender_.get(), *url_generator_)); + EXPECT_DEATH(request->Commit(), "Check failed"); +} + } // namespace google_apis |