การอัปโหลดสื่อที่กลับมาทํางานอีกครั้งในโปรโตคอลข้อมูลของ Google

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

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

บทนำ

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

โปรโตคอลที่กลับมาทํางานอีกครั้งของ Google จะช่วยแก้ปัญหาที่กล่าวถึงข้างต้นได้โดยตรงด้วยการรองรับคําขอ HTTP POST/PUT ที่กลับมาทํางานอีกครั้งใน HTTP/1.0 โปรโตคอลนี้สร้างโมเดลโดยอิงตาม ResumableHttpRequestsOffer ที่ทีม Googleเฟืองแนะนํา

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

โปรโตคอลที่กลับมาทํางานอีกครั้งได้

การเริ่มต้นคําขออัปโหลดที่กลับมาทํางานอีกครั้ง

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

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

โปรโตคอลที่กลับมาทํางานต่อได้ (แต่ไม่บังคับ) ให้อัปโหลดเนื้อหาใน "กลุ่ม" เนื่องจากไม่มี HTTP เกี่ยวกับขนาดคําขอ ลูกค้าของคุณสามารถเลือกขนาดไฟล์เป็นส่วนๆ หรือเพียงแค่อัปโหลดไฟล์ทั้งหมดก็ได้ ตัวอย่างนี้ใช้ URI การอัปโหลดที่ไม่ซ้ํากันเพื่อออก PUT กลับมาทํางานอีกครั้ง ตัวอย่างต่อไปนี้จะส่งไฟล์ 1234567 ไบต์ PDF ขนาด 100,000 ไบต์แรก

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
// 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

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

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