Eric Bidelman, team delle API di G Suite
Febbraio 2010
Introduzione
Gli standard web attuali non forniscono un meccanismo affidabile per facilitare il caricamento HTTP di file di grandi dimensioni. Di conseguenza, i caricamenti di file su Google e altri siti sono stati tradizionalmente limitati a dimensioni moderate (ad es. 100 MB). Per servizi come le API YouTube e Google Documents List, che supportano il caricamento di file di grandi dimensioni, questo rappresenta un ostacolo importante.
Il protocollo di caricamento ripristinabile di Google Data risolve direttamente i problemi sopra menzionati supportando le richieste HTTP POST/PUT ripristinabili in HTTP/1.0. Il protocollo è stato modellato sulla base della ResumableHttpRequestsProposal suggerita dal team di Google Gears.
Questo documento descrive come incorporare la funzionalità di caricamento ripristinabile di Google Data nelle tue applicazioni. Gli esempi riportati di seguito utilizzano l'API Google Documents List Data. Tieni presente che le API Google aggiuntive che implementano questo protocollo potrebbero avere requisiti/codici di risposta/ecc. leggermente diversi. Per informazioni specifiche, consulta la documentazione del servizio.
Il protocollo ripristinabile
Avvio di una richiesta di caricamento ripristinabile
Per avviare una sessione di caricamento ripristinabile, invia una richiesta HTTP POST al link resumable-post. Questo link si trova a livello di feed.
Il link resumable-post dell'API DocList ha il seguente aspetto:
<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"/>
Il corpo della richiesta POST deve essere vuoto o contenere una voce XML Atom e non deve includere i contenuti effettivi del file.
L'esempio seguente crea una richiesta ripristinabile per caricare un PDF di grandi dimensioni e include un titolo per il documento futuro utilizzando l'intestazione 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
Le intestazioni X-Upload-Content-Type e X-Upload-Content-Length devono essere impostate sul tipo MIME e sulle dimensioni del file che caricherai in un secondo momento. Se la durata dei contenuti non è nota al momento della creazione della sessione di caricamento, l'intestazione X-Upload-Content-Length può essere omessa.
Ecco un'altra richiesta di esempio che carica invece un documento Word. Questa volta, i metadati Atom sono inclusi e verranno applicati alla voce del documento finale.
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>
La risposta del server alla POST iniziale è un URI di caricamento univoco nell'intestazione Location
e un corpo della risposta vuoto:
HTTP/1.1 200 OK
Location: <upload_uri>
L'URI di caricamento univoco verrà utilizzato per caricare i blocchi di file.
Nota: la richiesta iniziale di POST non crea una nuova voce nel feed.
Ciò accade solo al termine dell'intera operazione di caricamento.
Nota: l'URI di una sessione ripristinabile scade dopo una settimana.
Caricamento di un file
Il protocollo ripristinabile consente, ma non richiede, il caricamento dei contenuti in "blocchi", perché non esistono restrizioni intrinseche in HTTP sulle dimensioni delle richieste. Il client è libero di scegliere la dimensione dei blocchi o di caricare il file nel suo complesso.
Questo esempio utilizza l'URI di caricamento univoco per emettere un PUT ripristinabile. L'esempio seguente invia i primi 100.000
byte del file PDF di 1234567 byte:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567 bytes 0-99999
Se le dimensioni del file PDF non fossero note, questo esempio utilizzerebbe Content-Range: bytes
0-99999/*. Scopri di più sull'intestazione Content-Range
qui.
Il server risponde con l'intervallo di byte corrente che è stato archiviato:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
Il client deve continuare a PUT ogni blocco del file finché l'intero file non è stato caricato.
Fino al completamento del caricamento, il server risponderà con un codice HTTP 308 Resume Incomplete e l'intervallo di byte che conosce
nell'intestazione Range. I client devono utilizzare l'intestazione Range per determinare da dove iniziare il blocco successivo.
Pertanto, non dare per scontato che il server abbia ricevuto tutti i byte inviati originariamente nella richiesta PUT.
Nota: il server potrebbe emettere un nuovo URI di caricamento univoco nell'intestazione Location durante un blocco. Il cliente dovrebbe
controllare un Location aggiornato e utilizzare questo URI per inviare i chunk rimanenti al server.
Al termine del caricamento, la risposta sarà la stessa di quella che si otterrebbe se il caricamento fosse stato eseguito utilizzando
il meccanismo di caricamento non ripristinabile dell'API. ovvero verrà restituito un 201 Created insieme a <atom:entry>,
come creato dal server. Le successive PUT all'URI di caricamento univoco restituiranno la stessa risposta restituita al termine del caricamento.
Dopo un periodo di tempo, la risposta sarà 410 Gone o 404 Not Found.
Riprendere un caricamento
Se la richiesta viene terminata prima di ricevere una risposta dal server o se ricevi una risposta HTTP 503 dal server, puoi
interrogare lo stato attuale del caricamento inviando una richiesta PUT vuota all'URI di caricamento univoco.
Il client esegue il polling del server per determinare quali byte ha ricevuto:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
Utilizza * come content_length se la lunghezza non è nota.
Il server risponde con l'intervallo di byte corrente:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
Nota: se il server non ha eseguito il commit di alcun byte per la sessione, ometterà l'intestazione Range.
Nota: il server potrebbe emettere un nuovo URI di caricamento univoco nell'intestazione Location durante un blocco. Il cliente dovrebbe
controllare un Location aggiornato e utilizzare questo URI per inviare i chunk rimanenti al server.
Infine, il client riprende da dove era stato interrotto il server:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
Annullamento di un caricamento
Se vuoi annullare il caricamento e impedire qualsiasi ulteriore azione, invia una richiesta
DELETE all'URI di caricamento univoco.
DELETE upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
Se l'operazione va a buon fine, il server risponde che la sessione è stata annullata e restituisce lo stesso codice
per ulteriori PUT o richieste di stato delle query:
HTTP/1.1 499 Client Closed Request
Nota: se un caricamento viene abbandonato senza essere annullato, scade naturalmente una settimana dopo la creazione.
Aggiornamento di una risorsa esistente
Analogamente all'avvio di una sessione di caricamento ripristinabile, puoi utilizzare
il protocollo di caricamento ripristinabile per sostituire il contenuto di un file esistente. Per avviare una richiesta di aggiornamento ripristinabile,
invia un HTTP PUT al link della voce con rel='...#resumable-edit-media'.
Ogni media entry conterrà un link di questo tipo se l'API supporta l'aggiornamento dei contenuti della risorsa.
Ad esempio, una voce di documento nell'API DocList conterrà un link simile a:
<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"/>
Pertanto, la richiesta iniziale sarebbe:
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
Per aggiornare contemporaneamente i metadati e i contenuti di una risorsa, includi Atom XML anziché un corpo vuoto. Consulta l'esempio nella sezione Avvio di una richiesta di caricamento ripristinabile.
Quando il server risponde con l'URI di caricamento univoco, invia un PUT con il payload. Una volta ottenuto l'URI di caricamento univoco, la procedura per aggiornare i contenuti del file è la stessa del caricamento di un file.
Questo esempio specifico aggiornerà i contenuti del documento esistente in un'unica operazione:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
Esempi di librerie client
Di seguito sono riportati esempi di caricamento di un file di film su Documenti Google (utilizzando il protocollo di caricamento ripristinabile) nelle librerie client Google Data. Tieni presente che al momento non tutte le librerie supportano la funzionalità di ripresa.
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
Per esempi completi e riferimenti al codice sorgente, consulta le seguenti risorse:
- App di esempio e origine della libreria Java
- App di esempio della libreria Objective-C
- Origine della libreria .NET