Upload Media yang Dapat Dilanjutkan di Google Data Protocol

Eric Bidelman, tim G Suite API
Februari 2010

  1. Pengantar
  2. Protokol yang Dapat Dilanjutkan
    1. Memulai permintaan upload yang dapat dilanjutkan
    2. Mengupload file
    3. Melanjutkan upload
    4. Membatalkan upload
    5. Memperbarui resource yang ada
  3. Contoh library klien

Pengantar

Standar web saat ini tidak menyediakan mekanisme yang andal untuk memfasilitasi upload file besar melalui HTTP. Akibatnya, upload file di Google dan situs lain biasanya dibatasi hingga ukuran sedang (misalnya, 100 MB). Untuk layanan seperti YouTube dan Google Documents List API yang mendukung upload file besar, hal ini menjadi kendala besar.

Protokol yang dapat dilanjutkan Google Data secara langsung mengatasi masalah yang disebutkan di atas dengan mendukung permintaan HTTP POST/PUT yang dapat dilanjutkan di HTTP/1.0. Protokol ini dibuat berdasarkan ResumableHttpRequestsProposal yang disarankan oleh tim Google Gears.

Dokumen ini menjelaskan cara menggabungkan fitur upload yang dapat dilanjutkan Google Data ke dalam aplikasi Anda. Contoh di bawah menggunakan Google Documents List Data API. Perhatikan bahwa API Google tambahan yang menerapkan protokol ini mungkin memiliki persyaratan/kode respons/dll. yang sedikit berbeda. Lihat dokumentasi layanan untuk mengetahui detailnya.

Protokol yang Dapat Dilanjutkan

Memulai permintaan upload yang dapat dilanjutkan

Untuk memulai sesi upload yang dapat dilanjutkan, kirim permintaan HTTP POST ke link resumable-post. Link ini ditemukan di tingkat feed. Link resumable-post DocList API terlihat seperti:

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

Isi permintaan POST Anda harus kosong atau berisi entri XML Atom dan tidak boleh menyertakan konten file yang sebenarnya. Contoh di bawah ini membuat permintaan yang dapat dilanjutkan untuk mengupload PDF berukuran besar, dan menyertakan judul untuk dokumen mendatang menggunakan header 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

Header X-Upload-Content-Type dan X-Upload-Content-Length harus disetel ke mimetype dan ukuran file yang akan Anda upload. Jika panjang konten tidak diketahui saat sesi upload dibuat, header X-Upload-Content-Length dapat dihilangkan.

Berikut adalah contoh permintaan lain yang mengupload dokumen Word. Kali ini, metadata Atom disertakan dan akan diterapkan ke entri dokumen akhir.

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>

Respons server dari POST awal adalah URI upload unik di header Location dan isi respons kosong:

HTTP/1.1 200 OK
Location: <upload_uri>

URI upload unik akan digunakan untuk mengupload potongan file.

Catatan: Permintaan POST awal tidak membuat entri baru di feed. Peristiwa ini hanya terjadi saat seluruh operasi upload telah selesai.

Catatan: Masa berlaku URI sesi yang dapat dilanjutkan akan berakhir setelah satu minggu.

Mengupload file

Protokol yang dapat dilanjutkan memungkinkan, tetapi tidak mewajibkan, konten diupload dalam 'potongan', karena tidak ada batasan bawaan dalam HTTP pada ukuran permintaan. Klien Anda bebas memilih ukuran chunk atau hanya mengupload file secara keseluruhan. Contoh ini menggunakan URI upload unik untuk mengeluarkan PUT yang dapat dilanjutkan. Contoh berikut mengirimkan 100000 byte pertama file PDF 1234567 byte:

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

bytes 0-99999

