Caricamenti di contenuti multimediali ripristinabili nel protocollo Google Data

Eric Bidelman, team delle API di G Suite
febbraio 2010

  1. Introduzione
  2. Il protocollo ripristinabile
    1. Avviare una richiesta di caricamento ripristinabile
    2. Caricamento di un file
    3. Riprendere un caricamento
    4. Annullare un caricamento
    5. Aggiornamento di una risorsa esistente
  3. Esempi di librerie client

Introduzione

Gli attuali standard web non offrono un meccanismo affidabile per facilitare il caricamento HTTP di file di grandi dimensioni. Di conseguenza, i caricamenti di file su Google e su altri siti sono stati tradizionalmente limitati a dimensioni moderate (ad esempio 100 MB). Per servizi come YouTube e le API Elenco documenti di Google che supportano il caricamento di file di grandi dimensioni, questo rappresenta un grosso ostacolo.

Il protocollo ripristinabile di Google Data risolve direttamente i problemi indicati in precedenza supportando le richieste HTTP POST/PUT ripristinabili in HTTP/1.0. Il protocollo è stato modellato sulla base della ResumableHttpRequestsProposta suggerita dal team di Google Gear.

Questo documento descrive come incorporare la funzionalità di caricamento ripristinabile di Google Data nelle tue applicazioni. Gli esempi riportati di seguito utilizzano l'API Documenti Google per l'elenco dei documenti. Tieni presente che le API di Google aggiuntive che implementano questo protocollo potrebbero avere requisiti/codici di risposta/lenti leggermente diversi. Consulta la documentazione del servizio per le specifiche.

Protocollo ripristinabile

Avvio di una richiesta di caricamento ripristinabile

Per avviare una sessione di caricamento ripristinabile, invia una richiesta HTTP POST al link ripristinabile del post. Questo link si trova a livello di feed. Il link ripristinabile del 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 Atom XML e non deve includere i contenuti effettivi del file. L'esempio seguente crea una richiesta ripristinabile per caricare un file 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 sulla dimensione del file che caricherai. Se la lunghezza dei contenuti è sconosciuta durante la creazione della sessione di caricamento, l'intestazione X-Upload-Content-Length può essere omessa.

Ecco un'altra richiesta di esempio che carica un documento Word. Questa volta, i metadati Atom sono inclusi e verranno applicati alla voce finale del documento.

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 dalla POST iniziale è un URI di caricamento univoco nell'intestazione Location e un corpo di 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 POST iniziale non crea una nuova voce nel feed. Questo accade solo quando l'intera operazione di caricamento è stata completata.

Nota: un URI di sessione ripristinabile scade dopo una settimana.

Caricamento di un file

Il protocollo ripristinabile consente, ma non richiede, di caricare contenuti in "blocchi", perché non esistono limitazioni intrinseche in HTTP sulle dimensioni delle richieste. Il cliente è libero di scegliere la dimensione del componente o di caricare semplicemente il file. In questo esempio viene utilizzato l'URI di caricamento univoco per emettere un PUT ripristinabile. L'esempio seguente invia i primi 100.000 byte di un file PDF di 1234.567 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 sono sconosciute, in questo esempio viene utilizzato Content-Range: bytes 0-99999/*. Scopri di più sull'intestazione Content-Range qui.

Il server risponde con l'intervallo di byte corrente archiviato:

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

Il tuo client dovrebbe continuare a eseguire il PUT per ogni blocco di file fino al caricamento dell'intero file. Fino al completamento del caricamento, il server risponderà con un 308 Resume Incomplete HTTP 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 originariamente inviati nella richiesta PUT.

Nota: durante un blocco, il server può emettere un nuovo URI di caricamento univoco nell'intestazione Location. Il tuo client deve verificare la presenza di un Location aggiornato e utilizzare l'URI per inviare i componenti rimanenti al server.

Una volta completato il caricamento, la risposta sarà la stessa di se il caricamento fosse stato effettuato utilizzando il meccanismo di caricamento non ripristinabile dell'API. Ciò significa che verrà restituito un 201 Created insieme a <atom:entry>, come creato dal server. I successivi PUT all'URI di caricamento univoco restituiranno la stessa risposta che è stata restituita al termine del caricamento. Dopo un periodo di tempo, la risposta sarà 410 Gone o 404 Not Found.

Ripresa di un caricamento

Se la tua richiesta viene terminata prima di ricevere una risposta dal server o se ricevi una risposta HTTP 503 dal server, puoi eseguire query sullo stato attuale del caricamento inviando una richiesta PUT vuota sull'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: durante un blocco, il server può emettere un nuovo URI di caricamento univoco nell'intestazione Location. Il tuo client deve verificare la presenza di un Location aggiornato e utilizzare l'URI per inviare i componenti rimanenti al server.

Infine, il client riprende da dove il server aveva interrotto:

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 altra azione al riguardo, invia una richiesta DELETE per l'URI di caricamento univoco.

DELETE upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 0

Se l'operazione ha esito positivo, il server risponde che la sessione è stata annullata e risponde con lo stesso codice per ulteriori PUTs o richieste di stato della query:

HTTP/1.1 499 Client Closed Request

Nota: se un caricamento viene abbandonato senza annullare, 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 i contenuti di un file esistente. Per avviare una richiesta di aggiornamento ripristinabile, invia un elemento PUT HTTP al link della voce con rel='...#resumable-edit-media'. Ogni elemento multimediale entry conterrà questo link 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"/>

Di conseguenza, 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. Vedi l'esempio nella sezione Avviare una richiesta di caricamento ripristinabile.

Quando il server risponde con l'URI di caricamento univoco, invia un PUT con il payload. Una volta creato l'URI di caricamento univoco, la procedura per aggiornare i contenuti del file equivale a caricare un file.

Questo specifico esempio aggiorna i contenuti del documento esistente in un'unica ripresa:

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

<bytes 0-999>

Torna all'inizio

Esempi di libreria client

Di seguito sono riportati esempi di caricamento di file di film in Documenti Google (utilizzando il protocollo di caricamento ripristinabile) nelle librerie client di Google Data. Tieni presente che al momento non tutte le librerie supportano la funzionalità ripristinabile.

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

Per esempi completi e riferimento di codice sorgente, consulta le risorse seguenti:

Torna all'inizio