Eric Bidelman, zespół interfejsów API G Suite
Luty 2010
Wprowadzenie
Obecne standardy internetowe nie zapewniają niezawodnego mechanizmu ułatwiającego przesyłanie dużych plików przez HTTP. Dlatego przesyłanie plików w Google i innych witrynach było tradycyjnie ograniczone do umiarkowanych rozmiarów (np. 100 MB). W przypadku usług takich jak interfejsy API YouTube i Listy dokumentów Google, które obsługują przesyłanie dużych plików, stanowi to poważną przeszkodę.
Protokół Google Data z możliwością wznawiania bezpośrednio rozwiązuje wspomniane problemy, ponieważ obsługuje żądania HTTP POST/PUT z możliwością wznawiania w protokole HTTP/1.0. Protokół został opracowany na podstawie ResumableHttpRequestsProposal zespołu Google Gears.
Z tego dokumentu dowiesz się, jak zintegrować funkcję wznawiania przesyłania danych Google z aplikacjami. W przykładach poniżej użyto interfejsu Google Documents List Data API. Pamiętaj, że dodatkowe interfejsy API Google, które implementują ten protokół, mogą mieć nieco inne wymagania, kody odpowiedzi itp. Szczegółowe informacje znajdziesz w dokumentacji usługi.
Protokół wznawiania
Inicjowanie żądania przesyłania z możliwością wznowienia
Aby rozpocząć sesję przesyłania z możliwością wznowienia, wyślij żądanie HTTP POST na link resumable-post. Ten link znajduje się na poziomie pliku danych.
Link do funkcji resumable-post w DocList API wygląda tak:
<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"/>
Treść żądania POST powinna być pusta lub zawierać wpis XML w formacie Atom. Nie może zawierać rzeczywistej zawartości pliku.
W przykładzie poniżej tworzone jest żądanie z możliwością wznowienia, które umożliwia przesłanie dużego pliku PDF. Zawiera ono też tytuł przyszłego dokumentu w nagłówku 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
Nagłówki X-Upload-Content-Type i X-Upload-Content-Length powinny być ustawione na typ MIME i rozmiar pliku, który ostatecznie prześlesz. Jeśli w momencie tworzenia sesji przesyłania długość treści jest nieznana, nagłówek X-Upload-Content-Length można pominąć.
Oto przykładowa prośba, która zamiast tego przesyła dokument Worda. Tym razem metadane Atom są uwzględnione i zostaną zastosowane do końcowego wpisu dokumentu.
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>
Odpowiedź serwera na początkowe żądanie POST to unikalny identyfikator URI przesyłania w nagłówku Location i pusty tekst odpowiedzi:
HTTP/1.1 200 OK
Location: <upload_uri>
Unikalny identyfikator URI przesyłania będzie używany do przesyłania fragmentów pliku.
Uwaga: początkowa prośba POST nie powoduje utworzenia nowego wpisu w pliku danych.
Dzieje się tak tylko po zakończeniu całej operacji przesyłania.
Uwaga: identyfikator URI sesji z możliwością wznowienia wygasa po tygodniu.
Przesyłanie pliku
Protokół wznawialny umożliwia przesyłanie treści w „porcjach”, ale nie wymaga tego, ponieważ w HTTP nie ma ograniczeń dotyczących rozmiarów żądań. Klient może wybrać rozmiar fragmentu lub przesłać plik w całości.
W tym przykładzie używamy unikalnego identyfikatora URI przesyłania, aby wydać żądanie przesyłania możliwego do wznowienia PUT. Ten przykład wysyła pierwsze 100 000 bajtów pliku PDF o rozmiarze 1234567 bajtów:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567 bytes 0-99999
Jeśli rozmiar pliku PDF byłby nieznany, w tym przykładzie użyto by wartości Content-Range: bytes
0-99999/*. Więcej informacji o nagłówku Content-Range znajdziesz tutaj.
Serwer odpowiada bieżącym zakresem bajtów, który został zapisany:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
Klient powinien nadal PUT każdy fragment pliku, dopóki nie zostanie przesłany cały plik.
Do czasu zakończenia przesyłania serwer będzie odpowiadać kodem HTTP 308 Resume Incomplete i zakresem bajtów, które zna, w nagłówku Range. Klienci muszą używać nagłówka Range, aby określić, od którego miejsca rozpocząć następny fragment.
Nie zakładaj więc, że serwer otrzymał wszystkie bajty pierwotnie wysłane w żądaniu PUT.
Uwaga: serwer może wygenerować nowy, niepowtarzalny identyfikator URI przesyłania w nagłówku Location podczas przesyłania fragmentu. Klient powinien sprawdzić, czy jest dostępny zaktualizowany Location, i użyć tego identyfikatora URI do wysłania pozostałych części do serwera.
Po zakończeniu przesyłania odpowiedź będzie taka sama jak w przypadku przesyłania za pomocą mechanizmu przesyłania bez możliwości wznowienia interfejsu API. Oznacza to, że wraz z wartością <atom:entry> zwrócona zostanie wartość 201 Created utworzona przez serwer. Kolejne żądania PUT wysyłane do unikalnego identyfikatora URI przesyłania będą zwracać tę samą odpowiedź, która została zwrócona po zakończeniu przesyłania.
Po pewnym czasie odpowiedź będzie miała wartość 410 Gone lub 404 Not Found.
Wznawianie przesyłania
Jeśli żądanie zostanie zakończone przed otrzymaniem odpowiedzi z serwera lub jeśli otrzymasz z serwera odpowiedź HTTP 503, możesz sprawdzić bieżący stan przesyłania, wysyłając puste żądanie PUT na unikalny identyfikator URI przesyłania.
Klient wysyła do serwera zapytanie, aby określić, które bajty zostały odebrane:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
Jeśli długość jest nieznana, użyj * jako content_length.
Serwer odpowiada z bieżącym zakresem bajtów:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
Uwaga: jeśli serwer nie przesłał żadnych bajtów w ramach sesji, pominie nagłówek Range.
Uwaga: serwer może wygenerować nowy, niepowtarzalny identyfikator URI przesyłania w nagłówku Location podczas przesyłania fragmentu. Klient powinien sprawdzić, czy jest dostępny zaktualizowany Location, i użyć tego identyfikatora URI do wysłania pozostałych części do serwera.
Na koniec klient wznawia działanie od miejsca, w którym serwer je przerwał:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
Anulowanie przesyłania
Jeśli chcesz anulować przesyłanie i zapobiec dalszym działaniom, wyślij żądanie DELETE na unikalny identyfikator URI przesyłania.
DELETE upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
Jeśli operacja się uda, serwer odpowie, że sesja została anulowana, i zwróci ten sam kod w przypadku kolejnych PUT lub żądań stanu:
HTTP/1.1 499 Client Closed Request
Uwaga: jeśli przesyłanie zostanie przerwane bez anulowania, wygaśnie po tygodniu od utworzenia.
Aktualizowanie istniejącego zasobu
Podobnie jak w przypadku rozpoczynania sesji przesyłania z możliwością wznowienia, możesz użyć protokołu przesyłania z możliwością wznowienia, aby zastąpić zawartość istniejącego pliku. Aby rozpocząć żądanie aktualizacji z możliwością wznowienia, wyślij żądanie HTTP PUT na link do wpisu z rel='...#resumable-edit-media'. Każdy element multimedialny entry będzie zawierać taki link, jeśli interfejs API obsługuje aktualizowanie treści zasobu.
Na przykład wpis dokumentu w interfejsie DocList API będzie zawierać link podobny do tego:
<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"/>
Początkowe żądanie będzie więc wyglądać tak:
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
Aby zaktualizować metadane i treść zasobu w tym samym czasie, zamiast pustej treści uwzględnij Atom XML. Przykład znajdziesz w sekcji Wysyłanie prośby o wznowienie przesyłania.
Gdy serwer odpowie unikalnym identyfikatorem URI przesyłania, wyślij PUT z ładunkiem. Gdy uzyskasz unikalny identyfikator URI przesyłania, proces aktualizowania zawartości pliku będzie taki sam jak w przypadku przesyłania pliku.
Ten konkretny przykład zaktualizuje treść istniejącego dokumentu za jednym razem:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
Przykłady bibliotek klienta
Poniżej znajdziesz przykłady przesyłania pliku filmowego do Dokumentów Google (przy użyciu protokołu przerywanego przesyłania) w bibliotekach klienta Google Data. Pamiętaj, że nie wszystkie biblioteki obsługują obecnie funkcję wznawiania.
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
Pełne przykłady i informacje o kodzie źródłowym znajdziesz w tych materiałach:
- Biblioteka Java przykładowa aplikacja i źródło
- Przykładowa aplikacja biblioteki Objective-C
- Źródło biblioteki .NET