Tải nội dung nghe nhìn lên tiếp nối trong Giao thức dữ liệu của Google

Eric Bidelman, Nhóm API G Suite
Tháng 2 năm 2010

  1. Giới thiệu
  2. Giao thức có thể tiếp tục
    1. Bắt đầu yêu cầu tải lên có thể tiếp tục
    2. Tải tệp lên
    3. Tiếp tục tải lên
    4. Huỷ tải lên
    5. Cập nhật tài nguyên hiện có
  3. Ví dụ về thư viện ứng dụng

Giới thiệu

Các tiêu chuẩn web hiện tại không cung cấp cơ chế đáng tin cậy để hỗ trợ việc tải tệp lớn lên qua HTTP. Do đó, việc tải tệp lên tại Google và các trang web khác thường bị giới hạn ở kích thước vừa phải (ví dụ: 100 MB). Đối với các dịch vụ như YouTube và Google Documents List API hỗ trợ tải tệp lớn lên, đây là một trở ngại lớn.

Giao thức có thể tiếp tục của Dữ liệu Google trực tiếp giải quyết các vấn đề nêu trên bằng cách hỗ trợ các yêu cầu HTTP POST/PUT có thể tiếp tục trong HTTP/1.0. Giao thức này được mô hình hoá theo ResumableHttpRequestsProposal do nhóm Google Gears đề xuất.

Tài liệu này mô tả cách kết hợp tính năng tải lên tiếp tục của Dữ liệu Google vào các ứng dụng của bạn. Các ví dụ dưới đây sử dụng Google Documents List Data API. Xin lưu ý rằng các API khác của Google triển khai giao thức này có thể có các yêu cầu/mã phản hồi/v.v. hơi khác. Vui lòng tham khảo tài liệu của dịch vụ để biết thông tin cụ thể.

Giao thức tiếp tục

Bắt đầu yêu cầu tải lên có thể tiếp tục

Để bắt đầu một phiên tải lên tiếp tục, hãy gửi một yêu cầu HTTP POST đến đường liên kết resumable-post. Đường liên kết này nằm ở cấp nguồn cấp dữ liệu. Đường liên kết có thể tiếp tục đăng của DocList API có dạng như sau:

<link rel="http://schemas.google.com/g/2005#resumable-create-media" type="application/atom+xml"
    href="https://docs.google.com/feeds/upload/create-session/default/private/full"/>

Nội dung của yêu cầu POST phải trống hoặc chứa một mục nhập XML Atom và không được chứa nội dung thực tế của tệp. Ví dụ bên dưới tạo một yêu cầu có thể tiếp tục để tải một tệp PDF lớn lên, đồng thời bao gồm tiêu đề cho tài liệu trong tương lai bằng cách sử dụng tiêu đề Slug.

POST /feeds/upload/create-session/default/private/full HTTP/1.1
Host: docs.google.com
GData-Version: version_number
Authorization: authorization
Content-Length: 0
Slug: MyTitle
X-Upload-Content-Type: content_type
X-Upload-Content-Length: content_length

empty body

Tiêu đề X-Upload-Content-TypeX-Upload-Content-Length phải được đặt thành loại MIME và kích thước của tệp mà bạn sẽ tải lên sau cùng. Nếu không nắm rõ được độ dài nội dung tại thời điểm tạo phiên tải lên, bạn có thể bỏ qua tiêu đề X-Upload-Content-Length.

Sau đây là một yêu cầu ví dụ khác, thay vào đó sẽ tải một tài liệu Word lên. Lần này, siêu dữ liệu Atom sẽ được đưa vào và áp dụng cho mục nhập tài liệu cuối cùng.

POST /feeds/upload/create-session/default/private/full?convert=false HTTP/1.1
Host: docs.google.com
GData-Version: version_number
Authorization: authorization
Content-Length: atom_metadata_content_length
Content-Type: application/atom+xml
X-Upload-Content-Type: application/msword
X-Upload-Content-Length: 7654321

<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">
  <category scheme="http://schemas.google.com/g/2005#kind"
      term="http://schemas.google.com/docs/2007#document"/>
  <title>MyTitle</title>
  <docs:writersCanInvite value="false"/>
</entry>

Phản hồi của máy chủ từ POST ban đầu là một URI tải lên duy nhất trong tiêu đề Location và một nội dung phản hồi trống:

