summaryrefslogtreecommitdiffstats
path: root/google_apis
diff options
context:
space:
mode:
authorhirono <hirono@chromium.org>2015-04-15 01:27:03 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-15 08:27:28 +0000
commit289a1e6dc3bde6c00496a3ce6d19e4258dcfd073 (patch)
tree380a355d4a8e183c1b8cd57b0a981e0263ba38de /google_apis
parent9e30e307e27f6ce8bfa373a29a19ac4fad431925 (diff)
downloadchromium_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.cc167
-rw-r--r--google_apis/drive/drive_api_requests.h50
-rw-r--r--google_apis/drive/drive_api_requests_unittest.cc112
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