การอัปโหลดสื่อที่สามารถดำเนินการต่อได้ใน Google Data Protocol

Eric Bidelman ทีม G Suite API
กุมภาพันธ์ 2010

  1. บทนำ
  2. โปรโตคอลที่สามารถดำเนินการต่อได้
    1. การเริ่มต้นคำขออัปโหลดต่อได้
    2. การอัปโหลดไฟล์
    3. การอัปโหลดต่อ
    4. การยกเลิกการอัปโหลด
    5. การอัปเดตทรัพยากรที่มีอยู่
  3. ตัวอย่างไลบรารีของไคลเอ็นต์

บทนำ

มาตรฐานเว็บปัจจุบันไม่มีกลไกที่เชื่อถือได้เพื่ออำนวยความสะดวกในการอัปโหลดไฟล์ขนาดใหญ่ผ่าน HTTP ด้วยเหตุนี้ การอัปโหลดไฟล์ที่ Google และเว็บไซต์อื่นๆ จึงมักจำกัดขนาดไว้ที่ปานกลาง (เช่น 100 MB) สำหรับบริการต่างๆ เช่น YouTube และ Google Documents List API ที่รองรับการอัปโหลดไฟล์ขนาดใหญ่ ปัญหานี้ถือเป็นอุปสรรคสำคัญ

โปรโตคอลการอัปโหลดต่อของ Google Data แก้ปัญหาที่กล่าวถึงข้างต้นได้โดยตรงด้วยการรองรับคำขอ HTTP แบบ POST/PUT ที่อัปโหลดต่อได้ใน HTTP/1.0 โปรโตคอลนี้สร้างขึ้นตามResumableHttpRequestsProposal ที่ทีม Google Gears แนะนำ

เอกสารนี้อธิบายวิธีรวมฟีเจอร์การอัปโหลดต่อของ Google Data ไว้ในแอปพลิเคชัน ตัวอย่างด้านล่างใช้ Google Documents List Data API โปรดทราบว่า Google API อื่นๆ ที่ใช้โปรโตคอลนี้อาจมีข้อกำหนด/รหัสการตอบกลับ/ฯลฯ ที่แตกต่างกันเล็กน้อย โปรดดูรายละเอียดในเอกสารประกอบของบริการ

โปรโตคอลการอัปโหลดต่อ

การเริ่มคำขออัปโหลดต่อได้

หากต้องการเริ่มเซสชันการอัปโหลดต่อได้ ให้ส่งคำขอ 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 และต้องไม่มีเนื้อหาไฟล์จริง ตัวอย่างด้านล่างสร้างคำขอที่อัปโหลดต่อได้เพื่ออัปโหลด 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>

ระบบจะใช้ URI การอัปโหลดที่ไม่ซ้ำกันเพื่ออัปโหลดกลุ่มไฟล์

หมายเหตุ: POST คำขอเริ่มต้นจะไม่สร้างรายการใหม่ในฟีด ซึ่งจะเกิดขึ้นเฉพาะเมื่อการดำเนินการอัปโหลดทั้งหมดเสร็จสมบูรณ์แล้วเท่านั้น

หมายเหตุ: URI ของเซสชันที่สามารถกลับมาทำงานต่อได้จะหมดอายุหลังจากผ่านไป 1 สัปดาห์

การอัปโหลดไฟล์

โปรโตคอลที่อัปโหลดต่อได้อนุญาตให้ (แต่ไม่บังคับ) อัปโหลดเนื้อหาเป็น "ก้อน" เนื่องจาก 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

หมายเหตุ: เซิร์ฟเวอร์อาจออก 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

หมายเหตุ: หากคุณละทิ้งการอัปโหลดโดยไม่ยกเลิก การอัปโหลดจะหมดอายุโดยอัตโนมัติหลังจากสร้าง 1 สัปดาห์

การอัปเดตทรัพยากรที่มีอยู่

คุณสามารถใช้โปรโตคอลการอัปโหลดต่อได้เพื่อแทนที่เนื้อหาของไฟล์ที่มีอยู่ได้เช่นเดียวกับการเริ่มเซสชันการอัปโหลดต่อได้ หากต้องการเริ่มคำขออัปเดตที่สามารถดำเนินการต่อได้ ให้ส่ง HTTP PUT ไปยังลิงก์ของรายการที่มี rel='...#resumable-edit-media' สื่อแต่ละรายการ entry จะมีลิงก์ดังกล่าวหาก API รองรับการอัปเดตเนื้อหาของทรัพยากร

ตัวอย่างเช่น รายการเอกสารใน 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
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

ดูตัวอย่างที่สมบูรณ์และการอ้างอิงซอร์สโค้ดได้จากแหล่งข้อมูลต่อไปนี้

กลับไปด้านบน