HTTP/1.1 200 OK
Location: <upload_uri>

URI tải lên duy nhất sẽ được dùng để tải các khối tệp lên.

Lưu ý: Yêu cầu POST ban đầu sẽ không tạo một mục mới trong nguồn cấp dữ liệu. Trường hợp này chỉ xảy ra khi toàn bộ thao tác tải lên đã hoàn tất.

Lưu ý: URI của một phiên có thể tiếp tục sẽ hết hạn sau một tuần.

Tải tệp lên

Giao thức có thể tiếp tục cho phép (nhưng không yêu cầu) nội dung được tải lên theo "khối", vì HTTP không có hạn chế vốn có về kích thước yêu cầu. Ứng dụng có thể tự do chọn kích thước khối hoặc chỉ cần tải toàn bộ tệp lên. Ví dụ này sử dụng URI tải lên duy nhất để phát hành một lệnh PUT có thể tiếp tục. Ví dụ sau đây gửi 100.000 byte đầu tiên của tệp PDF có 1.234.567 byte:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 100000
Content-Range: bytes 0-99999/1234567

bytes 0-99999

Nếu không xác định được kích thước của tệp PDF, thì ví dụ này sẽ sử dụng Content-Range: bytes 0-99999/*. Đọc thêm thông tin về tiêu đề Content-Range tại đây.

Máy chủ phản hồi bằng dải byte hiện tại đã được lưu trữ:

HTTP/1.1 308 Resume Incomplete
Content-Length: 0
Range: bytes=0-99999

Ứng dụng của bạn phải tiếp tục PUT từng phần của tệp cho đến khi toàn bộ tệp được tải lên. Cho đến khi quá trình tải lên hoàn tất, máy chủ sẽ phản hồi bằng HTTP 308 Resume Incomplete và dải byte mà máy chủ biết trong tiêu đề Range. Các ứng dụng phải sử dụng tiêu đề Range để xác định vị trí bắt đầu phần tiếp theo. Do đó, đừng cho rằng máy chủ đã nhận được tất cả các byte được gửi ban đầu trong yêu cầu PUT.

Lưu ý: Máy chủ có thể phát hành một URI tải lên duy nhất mới trong tiêu đề Location trong một đoạn. Ứng dụng của bạn nên kiểm tra Location mới và dùng URI đó để gửi các khối còn lại đến máy chủ.

Khi quá trình tải lên hoàn tất, phản hồi sẽ giống như khi quá trình tải lên được thực hiện bằng cơ chế tải lên không tiếp tục của API. Tức là 201 Created sẽ được trả về cùng với <atom:entry>, do máy chủ tạo. Các PUT tiếp theo đến URI tải lên duy nhất sẽ trả về cùng một phản hồi như phản hồi được trả về khi quá trình tải lên hoàn tất. Sau một khoảng thời gian, phản hồi sẽ là 410 Gone hoặc 404 Not Found.

Tiếp tục tải lên

Nếu yêu cầu của bạn bị chấm dứt trước khi nhận được phản hồi từ máy chủ hoặc nếu bạn nhận được phản hồi HTTP 503 từ máy chủ, thì bạn có thể truy vấn trạng thái hiện tại của quá trình tải lên bằng cách đưa ra một yêu cầu PUT trống trên URI tải lên duy nhất.

Ứng dụng khách thăm dò máy chủ để xác định số byte mà máy chủ đã nhận được:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 0
Content-Range: bytes */content_length

Dùng * làm content_length nếu bạn không biết độ dài.

Máy chủ phản hồi bằng dải byte hiện tại:

HTTP/1.1 308 Resume Incomplete
Content-Length: 0
Range: bytes=0-42

Lưu ý: Nếu chưa cam kết bất kỳ byte nào cho phiên, thì máy chủ sẽ bỏ qua tiêu đề Range.

Lưu ý: Máy chủ có thể phát hành một URI tải lên duy nhất mới trong tiêu đề Location trong một đoạn. Ứng dụng của bạn nên kiểm tra Location mới và dùng URI đó để gửi các khối còn lại đến máy chủ.

Cuối cùng, ứng dụng sẽ tiếp tục từ nơi máy chủ dừng lại:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 57
Content-Range: 43-99/100

<bytes 43-99>

Huỷ tải lên

