Eric Bidelman, equipe de APIs do G Suite
fevereiro de 2010
Introdução
Os padrões atuais da Web não oferecem um mecanismo confiável para facilitar o upload por HTTP de arquivos grandes. Como resultado, os uploads de arquivos no Google e em outros sites costumam ser limitados a tamanhos moderados (por exemplo, 100 MB). Para serviços como o YouTube e as APIs da Lista de documentos do Google, que suportam uploads de grandes arquivos, isso apresenta um grande obstáculo.
O protocolo retomável de dados do Google aborda os problemas mencionados acima com suporte às solicitações HTTP/POST HTTP retomáveis em HTTP/1.0. O protocolo foi modelado após o ResumableHttpRequestsProposal, sugerido pela equipe do Google Gear.
Este documento descreve como incorporar o recurso de upload retomável dos dados do Google em seus aplicativos. Os exemplos abaixo usam a API Google Documents List Data. Outras APIs do Google que implementam esse protocolo podem ter requisitos/códigos de resposta um pouco diferentes etc. Consulte a documentação do serviço para ver as especificações.
O protocolo retomável
Como iniciar uma solicitação de upload retomável
Para iniciar uma sessão de upload retomável, envie uma solicitação POST
HTTP ao link de postagem retomável. Esse link está disponível no nível do feed.
O link de postagem retomável da API DocList tem esta aparência:
<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"/>
O corpo da solicitação POST
precisa estar vazio ou conter uma entrada XML Atom e não pode incluir o conteúdo real do arquivo.
O exemplo abaixo cria uma solicitação retomável para fazer upload de um PDF grande e inclui um título para o documento futuro usando o
cabeçalho 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
Os cabeçalhos X-Upload-Content-Type
e X-Upload-Content-Length
precisam ser definidos como
o tipo MIME e o tamanho do arquivo que você vai enviar. Se a duração do conteúdo for desconhecida na
criação da sessão de upload, o cabeçalho X-Upload-Content-Length
poderá ser omitido.
Veja outro exemplo de solicitação que, em vez disso, faz upload de um documento do Word. Dessa vez, os metadados Atom são incluídos e serão aplicados à entrada final do documento.
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>
A resposta do servidor a partir do POST
inicial é um URI de upload exclusivo no cabeçalho Location
e um corpo de resposta vazio:
HTTP/1.1 200 OK
Location: <upload_uri>
O URI de upload exclusivo será usado para fazer upload dos blocos de arquivos.
Observação: a solicitação POST
inicial não cria uma nova entrada no feed.
Isso só acontece quando toda a operação de upload é concluída.
Observação: um URI de sessão retomável expira após uma semana.
Como enviar um arquivo
O protocolo retomável permite, mas não exige, o upload do conteúdo em "blocos", porque não há restrições
inerentes em HTTP nos tamanhos de solicitações. O cliente pode escolher o tamanho do fragmento ou apenas fazer upload do arquivo como um todo.
Este exemplo usa o URI de upload exclusivo para emitir um PUT
retomável. O exemplo a seguir envia os primeiros 100.000
bytes de um arquivo PDF de 1234.567 bytes:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 100000 Content-Range: bytes 0-99999/1234567 bytes 0-99999
Se o tamanho do arquivo PDF for desconhecido, este exemplo usará Content-Range: bytes
0-99999/*
. Leia mais informações sobre o cabeçalho Content-Range
aqui.
O servidor responde com o intervalo de bytes atual que foi armazenado:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-99999
O cliente precisa continuar a usar PUT
em cada fragmento do arquivo até que tenha sido concluído
Até que o upload seja concluído, o servidor responderá com um 308 Resume Incomplete
HTTP e o intervalo de bytes conhecido
no cabeçalho Range
. Os clientes precisam usar o cabeçalho Range
para determinar onde iniciar a próxima parte.
Portanto, não presuma que o servidor recebeu todos os bytes originalmente enviados na solicitação PUT
.
Observação: o servidor pode emitir um novo URI de upload exclusivo no cabeçalho Location
durante uma parte. Seu cliente precisa
verificar um Location
atualizado e usar esse URI para enviar os blocos restantes para o servidor.
Quando o upload for concluído, a resposta será a mesma que se o upload tivesse sido feito usando o mecanismo de upload não retomável da API. Ou seja, um 201 Created
será retornado junto com o <atom:entry>
,
conforme criado pelo servidor. As PUT
s subsequentes ao URI de upload exclusivo retornarão a mesma resposta que foi retornada quando o upload foi concluído.
Após um período, a resposta será 410 Gone
ou 404 Not Found
.
Como retomar um upload
Se a solicitação for encerrada antes de receber uma resposta do servidor ou se você receber uma resposta HTTP 503
, envie uma solicitação PUT
vazia no URI exclusivo do upload.
O cliente pesquisa o servidor para determinar quais bytes ele recebeu:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0 Content-Range: bytes */content_length
Use *
como content_length se o comprimento não for conhecido.
O servidor responde com o intervalo de bytes atual:
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-42
Observação: se o servidor não tiver confirmado nenhum bytes para a sessão, ele omitirá o cabeçalho Range
.
Observação: o servidor pode emitir um novo URI de upload exclusivo no cabeçalho Location
durante uma parte. Seu cliente precisa
verificar um Location
atualizado e usar esse URI para enviar os blocos restantes para o servidor.
Por fim, o cliente retoma de onde o servidor parou:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 57 Content-Range: 43-99/100 <bytes 43-99>
Como cancelar um upload
Se você quiser cancelar o upload e impedir qualquer ação futura nele, emita uma solicitação
DELETE
no URI exclusivo do upload.
DELETE upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 0
Se for bem-sucedido, o servidor vai responder que a sessão foi cancelada e responder com o mesmo código
para mais PUT
s ou solicitações de status de consulta:
HTTP/1.1 499 Client Closed Request
Observação: se um upload for abandonado sem cancelamento, ele vai expirar naturalmente uma semana após a criação.
Como atualizar um recurso atual
Semelhante à inicialização de uma sessão de upload retomável, é possível utilizar
o protocolo de upload retomável para substituir o conteúdo de um arquivo existente. Para iniciar uma solicitação de atualização retomável,
envie um PUT
HTTP para o link da entrada com rel='...#resumable-edit-media
'.
Cada entry
de mídia conterá esse link se a API for compatível com a atualização do conteúdo do recurso.
Por exemplo, a entrada de um documento na API DocList conterá um link semelhante a:
<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"/>
Assim, a solicitação inicial seria:
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
Para atualizar os metadados e o conteúdo de um recurso ao mesmo tempo, inclua o Atom XML em vez de um corpo vazio. Veja o exemplo na seção Como iniciar uma solicitação de upload retomável.
Quando o servidor responder com o URI de upload exclusivo, envie um PUT
com o payload. Com o URI de upload
exclusivo, o processo de atualização do conteúdo do arquivo é o mesmo que o do upload de um arquivo.
Este exemplo específico atualizará o conteúdo do documento de uma só vez:
PUT upload_uri HTTP/1.1 Host: docs.google.com Content-Length: 1000 Content-Range: 0-999/1000 <bytes 0-999>
Exemplos de biblioteca de cliente
Veja abaixo exemplos de upload de um arquivo de filme para o Documentos Google (usando o protocolo de upload retomável) nas bibliotecas de cliente do Google Data. Observe que nem todas as bibliotecas são compatíveis com o recurso retomável no momento.
- Java
- .NET
- Objective-C (link em inglês)
- Python
int MAX_CONCURRENT_UPLOADS = 10; int PROGRESS_UPDATE_INTERVAL = 1000; int DEFAULT_CHUNK_SIZE = 10485760; DocsService client = new DocsService("yourCompany-yourAppName-v1"); client.setUserCredentials("[email protected]", "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, "[email protected]", "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('[email protected]', '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
Para ver exemplos completos e referências de código-fonte, consulte os seguintes recursos:
- App de exemplo e origem da biblioteca Java
- App de exemplo da biblioteca Objective-C
- Fonte da biblioteca .NET