اریک بیدلمن، تیم رابطهای برنامهنویسی کاربردی G Suite
فوریه ۲۰۱۰
مقدمه
استانداردهای فعلی وب هیچ مکانیزم قابل اعتمادی برای تسهیل آپلود فایلهای بزرگ از طریق HTTP ارائه نمیدهند. در نتیجه، آپلود فایل در گوگل و سایر سایتها به طور سنتی به اندازههای متوسط (مثلاً ۱۰۰ مگابایت) محدود شده است. برای سرویسهایی مانند یوتیوب و APIهای فهرست اسناد گوگل که از آپلود فایلهای بزرگ پشتیبانی میکنند، این یک مانع بزرگ است.
پروتکل از سرگیری دادههای گوگل (Google Data resumable protocol) با پشتیبانی از درخواستهای POST/PUT HTTP قابل از سرگیری در HTTP/1.0، مستقیماً به مشکلات فوقالذکر میپردازد. این پروتکل پس از ResumableHttpRequestsProposal که توسط تیم Google Gears پیشنهاد شده بود، مدلسازی شد.
این سند نحوهی گنجاندن ویژگی آپلود قابل از سرگیری گوگل دیتا را در برنامههای شما شرح میدهد. مثالهای زیر از API فهرست دادههای اسناد گوگل استفاده میکنند. توجه داشته باشید که APIهای گوگل دیگری که این پروتکل را پیادهسازی میکنند، ممکن است الزامات/کدهای پاسخ/و غیره کمی متفاوت داشته باشند. لطفاً برای جزئیات بیشتر به مستندات سرویس مراجعه کنید.
پروتکل قابل از سرگیری
شروع یک درخواست آپلود با قابلیت از سرگیری
برای شروع یک جلسه آپلود قابل از سرگیری، یک درخواست HTTP POST به لینک resumable-post ارسال کنید. این لینک در سطح فید یافت میشود. لینک resumable-post مربوط به DocList API به شکل زیر است:
<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"/>
بدنه درخواست POST شما باید خالی باشد یا حاوی یک ورودی XML از نوع Atom باشد و نباید شامل محتوای واقعی فایل باشد. مثال زیر یک درخواست قابل از سرگیری برای آپلود یک PDF بزرگ ایجاد میکند و با استفاده از سربرگ 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
هدرهای X-Upload-Content-Type و X-Upload-Content-Length باید روی mimetype و اندازه فایلی که در نهایت آپلود خواهید کرد تنظیم شوند. اگر طول محتوا در هنگام ایجاد جلسه آپلود ناشناخته باشد، میتوان هدر X-Upload-Content-Length را حذف کرد.
در اینجا نمونه درخواست دیگری را مشاهده میکنید که در آن به جای آپلود یک سند ورد، ابردادههای اتم نیز اضافه شده و در ورودی نهایی سند اعمال میشوند.
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>
پاسخ سرور از POST اولیه، یک URI آپلود منحصر به فرد در هدر Location و یک بدنه پاسخ خالی است:
HTTP/1.1 200 OK
Location: <upload_uri>
از URI منحصر به فرد آپلود برای آپلود تکههای فایل استفاده خواهد شد.
نکته : درخواست اولیه POST ورودی جدیدی در فید ایجاد نمیکند. این اتفاق فقط زمانی میافتد که کل عملیات آپلود تکمیل شده باشد.
نکته : یک URI جلسه که قابلیت از سرگیری دارد، پس از یک هفته منقضی میشود.
آپلود فایل
پروتکل resumable اجازه میدهد، اما الزامی ندارد که محتوا به صورت «تکه تکه» آپلود شود، زیرا هیچ محدودیت ذاتی در HTTP برای اندازه درخواست وجود ندارد. کلاینت شما آزاد است که اندازه تکه تکه خود را انتخاب کند یا فایل را به صورت کلی آپلود کند. این مثال از URI آپلود منحصر به فرد برای صدور PUT resumable استفاده میکند. مثال زیر ۱۰۰۰۰۰ بایت اول از فایل PDF با حجم ۱۲۳۴۵۶۷ بایت را ارسال میکند:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567 bytes 0-99999
اگر اندازه فایل PDF ناشناخته بود، این مثال Content-Range: bytes 0-99999/* استفاده میکرد. اطلاعات بیشتر در مورد سربرگ Content-Range را اینجا بخوانید.
سرور با محدوده بایت فعلی ذخیره شده پاسخ میدهد:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
کلاینت شما باید به PUT هر بخش از فایل ادامه دهد تا کل فایل آپلود شود. تا زمانی که آپلود کامل شود، سرور با خطای HTTP 308 Resume Incomplete و محدوده بایتی که از آن اطلاع دارد در سربرگ Range پاسخ خواهد داد. کلاینتها باید از سربرگ Range برای تعیین محل شروع بخش بعدی استفاده کنند. بنابراین، فرض نکنید که سرور تمام بایتهای ارسالی اولیه در درخواست PUT را دریافت کرده است.
نکته : سرور ممکن است در طول یک تکه، یک URI آپلود منحصر به فرد جدید در هدر Location صادر کند. کلاینت شما باید Location بهروزرسانیشده را بررسی کند و از آن URI برای ارسال تکههای باقیمانده به سرور استفاده کند.
وقتی آپلود کامل شود، پاسخ مشابه زمانی خواهد بود که آپلود با استفاده از مکانیزم آپلود غیرقابل از سرگیری API انجام شده باشد. به عبارت دیگر، کد 201 Created به همراه <atom:entry> که توسط سرور ایجاد شده است، بازگردانده میشود. PUT های بعدی به URI آپلود منحصر به فرد، همان پاسخی را که هنگام اتمام آپلود بازگردانده شده بود، بازمیگردانند. پس از مدتی، پاسخ 410 Gone یا 404 Not Found خواهد بود.
از سرگیری آپلود
اگر درخواست شما قبل از دریافت پاسخ از سرور خاتمه یابد یا اگر پاسخ HTTP 503 از سرور دریافت کنید، میتوانید با ارسال یک درخواست PUT خالی روی URI منحصر به فرد آپلود، وضعیت فعلی آپلود را بررسی کنید.
کلاینت از سرور نظرسنجی میکند تا مشخص کند کدام بایتها را دریافت کرده است:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
اگر طول مشخص نیست، از * به عنوان content_length استفاده کنید.
سرور با محدوده بایت فعلی پاسخ میدهد:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
نکته : اگر سرور هیچ بایتی را برای جلسه ارسال نکرده باشد، هدر Range را حذف خواهد کرد.
نکته : سرور ممکن است در طول یک تکه، یک URI آپلود منحصر به فرد جدید در هدر Location صادر کند. کلاینت شما باید Location بهروزرسانیشده را بررسی کند و از آن URI برای ارسال تکههای باقیمانده به سرور استفاده کند.
در نهایت، کلاینت از جایی که سرور متوقف شده بود، ادامه میدهد:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
لغو آپلود
اگر میخواهید آپلود را لغو کنید و از هرگونه اقدام بعدی روی آن جلوگیری کنید، یک درخواست DELETE روی URI آپلود منحصر به فرد ارسال کنید.
DELETE upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
در صورت موفقیت، سرور پاسخ میدهد که جلسه لغو شده است و با همان کد برای درخواستهای PUT یا وضعیت پرسوجوی بیشتر پاسخ میدهد:
HTTP/1.1 499 Client Closed Request
توجه : اگر آپلودی بدون لغو رها شود، طبیعتاً یک هفته پس از ایجاد منقضی میشود.
بهروزرسانی یک منبع موجود
مشابه شروع یک جلسه آپلود با قابلیت از سرگیری ، میتوانید از پروتکل آپلود با قابلیت از سرگیری برای جایگزینی محتوای یک فایل موجود استفاده کنید. برای شروع یک درخواست بهروزرسانی با قابلیت از سرگیری، یک HTTP PUT به لینک ورودی با rel=' ...#resumable-edit-media ' ارسال کنید. اگر API از بهروزرسانی محتوای منبع پشتیبانی کند، هر entry رسانه حاوی چنین لینکی خواهد بود.
به عنوان مثال، یک ورودی سند در DocList API شامل لینکی مشابه زیر خواهد بود:
<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"/>
بنابراین، درخواست اولیه به این صورت خواهد بود:
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
برای بهروزرسانی همزمان فراداده و محتوای یک منبع، به جای یک بدنه خالی، از Atom XML استفاده کنید. به مثال موجود در بخش «آغاز درخواست آپلود قابل از سرگیری» مراجعه کنید.
وقتی سرور با URI منحصر به فرد آپلود پاسخ میدهد، یک PUT به همراه payload خود ارسال کنید. وقتی URI منحصر به فرد آپلود را داشتید، فرآیند بهروزرسانی محتوای فایل مشابه آپلود یک فایل است.
این مثال خاص، محتوای سند موجود را در یک لحظه بهروزرسانی میکند:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
نمونههای کتابخانه کلاینت
در زیر نمونههایی از آپلود یک فایل فیلم در Google Docs (با استفاده از پروتکل آپلود قابل از سرگیری) در کتابخانههای کلاینت Google Data آورده شده است. توجه داشته باشید که در حال حاضر همه کتابخانهها از ویژگی قابل از سرگیری پشتیبانی نمیکنند.
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
برای مشاهده نمونه کامل و مرجع کد منبع، به منابع زیر مراجعه کنید:
- نمونه برنامه و سورس کتابخانه جاوا
- نمونه برنامه کتابخانه Objective-C
- منبع کتابخانه دات نت