Nếu bạn muốn huỷ quá trình tải lên và ngăn mọi hành động tiếp theo đối với quá trình này, hãy đưa ra yêu cầu DELETE trên URI tải lên duy nhất.

DELETE upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 0

Nếu thành công, máy chủ sẽ phản hồi rằng phiên đã bị huỷ và phản hồi bằng cùng một mã cho các yêu cầu PUT hoặc yêu cầu trạng thái truy vấn khác:

HTTP/1.1 499 Client Closed Request

Lưu ý: Nếu bạn bỏ ngang quá trình tải lên mà không huỷ, thì yêu cầu tải lên sẽ tự động hết hạn sau một tuần kể từ khi được tạo.

Cập nhật tài nguyên hiện có

Tương tự như bắt đầu một phiên tải lên tiếp nối, bạn có thể sử dụng giao thức tải lên tiếp nối để thay thế nội dung của một tệp hiện có. Để bắt đầu yêu cầu cập nhật có thể tiếp tục, hãy gửi một PUT HTTP đến đường liên kết của mục nhập có rel='...#resumable-edit-media'. Mỗi entry nội dung nghe nhìn sẽ chứa một đường liên kết như vậy nếu API hỗ trợ việc cập nhật nội dung của tài nguyên.

Ví dụ: một mục tài liệu trong DocList API sẽ chứa một đường liên kết tương tự như:

<link rel="http://schemas.google.com/g/2005#resumable-edit-media" type="application/atom+xml"
      href="https://docs.google.com/feeds/upload/create-session/default/private/full/document%3A12345"/>

Do đó, yêu cầu ban đầu sẽ là:

PUT /feeds/upload/create-session/default/private/full/document%3A12345 HTTP/1.1
Host: docs.google.com
GData-Version: version_number
Authorization: authorization
If-Match: ETag | *
Content-Length: 0
X-Upload-Content-Length: content_length
X-Upload-Content-Type: content_type

empty body

Để cập nhật siêu dữ liệu và nội dung của tài nguyên cùng một lúc, hãy thêm Atom XML thay vì một phần nội dung trống. Hãy xem ví dụ trong phần Bắt đầu yêu cầu tải lên có thể tiếp tục.

Khi máy chủ phản hồi bằng URI tải lên duy nhất, hãy gửi một PUT cùng với tải trọng của bạn. Sau khi bạn có URI tải lên duy nhất, quy trình cập nhật nội dung của tệp cũng giống như quy trình tải tệp lên.

Ví dụ cụ thể này sẽ cập nhật nội dung của tài liệu hiện có trong một lần:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 1000
Content-Range: 0-999/1000

<bytes 0-999>

Quay lại đầu trang

Ví dụ về thư viện ứng dụng

Dưới đây là các mẫu tải tệp phim lên Google Tài liệu (bằng giao thức tải lên tiếp nối) trong Thư viện ứng dụng Google Data. Xin lưu ý rằng hiện tại, một số thư viện không hỗ trợ tính năng có thể tiếp tục.

int MAX_CONCURRENT_UPLOADS = 10;
int PROGRESS_UPDATE_INTERVAL = 1000;
int DEFAULT_CHUNK_SIZE = 10485760;


DocsService client = new DocsService("yourCompany-yourAppName-v1");
client.setUserCredentials("user@gmail.com", "pa$$word");

// Create a listener
FileUploadProgressListener listener = new FileUploadProgressListener(); // See the sample for details on this class.

// Pool for handling concurrent upload tasks
ExecutorService executor = Executors.newFixedThreadPool(MAX_CONCURRENT_UPLOADS);

// Create {@link ResumableGDataFileUploader} for each file to upload
List uploaders = Lists.newArrayList();

File file = new File("test.mpg");
String contentType = DocumentListEntry.MediaType.fromFileName(file.getName()).getMimeType();
MediaFileSource mediaFile = new MediaFileSource(file, contentType);
URL createUploadUrl = new URL("https://docs.google.com/feeds/upload/create-session/default/private/full");
ResumableGDataFileUploader uploader = new ResumableGDataFileUploader(createUploadUrl, mediaFile, client, DEFAULT_CHUNK_SIZE,
                                                                     executor, listener, PROGRESS_UPDATE_INTERVAL);
uploaders.add(uploader);

listener.listenTo(uploaders); // attach the listener to list of uploaders

