Upload Media yang Dapat Dilanjutkan di Protokol Data Google

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 HTTP file berukuran besar. Akibatnya, upload file di Google dan situs lain biasanya dibatasi hingga ukuran sedang (mis. 100 MB). Untuk layanan seperti YouTube dan API Daftar Dokumen Google yang mendukung upload file besar, proses ini menghadirkan tantangan utama.

Protokol yang dapat dilanjutkan Data Google secara langsung mengatasi masalah yang disebutkan di atas dengan mendukung permintaan HTTP POST/PUT yang dapat dilanjutkan di HTTP/1.0. Protokol tersebut menjalani model setelah ResumableHttpRequestsProposal yang disarankan oleh tim Google Gears.

Dokumen ini menjelaskan cara menggabungkan fitur upload Google Data yang dapat dilanjutkan ke aplikasi Anda. Contoh di bawah ini menggunakan Google Docs List Data API. Perlu diperhatikan bahwa Google API tambahan yang menerapkan protokol ini mungkin memiliki persyaratan/kode respons/sedikit berbeda. Harap baca dokumentasi layanan untuk mengetahui detailnya.

Protokol yang Dapat Dilanjutkan

Memulai permintaan upload yang dapat dilanjutkan

Untuk memulai sesi upload yang dapat dilanjutkan, kirim permintaan POST HTTP ke link postingan yang dapat dilanjutkan. Link ini ditemukan di level feed. Link postingan yang dapat dilanjutkan di 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 membuat permintaan yang dapat dilanjutkan untuk mengupload PDF 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 jenis mime dan ukuran file yang pada akhirnya akan Anda upload. Jika durasi konten tidak diketahui pada waktu pembuatan sesi upload, header X-Upload-Content-Length dapat dihilangkan.

Berikut adalah contoh permintaan lain yang mengupload dokumen kata. 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. Hal ini hanya terjadi saat seluruh operasi upload selesai.

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

Mengupload file

Protokol yang dapat dilanjutkan memungkinkan, tetapi tidak memerlukan, konten akan diupload di 'bagian', karena tidak ada batasan inheren di HTTP pada ukuran permintaan. Klien Anda bebas memilih ukuran potongannya atau cukup mengupload file secara keseluruhan. Contoh ini menggunakan URI upload unik untuk mengeluarkan PUT yang dapat dilanjutkan. Contoh berikut mengirimkan 100.000 byte pertama dari 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 melanjutkan ke PUT setiap bagian file hingga seluruh file telah diupload. Sampai upload selesai, server akan merespons dengan 308 Resume Incomplete HTTP 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 dalam header Location selama proses potongan. Klien Anda harus memeriksa Location yang telah diperbarui dan menggunakan URI tersebut untuk mengirim potongan yang tersisa ke server.

Setelah upload selesai, respons akan sama seperti jika upload telah dilakukan menggunakan mekanisme upload yang tidak 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 seperti yang ditampilkan saat upload selesai. Setelah jangka waktu tertentu, responsnya akan menjadi 410 Gone atau 404 Not Found.

Melanjutkan upload

Jika permintaan dihentikan sebelum menerima respons dari server atau jika Anda menerima respons 503 HTTP dari server, Anda dapat mengkueri status upload saat ini dengan mengeluarkan permintaan PUT kosong pada URI upload unik.

Klien memeriksa 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 menerapkan byte apa pun untuk sesi tersebut, header Range akan dihilangkan.

Catatan: Server dapat mengeluarkan URI upload unik baru dalam header Location selama proses potongan. Klien Anda harus memeriksa Location yang telah diperbarui dan menggunakan URI tersebut untuk mengirim potongan yang tersisa ke server.

Terakhir, klien melanjutkan dari bagian terakhir yang ditinggalkan server:

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 terhadapnya, kirimkan 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 diabaikan tanpa pembatalan, biasanya upload akan berakhir satu minggu setelah dibuat.

Memperbarui resource yang ada

Serupa 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 pembaruan konten resource.

Misalnya, 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 awal 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 sekaligus, sertakan Atom XML, bukan body 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 kesempatan:

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

Berikut adalah contoh upload file film ke Google Dokumen (menggunakan protokol upload yang dapat dilanjutkan) di library klien Data Google. 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 referensi lengkap dan contoh kode sumber, lihat referensi berikut:

Kembali ke atas