Eric Bidelman, nhóm API G Suite
Tháng 2 năm 2010
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-Type
và X-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>
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 Listuploaders = 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:
- Ứng dụng mẫu và nguồn của thư viện Java
- Ứng dụng mẫu của thư viện Objective-C
- Nguồn của thư viện .NET