Fortsetzbare Media-Uploads im Google Data Protocol

Eric Bidelman, G Suite APIs-Team
Februar 2010

  1. Einführung
  2. Das fortsetzbare Protokoll
    1. Fortsetzbaren Upload starten
    2. Datei hochladen
    3. Upload fortsetzen
    4. Upload abbrechen
    5. Vorhandene Ressource aktualisieren
  3. Beispiele für Clientbibliotheken

Einführung

Die aktuellen Webstandards bieten keinen zuverlässigen Mechanismus für den HTTP-Upload großer Dateien. Daher waren Dateiuploads bei Google und anderen Websites bisher auf moderate Größen (z.B. 100 MB) beschränkt. Für Dienste wie die YouTube API und die Google Documents List API, die das Hochladen großer Dateien unterstützen, stellt dies ein großes Problem dar.

Das Google Data-Protokoll für fortsetzbare Uploads behebt die oben genannten Probleme direkt, indem es fortsetzbare POST-/PUT-HTTP-Anfragen in HTTP/1.0 unterstützt. Das Protokoll wurde nach dem vom Google Gears-Team vorgeschlagenen ResumableHttpRequestsProposal modelliert.

In diesem Dokument wird beschrieben, wie Sie die Funktion für fortsetzbare Uploads von Google Data in Ihre Anwendungen einbinden. In den folgenden Beispielen wird die Google Documents List Data API verwendet. Beachten Sie, dass für zusätzliche Google APIs, die dieses Protokoll implementieren, möglicherweise leicht abweichende Anforderungen, Antwortcodes usw. gelten. Weitere Informationen finden Sie in der Dokumentation des jeweiligen Dienstes.

Das fortsetzbare Protokoll

Anfrage für fortsetzbaren Upload starten

Senden Sie zum Starten einer fortsetzbaren Uploadsitzung eine HTTP-POST-Anfrage an den Link „resumable-post“. Dieser Link befindet sich auf Feedebene. Der Link für fortsetzbare POST-Anfragen der DocList API sieht so aus:

<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"/>

Der Body Ihrer POST-Anfrage sollte leer sein oder einen Atom-XML-Eintrag enthalten. Die eigentlichen Dateiinhalte dürfen nicht enthalten sein. Im folgenden Beispiel wird eine fortsetzbare Anfrage zum Hochladen einer großen PDF-Datei erstellt. Der Titel für das zukünftige Dokument wird mit dem Header Slug angegeben.

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

Die Header X-Upload-Content-Type und X-Upload-Content-Length sollten auf den MIME-Typ und die Größe der Datei festgelegt werden, die Sie letztendlich hochladen. Wenn die Länge des Inhalts bei der Erstellung der Uploadsitzung unbekannt ist, kann der X-Upload-Content-Length-Header weggelassen werden.

Hier ist ein weiteres Beispiel für eine Anfrage, in der stattdessen ein Word-Dokument hochgeladen wird. Dieses Mal sind Atom-Metadaten enthalten, die auf den endgültigen Dokumenteintrag angewendet werden.

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>

Die Antwort des Servers auf die ursprüngliche POST ist ein eindeutiger Upload-URI im Location-Header und ein leerer Antworttext:

HTTP/1.1 200 OK
Location: <upload_uri>

Der eindeutige Upload-URI wird zum Hochladen der Dateichunks verwendet.

Hinweis: Durch die ursprüngliche POST-Anfrage wird kein neuer Eintrag im Feed erstellt. Das geschieht nur, wenn der gesamte Uploadvorgang abgeschlossen ist.

Hinweis: Der URI einer fortsetzbaren Sitzung läuft nach einer Woche ab.

Datei hochladen

Das fortsetzbare Protokoll ermöglicht, aber erfordert nicht, dass Inhalte in „Chunks“ hochgeladen werden, da es in HTTP keine Einschränkungen für die Anfragengröße gibt. Ihr Client kann die Chunk-Größe kostenlos wählen oder die Datei als Ganzes hochladen. In diesem Beispiel wird der eindeutige Upload-URI verwendet, um einen fortsetzbaren PUT auszugeben. Im folgenden Beispiel werden die ersten 100.000 Byte einer 1.234.567 Byte großen PDF-Datei gesendet:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 100000
Content-Range: bytes 0-99999/1234567

bytes 0-99999