// Start the upload(s)
for (ResumableGDataFileUploader uploader : uploaders) {
  uploader.start();
}

// wait for uploads to complete
while(!listener.isDone()) {
  try {
    Thread.sleep(100);
  } catch (InterruptedException ie) {
    listener.printResults();
    throw ie; // rethrow
  }
// Chunk size in MB
int CHUNK_SIZE = 1;

ClientLoginAuthenticator cla = new ClientLoginAuthenticator(
    "yourCompany-yourAppName-v1", ServiceNames.Documents, "user@gmail.com", "pa$$word");

// Set up resumable uploader and notifications
ResumableUploader ru = new ResumableUploader(CHUNK_SIZE);
ru.AsyncOperationCompleted += new AsyncOperationCompletedEventHandler(this.OnDone);
ru.AsyncOperationProgress += new AsyncOperationProgressEventHandler(this.OnProgress);

// Set metadata for our upload.
Document entry = new Document()
entry.Title = "My Video";
entry.MediaSource = new MediaFileSource("c:\\test.mpg", "video/mpeg");

// Add the upload uri to document entry.
Uri createUploadUrl = new Uri("https://docs.google.com/feeds/upload/create-session/default/private/full");
AtomLink link = new AtomLink(createUploadUrl.AbsoluteUri);
link.Rel = ResumableUploader.CreateMediaRelation;
entry.DocumentEntry.Links.Add(link);

ru.InsertAsync(cla, entry.DocumentEntry, userObject);
- (void)uploadAFile {
  NSString *filePath = @"~/test.mpg";
  NSString *fileName = [filePath lastPathComponent];

  // get the file's data
  NSData *data = [NSData dataWithContentsOfMappedFile:filePath];

  // create an entry to upload
  GDataEntryDocBase *newEntry = [GDataEntryStandardDoc documentEntry];
  [newEntry setTitleWithString:fileName];

  [newEntry setUploadData:data];
  [newEntry setUploadMIMEType:@"video/mpeg"];
  [newEntry setUploadSlug:fileName];

  // to upload, we need the entry, our service object, the upload URL,
  // and the callback for when upload has finished
  GDataServiceGoogleDocs *service = [self docsService];
  NSURL *uploadURL = [GDataServiceGoogleDocs docsUploadURL];
  SEL finishedSel = @selector(uploadTicket:finishedWithEntry:error:);

  // now start the upload
  GDataServiceTicket *ticket = [service fetchEntryByInsertingEntry:newEntry
                                                        forFeedURL:uploadURL
                                                          delegate:self
                                                 didFinishSelector:finishedSel];

  // progress monitoring is done by specifying a callback, like this
  SEL progressSel = @selector(ticket:hasDeliveredByteCount:ofTotalByteCount:);
  [ticket setUploadProgressSelector:progressSel];
}

// callback for when uploading has finished
- (void)uploadTicket:(GDataServiceTicket *)ticket
   finishedWithEntry:(GDataEntryDocBase *)entry
               error:(NSError *)error {
  if (error == nil) {
    // upload succeeded
  }
}

- (void)pauseOrResumeUploadForTicket:(GDataServiceTicket *)ticket {
  if ([ticket isUploadPaused]) {
    [ticket resumeUpload];
  } else {
    [ticket pauseUpload];
  }
}
import os.path
import atom.data
import gdata.client
import gdata.docs.client
import gdata.docs.data

CHUNK_SIZE = 10485760

client = gdata.docs.client.DocsClient(source='yourCompany-yourAppName-v1')
client.ClientLogin('user@gmail.com', 'pa$$word', client.source);

f = open('test.mpg')
file_size = os.path.getsize(f.name)

uploader = gdata.client.ResumableUploader(
    client, f, 'video/mpeg', file_size, chunk_size=CHUNK_SIZE, desired_class=gdata.docs.data.DocsEntry)

# Set metadata for our upload.
entry = gdata.docs.data.DocsEntry(title=atom.data.Title(text='My Video'))
new_entry = uploader.UploadFile('/feeds/upload/create-session/default/private/full', entry=entry)
print 'Document uploaded: ' + new_entry.title.text
print 'Quota used: %s' % new_entry.quota_bytes_used.text

Để xem các mẫu hoàn chỉnh và thông tin tham khảo về mã nguồn, hãy xem các tài nguyên sau:

Quay lại đầu trang