تحميلات الوسائط القابلة للاستئناف في بروتوكول بيانات Google

إريك بيدلمان، فريق واجهات برمجة تطبيقات G Suite
شباط (فبراير) 2010

  1. المقدمة
  2. البروتوكول القابل للاستئناف
    1. بدء طلب تحميل قابل للاستئناف
    2. تحميل ملف
    3. استئناف عملية تحميل
    4. إلغاء عملية تحميل
    5. تعديل مورد حالي
  3. أمثلة على مكتبة العملاء

المقدمة

لا توفر معايير الويب الحالية أي آلية موثوقة لتسهيل تحميل HTTP على الملفات الكبيرة. ونتيجةً لذلك، كانت عمليات تحميل الملفات في Google والمواقع الأخرى تقتصر عادةً على أحجام متوسطة (على سبيل المثال 100 ميغابايت). بالنسبة إلى خدمات مثل YouTube وواجهات برمجة التطبيقات لقائمة مستندات Google التي تدعم تحميلات الملفات الكبيرة، يمثل هذا عقبة رئيسية.

يعالج بروتوكول Google Data القابل للاستئناف مباشرة المشاكل المذكورة أعلاه من خلال دعم طلبات HTTP POST/PUT القابلة للاستئناف في HTTP/1.0. تم وضع نموذج البروتوكول وفق ResumableHttpRequestsProposal الذي اقترحه فريق Google Gears.

يصف هذا المستند كيفية دمج ميزة التحميل القابل للاستئناف لبيانات Google في تطبيقاتك. تستخدم الأمثلة أدناه واجهة برمجة التطبيقات لبيانات قائمة مستندات Google. لاحظ أن متطلبات Google APIs الإضافية التي تنفّذ هذا البروتوكول قد تختلف قليلاً في المتطلبات/رموز الاستجابة وما إلى ذلك. يُرجى الرجوع إلى وثائق الخدمة لمعرفة التفاصيل.

البروتوكول القابل للاستئناف

جارٍ بدء طلب تحميل قابل للاستئناف

لبدء جلسة تحميل قابلة للاستئناف، أرسِل طلب HTTP 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.

إليك مثال آخر على الطلب الذي يحمّل مستند كلمة بدلاً من ذلك. هذه المرة، يتم تضمين البيانات الوصفية 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 كل جزء من الملف حتى يتم تحميل الملف بأكمله. وإلى أن يكتمل التحميل، سيستجيب الخادم مع HTTP 308 Resume Incomplete ونطاق البايت الذي يعرفه في العنوان Range. يجب أن يستخدم العملاء الرأس Range لتحديد مكان بدء المجموعة التالية. لذلك، لا تفترض أن الخادم قد استلم جميع وحدات البايت المرسلة أصلاً في طلب PUT.

ملاحظة: قد يصدر الخادم عنوان URL فريدًا جديدًا للتحميل في عنوان Location أثناء إحدى المجموعات. يجب أن يتحقّق برنامجك من وجود Location محدّث وأن يستخدم معرّف الموارد المنتظم هذا لإرسال الأجزاء المتبقية إلى الخادم.

عند اكتمال التحميل، ستكون الاستجابة مماثلة تمامًا لما إذا تم التحميل باستخدام آلية التحميل غير القابلة للاستئناف في واجهة برمجة التطبيقات. بتعبير آخر، سيتم عرض 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.

ملاحظة: قد يصدر الخادم عنوان URL فريدًا جديدًا للتحميل في عنوان Location أثناء إحدى المجموعات. يجب أن يتحقّق برنامجك من وجود Location محدّث وأن يستخدم معرّف الموارد المنتظم هذا لإرسال الأجزاء المتبقية إلى الخادم.

وأخيرًا، يستأنف العميل من حيث توقف الخادم:

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'. ستحتوي كل وسائط entry على هذا الرابط إذا كانت واجهة برمجة التطبيقات تتيح تحديث محتوى المورد.

على سبيل المثال، سيتضمن إدخال مستند في واجهة برمجة تطبيقات DocList رابطًا مشابهًا لما يلي:

<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. تجدر الإشارة إلى أن الميزة لا يمكن استئنافها في بعض المكتبات حاليًا.

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

للاطلاع على نماذج كاملة ومرجع شفرة المصدر، راجع الموارد التالية:

الرجوع إلى الأعلى