Jika ukuran file PDF tidak diketahui, contoh ini akan menggunakan Content-Range: bytes 0-99999/*. Baca informasi selengkapnya tentang header Content-Range di sini.

Server merespons dengan rentang byte saat ini yang telah disimpan:

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

Klien Anda harus terus melakukan PUT pada setiap bagian file hingga seluruh file diupload. Hingga upload selesai, server akan merespons dengan HTTP 308 Resume Incomplete dan rentang byte yang diketahuinya di header Range. Klien harus menggunakan header Range untuk menentukan tempat memulai potongan berikutnya. Oleh karena itu, jangan berasumsi bahwa server menerima semua byte yang awalnya dikirim dalam permintaan PUT.

Catatan: Server dapat mengeluarkan URI upload unik baru di header Location selama chunk. Klien Anda harus memeriksa Location yang telah diupdate dan menggunakan URI tersebut untuk mengirimkan potongan yang tersisa ke server.

Setelah upload selesai, respons akan sama seperti jika upload dilakukan menggunakan mekanisme upload non-dapat dilanjutkan API. Artinya, 201 Created akan ditampilkan bersama dengan <atom:entry>, seperti yang dibuat oleh server. PUT berikutnya ke URI upload unik akan menampilkan respons yang sama dengan yang ditampilkan saat upload selesai. Setelah jangka waktu tertentu, responsnya akan menjadi 410 Gone atau 404 Not Found.

Melanjutkan upload

Jika permintaan Anda dihentikan sebelum menerima respons dari server atau jika Anda menerima respons 503 HTTP dari server, Anda dapat membuat kueri status upload saat ini dengan mengajukan permintaan PUT kosong di URI upload unik.

Klien melakukan polling server untuk menentukan byte yang telah diterima:

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

Gunakan * sebagai content_length jika panjangnya tidak diketahui.

Server merespons dengan rentang byte saat ini:

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

Catatan: Jika server belum melakukan byte apa pun untuk sesi, server akan menghilangkan header Range.

Catatan: Server dapat mengeluarkan URI upload unik baru di header Location selama chunk. Klien Anda harus memeriksa Location yang telah diupdate dan menggunakan URI tersebut untuk mengirimkan potongan yang tersisa ke server.

Terakhir, klien melanjutkan dari tempat server berhenti:

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

<bytes 43-99>

Membatalkan upload

Jika Anda ingin membatalkan upload dan mencegah tindakan lebih lanjut pada upload tersebut, ajukan permintaan DELETE pada URI upload unik.

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

Jika berhasil, server akan merespons bahwa sesi dibatalkan, dan merespons dengan kode yang sama untuk PUT lebih lanjut atau permintaan status kueri:

HTTP/1.1 499 Client Closed Request

Catatan: Jika upload dibatalkan tanpa pembatalan, masa berlakunya akan berakhir secara otomatis satu minggu setelah dibuat.

Memperbarui resource yang ada

Mirip dengan memulai sesi upload yang dapat dilanjutkan, Anda dapat menggunakan protokol upload yang dapat dilanjutkan untuk mengganti konten file yang ada. Untuk memulai permintaan update yang dapat dilanjutkan, kirim HTTP PUT ke link entri dengan rel='...#resumable-edit-media'. Setiap media entry akan berisi link tersebut jika API mendukung update konten resource.

Sebagai contoh, entri dokumen di DocList API akan berisi link yang mirip dengan:

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

Dengan demikian, permintaan awalnya adalah:

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

Untuk memperbarui metadata dan konten resource secara bersamaan, sertakan Atom XML, bukan isi kosong. Lihat contoh di bagian Memulai permintaan upload yang dapat dilanjutkan.

Saat server merespons dengan URI upload unik, kirim PUT dengan payload Anda. Setelah Anda memiliki URI upload unik, proses untuk memperbarui konten file sama dengan mengupload file.

Contoh khusus ini akan memperbarui konten dokumen yang ada dalam satu kali pengambilan:

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

<bytes 0-999>

Kembali ke atas

Contoh library klien

Di bawah ini adalah contoh mengupload file film ke Google Dokumen (menggunakan protokol upload yang dapat dilanjutkan) di library klien Google Data. Perhatikan bahwa tidak semua library mendukung fitur yang dapat dilanjutkan saat ini.

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

Untuk contoh lengkap dan referensi kode sumber, lihat referensi berikut:

Kembali ke atas