Eric Bidelman, équipe API G Suite
Février 2010
Introduction
Les normes Web actuelles ne fournissent pas de mécanisme fiable pour faciliter l'importation HTTP de fichiers volumineux. Par conséquent, la taille des fichiers importés sur Google et sur d'autres sites était généralement limitée à une taille moyenne (par exemple, 100 Mo). Pour les services tels que YouTube et les API Google Documents List qui prennent en charge l'importation de fichiers volumineux, cela représente un obstacle majeur.
Le protocole de reprise des données Google répond directement aux problèmes mentionnés ci-dessus en prenant en charge les requêtes HTTP POST/PUT avec reprise dans HTTP/1.0. Le protocole a été modélisé à partir de la ResumableHttpRequestsProposition suggérée par l'équipe Google Gears.
Ce document explique comment intégrer la fonctionnalité d'importation avec reprise de Google Data dans vos applications. Les exemples ci-dessous utilisent l'API de données de la liste Google Docs. Notez que d'autres API Google qui implémentent ce protocole peuvent avoir des exigences/codes de réponse/etc. légèrement différents. Veuillez consulter la documentation du service pour en savoir plus.
Protocole avec reprise
Lancer une requête d'importation avec reprise
Pour lancer une session d'importation avec reprise, envoyez une requête HTTP POST
au lien de reprise du post. Ce lien se trouve au niveau du flux.
Le lien vers l'article avec reprise de l'API DocList ressemble à ceci:
<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"/>
Le corps de votre requête POST
doit être vide ou contenir une entrée Atom XML. Il ne doit pas inclure le contenu réel du fichier.
L'exemple ci-dessous crée une requête de reprise pour importer un fichier PDF volumineux et inclut un titre pour le futur document à l'aide de l'en-tête 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
Les en-têtes X-Upload-Content-Type
et X-Upload-Content-Length
doivent être définis sur le type MIME et la taille du fichier que vous importerez. Si la longueur du contenu n'est pas connue à la création de la session d'importation, l'en-tête X-Upload-Content-Length
peut être omis.
Voici un autre exemple de requête qui importe à la place un document Word. Cette fois, les métadonnées Atom sont incluses et seront appliquées à l'entrée finale du document.
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 réponse du serveur à partir de POST
est un URI d'importation unique dans l'en-tête Location
et un corps de réponse vide:
HTTP/1.1 200 OK
Location: <upload_uri>
L'URI d'importation unique sera utilisé pour importer les fragments de fichier.
Remarque : La requête POST
initiale ne crée pas d'entrée dans le flux.
Cela ne se produit que lorsque l'importation est terminée.
Remarque: Un URI de session avec reprise expire au bout d'une semaine.
Importer un fichier
Le protocole avec reprise autorise, sans toutefois l'exiger, l'importation de contenus fragmentés, car il n'existe aucune restriction inhérente à HTTP sur les tailles de requêtes. Votre client est libre de choisir la taille de ses segments ou d'importer le fichier dans son ensemble.
Cet exemple utilise l'URI d'importation unique pour émettre une PUT
avec reprise. L'exemple suivant envoie les 100 000 premiers octets du fichier PDF de 1234567 octets:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567bytes 0-99999
Si la taille du fichier PDF n'est pas connue, Content-Range: bytes
0-99999/*
est utilisé dans cet exemple. Pour en savoir plus sur l'en-tête Content-Range
, cliquez ici.
Le serveur renvoie la plage d'octets actuellement stockée:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
Votre client doit continuer à PUT
chaque fragment du fichier jusqu'à ce que l'intégralité du fichier ait été importée.
Tant que l'importation n'est pas terminée, le serveur répond avec une valeur HTTP 308 Resume Incomplete
et la plage d'octets dont il a connaissance dans l'en-tête Range
. Les clients doivent utiliser l'en-tête Range
pour déterminer où démarrer le prochain fragment.
Par conséquent, ne partez pas du principe que le serveur a reçu tous les octets initialement envoyés dans la requête PUT
.
Remarque : Le serveur peut émettre un nouvel URI d'importation unique dans l'en-tête Location
pendant un fragment. Votre client doit rechercher une mise à jour de Location
et utiliser cet URI pour envoyer les fragments restants au serveur.
Une fois l'importation terminée, la réponse est la même que si l'importation avait été effectuée à l'aide du mécanisme d'importation non réactivable de l'API. Autrement dit, un message 201 Created
sera renvoyé avec l'identifiant <atom:entry>
, tel que créé par le serveur. Les PUT
suivantes vers l'URI d'importation unique renvoient la même réponse que celle renvoyée une fois l'importation terminée.
Après une période donnée, la réponse sera 410 Gone
ou 404 Not Found
.
Reprendre une importation
Si votre requête est arrêtée avant de recevoir une réponse du serveur ou si vous recevez une réponse HTTP 503
du serveur, vous pouvez interroger l'état actuel de l'importation en envoyant une requête PUT
vide sur l'URI d'importation unique.
Le client interroge le serveur pour déterminer les octets reçus:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
Utilisez *
comme content_length si la longueur n'est pas connue.
Le serveur répond avec la plage d'octets actuelle:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
Remarque: Si le serveur n'a pas validé d'octets pour la session, il omet l'en-tête Range
.
Remarque: Le serveur peut émettre un nouvel URI d'importation unique dans l'en-tête Location
pendant un fragment. Votre client doit rechercher une mise à jour de Location
et utiliser cet URI pour envoyer les fragments restants au serveur.
Enfin, le client reprend là où le serveur s'était arrêté:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
Annulation d'une importation
Si vous souhaitez annuler l'importation et empêcher toute autre action, envoyez une requête DELETE
à l'URI d'importation unique.
DELETEupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
Si l'opération réussit, le serveur indique que la session est annulée et renvoie le même code pour les autres PUT
ou requêtes d'état de la requête:
HTTP/1.1 499 Client Closed Request
Remarque: Si une importation est abandonnée sans annulation, elle expire naturellement une semaine après sa création.
Mettre à jour une ressource existante
De la même manière que pour lancer une session d'importation avec reprise, vous pouvez utiliser le protocole d'importation avec reprise pour remplacer le contenu d'un fichier existant. Pour démarrer une requête de mise à jour avec reprise, envoyez un PUT
HTTP au lien d'entrée avec rel='...#resumable-edit-media
'. Chaque support entry
contient ce lien si l'API est compatible avec la mise à jour du contenu de la ressource.
Par exemple, une entrée de document dans l'API DocList contient un lien semblable à celui-ci:
<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"/>
La requête initiale serait donc la suivante:
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
Pour mettre à jour les métadonnées et le contenu d'une ressource en même temps, incluez Atom XML au lieu d'un corps de texte vide. Consultez l'exemple de la section Lancer une requête d'importation avec reprise.
Lorsque le serveur répond avec l'URI d'importation unique, envoyez un PUT
avec votre charge utile. Une fois que vous disposez de l'URI d'importation unique, le processus de mise à jour du contenu du fichier est identique à la mise en ligne d'un fichier.
Cet exemple permet de mettre à jour le contenu d'un document existant en une seule image:
PUTupload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
Exemples de bibliothèques clientes
Vous trouverez ci-dessous des exemples d'importation d'un fichier vidéo dans Google Docs (à l'aide du protocole d'importation avec reprise) dans les bibliothèques clientes Google Data. Notez que les bibliothèques n'acceptent pas toutes la fonctionnalité de reprise pour le moment.
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
Pour obtenir des exemples complets et une référence du code source, consultez les ressources suivantes:
- Exemple d'application et de source de la bibliothèque Java
- Exemple d'application de la bibliothèque Objective-C
- Source de la bibliothèque .NET