اریک بیدلمن، تیم G Suite APIs
فوریه 2010
معرفی
استانداردهای فعلی وب هیچ مکانیسم قابل اعتمادی برای تسهیل آپلود HTTP فایل های بزرگ ارائه نمی دهند. در نتیجه، آپلود فایل ها در گوگل و سایر سایت ها به طور سنتی به اندازه های متوسط (مثلاً 100 مگابایت) محدود شده است. برای سرویسهایی مانند YouTube و APIهای فهرست اسناد Google که از آپلود فایلهای بزرگ پشتیبانی میکنند، این یک مانع بزرگ است.
پروتکل Google Data Resumable مستقیماً با پشتیبانی از درخواستهای POST/PUT HTTP قابل تجدید در HTTP/1.0 به مشکلات فوقالذکر میپردازد. این پروتکل بر اساس ResumableHttpRequestsProposal پیشنهاد شده توسط تیم Google Gears مدل شده است.
این سند نحوه ادغام ویژگی بارگذاری مجدد Google Data را در برنامه های خود توضیح می دهد. مثالهای زیر از API دادههای فهرست اسناد Google استفاده میکنند. توجه داشته باشید که APIهای Google اضافی که این پروتکل را پیادهسازی میکنند ممکن است نیازمندیها/کدهای پاسخ و غیره کمی متفاوت باشند. لطفاً برای جزئیات بیشتر به اسناد سرویس مراجعه کنید.
پروتکل Resumable
شروع یک درخواست بارگذاری مجدد
برای شروع یک جلسه آپلود مجدد، یک درخواست HTTP 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
شما باید خالی یا حاوی یک ورودی Atom XML باشد و نباید حاوی محتوای واقعی فایل باشد. مثال زیر یک درخواست قابل ازسرگیری برای آپلود یک پی دی اف بزرگ ایجاد می کند و با استفاده از سربرگ 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
می توان حذف کرد.
در اینجا یک نمونه درخواست دیگر است که در عوض یک سند word را آپلود می کند. این بار، ابرداده Atom گنجانده شده است و در ورودی سند نهایی اعمال خواهد شد.
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 جلسه قابل ازسرگیری پس از یک هفته منقضی می شود.
در حال آپلود یک فایل
پروتکل قابل ازسرگیری اجازه می دهد، اما نیازی به آپلود محتوا در «تکه ها» ندارد، زیرا هیچ محدودیت ذاتی در HTTP در اندازه درخواست وجود ندارد. مشتری شما آزاد است که اندازه قطعه خود را انتخاب کند یا فقط فایل را به عنوان یک کل آپلود کند. این مثال از URI آپلود منحصر به فرد برای صدور یک PUT
قابل ازسرگیری استفاده می کند. مثال زیر اولین 100000 بایت فایل PDF 1234567 بایتی را ارسال می کند:
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
هر تکه از فایل تا زمانی که کل فایل آپلود شود، ادامه دهد. تا زمانی که آپلود کامل شود، سرور با 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
به همراه بار ارسال کنید. هنگامی که 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 آمده است. توجه داشته باشید که در حال حاضر همه کتابخانهها از ویژگی resumable پشتیبانی نمیکنند.
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
- منبع کتابخانه دات نت