G Suite API 團隊 Eric Bidelman
2010 年 2 月
簡介
目前的網路標準並未提供可靠的機制,方便您上傳大型檔案的 HTTP。因此,在 Google 和其他網站上上傳檔案,傳統大小通常只有中等 (例如 100 MB)。使用 YouTube 和支援大型檔案的 Google Documents List API 等服務時,您就會遇到許多重大問題。
支援 HTTP/1.0 的可恢復 POST/PUT HTTP 要求,因此 Google 資料支援通訊協定能直接解決上述問題。 這個通訊協定是在 GoogleEclipse 小組建議的 ResumableHttpRequestsProposal 後建立。
本文說明如何在您的應用程式中加入 Google Data 支援續傳的上傳功能。以下範例使用 Google Documents List Data API。請注意,實作此通訊協定的其他 Google API 的要求/回應代碼可能稍有不同。詳情請參閱服務的說明文件。
支援通訊協定
啟動支援續傳的上傳要求
如要啟動可繼續的上傳工作階段,請將 HTTP POST
要求傳送至 resumable-post 連結。這個連結位於動態饋給層級。
DocList API 的 resumable-post 連結看起來會像這樣:
<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"/>
POST
要求的主體應為空白或包含 Atom XML 項目,且不可包含實際的檔案內容。下方範例會建立可續傳的請求來上傳大型 PDF 檔案,並使用 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
X-Upload-Content-Type
和 X-Upload-Content-Length
標頭應設為最終上傳檔案的 MIME 類型和大小。如果上傳工作階段期間的內容長度不明,則可省略 X-Upload-Content-Length
標頭。
以下是另一個要求上傳文字文件的另一個要求範例。這次,包含 Atom 中繼資料,並套用至最終文件項目。
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>
伺服器從初始 POST
的回應是 Location
標頭中唯一的上傳 URI 和空白回應主體:
HTTP/1.1 200 OK
Location: <upload_uri>
系統會使用不重複的上傳 URI 來上傳檔案區塊。
注意:初始 POST
要求不會在動態饋給中建立新項目。
這項作業只會在整個上傳作業完成之後發生。
注意:可繼續的工作階段 URI 會在一週後到期。
上傳檔案
透過可恢復的通訊協定,您無須上傳「內容區塊」的內容,因為 HTTP 沒有對要求大小的固有限制。您的用戶端可以自由選擇區塊大小,也可以直接上載整個檔案。
這個範例使用專屬上傳 URI 來發出可重新啟用的 PUT
。以下範例會傳送前 100000 個位元組的 1234567 位元組 PDF 檔案:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567bytes 0-99999
如果 PDF 檔案大小不明,這個範例會使用 Content-Range: bytes
0-99999/*
。如要進一步瞭解 Content-Range
標頭,請按這裡。
伺服器會以目前儲存的位元組範圍回應:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
在您的檔案上傳之前,您的用戶端應持續對檔案的每個區塊進行 PUT
。
上傳完成前,伺服器會在 Range
標頭中以 HTTP 308 Resume Incomplete
和已知的位元組範圍回應。用戶端必須使用 Range
標頭來判斷下一個區塊的起始位置。
因此,請勿假設伺服器接收的是原本在 PUT
要求中傳送的所有位元組。
注意:伺服器在區塊中可能在 Location
標頭發出新的專屬上傳 URI。您的用戶端應檢查是否有更新的 Location
,並使用該 URI 將剩餘的區塊傳送至伺服器。
上傳完成後,回應將與使用 API 非支援上傳機制進行的上傳相同。也就是說,系統會傳回 201 Created
,以及伺服器建立的 <atom:entry>
。專屬上傳 URI 的後續 PUT
會傳回與上傳完成時傳回的回應。一段時間後,回應會是 410 Gone
或 404 Not Found
。
繼續上傳
如果在收到伺服器的回應之前就終止要求,或是您收到伺服器傳來的 HTTP 503
回應,您可以對唯一的上傳 URI 發出空白的 PUT
要求,以查詢上傳目前的狀態。
用戶端會輪詢伺服器,以判斷其收到的位元組:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
如果長度不明,請使用 *
做為 content_length。
伺服器會以目前的位元組範圍回應:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
注意:如果伺服器並未針對工作階段提交任何位元組,就會省略 Range
標頭。
注意:伺服器在區塊中可能在 Location
標頭發出新的專屬上傳 URI。您的用戶端應檢查是否有更新的 Location
,並使用該 URI 將剩餘的區塊傳送至伺服器。
最後,用戶端會在伺服器上次中斷的地方繼續:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
取消上傳
如要取消上傳作業,並避免後續採取其他動作,請對專屬上傳 URI 發出 DELETE
要求。
DELETEupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
如果成功,伺服器會回應工作階段已取消,並以相同的程式碼回應後續 PUT
或查詢狀態要求:
HTTP/1.1 499 Client Closed Request
注意:如果上傳作業在沒有取消的情況下遭到捨棄,在建立後一週就會自然到期。
更新現有資源
與啟動支援續傳的上傳工作階段類似,您可以使用支援續傳的上傳通訊協定,取代現有檔案的內容。如要提出可繼續的更新要求,請將 HTTP PUT
傳送至含有 rel='...#resumable-edit-media
' 的項目連結。如果 API 支援更新資源內容,則每個媒體 entry
都會包含這類連結。
例如,DocList API 中的文件項目會包含類似下方的連結:
<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"/>
因此,初始要求會是:
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
如要同時更新資源的中繼資料和內容,請納入 Atom XML 而非空白內文。請參閱啟動支援續傳的上傳要求一節中的範例。
當伺服器以不重複的上傳 URI 回應時,請傳送含有酬載的 PUT
。取得唯一的上傳 URI 之後,更新檔案內容的程序與上傳檔案的程序相同。
此特定範例會一次更新現有文件的內容:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
用戶端程式庫範例
以下是在 Google Data 用戶端程式庫中使用電影檔案 (使用可恢復的上傳通訊協定) 將電影檔案上傳至 Google 文件的範例。請注意,並非所有程式庫都支援支援功能。
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
// 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
如需完整範例和原始碼參考資料,請參閱下列資源: