Tải lên nội dung nghe nhìn có thể tiếp tục 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 tiếp nối
    1. Bắt đầu một yêu cầu tải lên tiếp nối
    2. Tải tệp lên
    3. Tiếp tục tải lên
    4. Hủy 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ợ tải HTTP lên các tệp lớn. Do đó, trước đây việc tải tệp lên Google và những trang web khác thường bị giới hạn ở kích thước trung bình (ví dụ: 100 MB). Đối với các dịch vụ như API của YouTube và API Danh sách tài liệu của Google hỗ trợ tải lên tệp lớn, điều này gây trở ngại lớn.

Giao thức tiếp tục dữ liệu của Google giải quyết trực tiếp 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á sau khi ResumableHttpRequestsSuggested được nhóm Google Jetpack đề xuất.

Tài liệu này mô tả cách kết hợp tính năng tải lên có thể tiếp tục của Dữ liệu của Google vào ứng dụng của bạn. Các ví dụ bên dưới sử dụng API dữ liệu danh sách tài liệu của Google. 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 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 nối

Bắt đầu một yêu cầu tải lên tiếp nối

Để bắt đầu một phiên tải lên tiếp tục, hãy gửi yêu cầu HTTP POST tới đường liên kết của bài đăng có thể tiếp tục. Bạn có thể tìm thấy đường liên kết này ở cấp nguồn cấp dữ liệu. Đường liên kết có thể đăng lại của API DocsList 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ục XML Atom và không được chứa nội dung tệp thực tế. Ví dụ dưới đây sẽ tạo một yêu cầu có thể tiếp tục tải tệp PDF lớn lên và bao gồm tiêu đề cho tài liệu sau này 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

Bạn nên đặt tiêu đề X-Upload-Content-TypeX-Upload-Content-Length thành MIME và kích thước của tệp mà cuối cùng bạn sẽ tải lên. Nếu không xác định được độ dài nội dung khi tạo phiên tải lên, thì bạn có thể bỏ qua tiêu đề X-Upload-Content-Length.

Dưới đây là một yêu cầu mẫu khác thay vì việc tải tài liệu từ lên. Lần này, siêu dữ liệu Atom được đưa vào và sẽ á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à 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 phần tệp lên.

Lưu ý: Yêu cầu POST ban đầu không tạo mục nhập mới trong nguồn cấp dữ liệu. Điều này chỉ xảy ra khi toàn bộ hoạt động tải lên đã hoàn tất.

Lưu ý: Một URI 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 bắt buộc tải nội dung lên dưới dạng 'phân đoạn', vì không có hạn chế vốn có trong HTTP về kích thước yêu cầu. Khách hàng của bạn có thể chọn kích thước dữ liệu của họ 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 PUT có thể tiếp nối. Ví dụ sau đây sẽ gửi 100000 byte đầu tiên của tệp PDF 1234567 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 kích thước của tệp PDF không xác định, 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 ô hiện tại đã được lưu trữ:

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

Khách hà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 308 Resume Incomplete HTTP và phạm vi byte mà nó biết trong tiêu đề Range. Ứ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 giả định rằng máy chủ đã nhận được tất cả các byte ban đầu đã gửi 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 phân đoạn. Máy khách của bạn nên kiểm tra Location được cập nhật và sử dụng URI đó để gửi các phần 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 thể 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ư những gì đượ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 gửi một yêu cầu PUT trống trên URI tải lên duy nhất.

Máy khách thăm dò máy chủ để xác định số byte đã nhận:

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

Hãy dùng * làm content_length nếu không biết độ dài.

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

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

Lưu ý: Nếu máy chủ chưa gửi 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 phân đoạn. Máy khách của bạn nên kiểm tra Location được cập nhật và sử dụng URI đó để gửi các phần còn lại đến máy chủ.

Cuối cùng, máy khách sẽ tiếp tục từ nơi máy chủ đã ngừng hoạt động:

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 chặn hành động khác, hãy đưa ra yêu cầu DELETE trên URI tải lên riêng biệ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 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 một tệp tải lên bị bỏ qua mà không bị huỷ, thì tệp đó sẽ hết hạn một tuần sau khi tạo.

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

Tương tự như việc bắt đầu 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 tệp hiện có. Để bắt đầu một yêu cầu cập nhật có thể tiếp tục, hãy gửi một HTTP PUT đến đường liên kết của mục nhập bằng rel='...#resumable-edit-media'. Mỗi nội dung đa phương tiện entry 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 nhập tài liệu trong API DocList 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 nội dung và siêu dữ liệu của tài nguyên cùng một lúc, hãy thêm XML Atom thay vì nội dung trống. Xem ví dụ trong mục Bắt đầu một yêu cầu tải lên tiếp nối.

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 kèm theo trọng tải của bạn. Sau khi bạn có URI tải lên duy nhất, thì quy trình cập nhật nội dung của tệp cũng giống như quá 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 cảnh quay:

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

<bytes 0-999>

Trở 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 (sử dụng giao thức tải lên tiếp nối) trong Thư viện ứng dụng Google Data. Lưu ý rằng không phải thư viện nào cũng hỗ trợ tính năng có thể tiếp tục vào thời điểm này.

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 toàn bộ mẫu và tham chiếu mã nguồn, hãy xem các tài nguyên sau:

Trở lại đầu trang