Wenn die Größe der PDF-Datei unbekannt ist, wird in diesem Beispiel Content-Range: bytes 0-99999/* verwendet. Weitere Informationen zum Content-Range-Header

Der Server antwortet mit dem aktuellen Bytebereich, der gespeichert wurde:

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

Ihr Client sollte PUT für die einzelnen Teile so lange durchführen, bis die gesamte Datei hochgeladen wurde. Bis der Upload abgeschlossen ist, antwortet der Server mit einem HTTP-Statuscode 308 Resume Incomplete und dem Bytebereich, den er im Range-Header kennt. Clients müssen den Range-Header verwenden, um zu ermitteln, wo der nächste Teil beginnt. Gehen Sie daher nicht davon aus, dass der Server alle ursprünglich in der PUT-Anfrage gesendeten Byte erhalten hat.

Hinweis: Der Server kann während eines Chunks einen neuen eindeutigen Upload-URI im Header Location ausgeben. Ihr Client sollte nach einem aktualisierten Location suchen und diesen URI verwenden, um die verbleibenden Chunks an den Server zu senden.

Wenn der Upload abgeschlossen ist, entspricht die Antwort der Antwort, die zurückgegeben wird, wenn der Upload über den nicht fortsetzbaren Uploadmechanismus der API erfolgt ist. Das heißt, dass zusammen mit dem vom Server erstellten <atom:entry> ein 201 Created zurückgegeben wird. Bei nachfolgenden PUT-Anfragen an den eindeutigen Upload-URI wird dieselbe Antwort zurückgegeben wie beim Abschluss des Uploads. Nach einer gewissen Zeit lautet die Antwort 410 Gone oder 404 Not Found.

Upload fortsetzen

Wenn Ihre Anfrage beendet wird, bevor Sie eine Antwort vom Server erhalten, oder wenn Sie vom Server eine Antwort vom Typ 503 erhalten, können Sie den aktuellen Status des Uploads abfragen, indem Sie eine leere PUT-Anfrage an die eindeutige Upload-URI senden.

Der Client fragt den Server ab, um zu ermitteln, welche Bytes er empfangen hat:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 0
Content-Range: bytes */content_length

Verwenden Sie * als content_length, wenn die Länge nicht bekannt ist.

Der Server antwortet mit dem aktuellen Bytebereich:

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

Hinweis: Wenn der Server für die Sitzung keine Bytes übertragen hat, wird der Range-Header ausgelassen.

Hinweis: Der Server kann während eines Chunks einen neuen eindeutigen Upload-URI im Header Location ausgeben. Ihr Client sollte nach einem aktualisierten Location suchen und diesen URI verwenden, um die verbleibenden Chunks an den Server zu senden.

Schließlich setzt der Client an der Stelle fort, an der der Server aufgehört hat:

PUT upload_uri HTTP/1.1
Host: docs.google.com
Content-Length: 57
Content-Range: 43-99/100

<bytes 43-99>

Upload abbrechen

Wenn Sie den Upload abbrechen und weitere damit verbundene Aktionen verhindern möchten, senden Sie eine DELETE-Anfrage an den eindeutigen Upload-URI.

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

Bei Erfolg antwortet der Server, dass die Sitzung abgebrochen wurde, und gibt für weitere PUT- oder Anfragen zum Abfragestatus denselben Code zurück:

HTTP/1.1 499 Client Closed Request

Hinweis: Wenn ein Upload ohne Abbrechen abgebrochen wird, läuft er automatisch eine Woche nach der Erstellung ab.

Vorhandene Ressource aktualisieren

Ähnlich wie beim Starten einer fortsetzbaren Uploadsitzung können Sie das Protokoll für fortsetzbare Uploads verwenden, um den Inhalt einer vorhandenen Datei zu ersetzen. Um eine fortsetzbare Aktualisierungsanfrage zu starten, senden Sie eine HTTP-PUT-Anfrage an den Link des Eintrags mit rel='...#resumable-edit-media'. Jede Media-entry enthält einen solchen Link, wenn die API das Aktualisieren des Inhalts der Ressource unterstützt.

Ein Dokumenteintrag in der DocList API enthält beispielsweise einen Link wie:

<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"/>

Die ursprüngliche Anfrage wäre also:

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

Wenn Sie die Metadaten und den Inhalt einer Ressource gleichzeitig aktualisieren möchten, fügen Sie Atom-XML anstelle eines leeren Texts ein. Ein Beispiel finden Sie im Abschnitt Anfrage zum Starten eines fortsetzbaren Uploads.

Wenn der Server mit dem eindeutigen Upload-URI antwortet, senden Sie eine PUT-Anfrage mit Ihrer Nutzlast. Sobald Sie den eindeutigen Upload-URI haben, ist das Verfahren zum Aktualisieren des Inhalts der Datei dasselbe wie beim Hochladen einer Datei.

In diesem Beispiel wird der Inhalt des vorhandenen Dokuments in einem Schritt aktualisiert:

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

<bytes 0-999>

Nach oben

Beispiele für Clientbibliotheken

Unten finden Sie Beispiele für das Hochladen einer Filmdatei in Google Docs (mit dem fortsetzbaren Uploadprotokoll) in den Google Data-Clientbibliotheken. Hinweis: Derzeit unterstützen nicht alle Bibliotheken die Funktion zum Fortsetzen.

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

Vollständige Beispiele und Quellcode-Referenzen finden Sie in den folgenden Ressourcen:

Nach oben