إريك بيدلمان، فريق واجهات برمجة التطبيقات في G Suite
فبراير 2010
مقدمة
لا توفّر معايير الويب الحالية آلية موثوقة لتسهيل تحميل ملفات كبيرة عبر HTTP. نتيجةً لذلك، كان الحدّ الأقصى لحجم الملفات التي يمكن تحميلها على Google والمواقع الإلكترونية الأخرى محدودًا عادةً (مثل 100 ميغابايت). يشكّل ذلك عقبة كبيرة أمام الخدمات التي تتيح تحميل ملفات كبيرة، مثل واجهات برمجة التطبيقات الخاصة بـ YouTube و"قائمة مستندات Google".
يعالج بروتوكول Google Data القابل للاستئناف المشاكل المذكورة أعلاه مباشرةً من خلال إتاحة طلبات HTTP POST/PUT القابلة للاستئناف في HTTP/1.0. تم تصميم البروتوكول وفقًا ResumableHttpRequestsProposal الذي اقترحه فريق Google Gears.
يوضّح هذا المستند كيفية دمج ميزة "التحميل القابل للاستئناف" في Google Data في تطبيقاتك. تستخدِم الأمثلة أدناه Google Documents List Data API. يُرجى العِلم أنّ واجهات برمجة التطبيقات الإضافية من Google التي تنفّذ هذا البروتوكول قد تتضمّن متطلبات أو رموز استجابة أو غير ذلك مختلفة قليلاً. يُرجى الرجوع إلى مستندات الخدمة لمعرفة التفاصيل.
بروتوكول الاستئناف
بدء طلب تحميل قابل للاستئناف
لبدء جلسة تحميل قابلة للاستئناف، أرسِل طلب HTTP POST إلى رابط resumable-post. يمكن العثور على هذا الرابط على مستوى الخلاصة.
يبدو رابط 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، ويجب ألا يتضمّن محتوى الملف الفعلي.
ينشئ المثال أدناه طلبًا قابلاً للاستئناف لتحميل ملف 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 على نوع MIME وحجم الملف الذي سيتم تحميله في النهاية. إذا كان طول المحتوى غير معروف عند إنشاء جلسة التحميل، يمكن حذف العنوان 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>
سيتم استخدام معرّف الموارد المنتظم الفريد للتحميل لتحميل أجزاء الملف.
ملاحظة: لا يؤدي طلب POST الأوّلي إلى إنشاء إدخال جديد في الخلاصة.
لا يحدث ذلك إلا عند اكتمال عملية التحميل بأكملها.
ملاحظة: تنتهي صلاحية معرّف الموارد المنتظم (URI) الخاص بالجلسة القابلة للاستئناف بعد أسبوع واحد.
تحميل ملف
يسمح البروتوكول القابل للاستئناف بتحميل المحتوى على شكل "أجزاء"، ولكنّه لا يشترط ذلك، لأنّه لا توجد قيود مضمّنة في HTTP على أحجام الطلبات. يمكن للعميل اختيار حجم الجزء أو تحميل الملف بأكمله.
يستخدم هذا المثال معرّف الموارد المنتظم الفريد للتحميل لإصدار 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 كل جزء من الملف إلى أن يتم تحميل الملف بأكمله.
إلى أن يكتمل التحميل، سيردّ الخادم برمز HTTP 308 Resume Incomplete ونطاق البايتات الذي يعرفه في العنوان Range. يجب أن يستخدم العملاء العنوان Range لتحديد مكان بدء الجزء التالي.
لذلك، لا تفترض أنّ الخادم تلقّى جميع وحدات البايت التي تم إرسالها في الأصل في طلب PUT.
ملاحظة: قد يصدر الخادم عنوان URI جديدًا وفريدًا للتحميل في العنوان Location أثناء تحميل جزء. على العميل التحقّق من وجود Location معدَّل واستخدام معرّف الموارد المنتظم هذا لإرسال الأجزاء المتبقية إلى الخادم.
عند اكتمال عملية التحميل، سيكون الردّ مماثلاً للردّ الذي يتم تلقّيه عند إجراء عملية التحميل باستخدام أسلوب التحميل غير القابل للاستئناف في واجهة برمجة التطبيقات. وهذا يعني أنّه سيتم عرض 201 Created مع <atom:entry>،
كما أنشأه الخادم. ستعرض طلبات PUT اللاحقة إلى معرّف الموارد الموحّد الفريد للتحميل الاستجابة نفسها التي تم عرضها عند اكتمال عملية التحميل.
بعد فترة من الوقت، ستكون الاستجابة 410 Gone أو 404 Not Found.
استئناف عملية تحميل
إذا تم إنهاء طلبك قبل تلقّي ردّ من الخادم أو إذا تلقّيت الردّ 503 من الخادم، يمكنك الاستعلام عن الحالة الحالية لعملية التحميل من خلال إرسال طلب PUT فارغ إلى معرّف الموارد المنتظم الفريد الخاص بعملية التحميل.
يطلب العميل من الخادم بيانات لتحديد عدد البايتات التي تلقّاها:
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 معدَّل واستخدام معرّف الموارد المنتظم هذا لإرسال الأجزاء المتبقية إلى الخادم.
أخيرًا، يستأنف العميل من حيث توقّف الخادم:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
إلغاء عملية تحميل
إذا أردت إلغاء عملية التحميل ومنع اتّخاذ أي إجراء آخر بشأنها، أرسِل طلب DELETE إلى معرّف الموارد المنتظم الفريد الخاص بعملية التحميل.
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'.
سيحتوي كل 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" (باستخدام بروتوكول التحميل القابل للاستئناف) في مكتبات برامج 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
للاطّلاع على أمثلة كاملة ومرجع الرمز المصدر، يُرجى الرجوع إلى المراجع التالية:
- تطبيق نموذجي ومصدر لمكتبة Java
- تطبيق نموذجي لمكتبة Objective-C
- المصدر لمكتبة .NET