Eric Bidelman, tim G Suite API
Februari 2010
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>
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 Listuploaders = 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:
- Aplikasi contoh dan sumber library Java
- Aplikasi contoh library Objective-C
- Sumber library .NET