I am attempting to upload a file from an HTML5 file dialog to my Google Docs account.
I am using the Google Documents List API version 3.0 for this request, and the instructions at this link.
I gather the correct resumable-create-link URL from the Documents List JSON after a query, and POST an XML entity to that link to obtain the location URL. I get a 200 OK response and the location URL.
After setting the headers, I make a PUT request to the location URL to upload the file. For the purposes of this question, the file is smaller than 512kb, and does not need to be chunked.
After submitting this PUT request, I receive a 400 Bad Request (sometimes 400 Invalid Request) response and the file is not uploaded.
Interestingly, if I omit the Content-Range header, I can upload a small file, but files other than text files are corrupted.
I have done a binary compare of the file loaded in memory and the local file. It appears that they match.
To access the API, I am using Javascript inside of a Google Gadget with http://www.google.com/jsapi.
I GET, POST, and PUT data using gadgets.io.makeRequest. My authorization header and token seem correct given that I am able to query for a list of documents with no issue.
What steps should I take to resolve this issue?
Sample Output
PUT /feeds/upload/create-session/default/private/full?v=3.0&convert=false&upload_id=[id]
Host: docs.google.com
X-Shindig-AuthType: oauth
Authorization: OAuth oauth_body_hash="x", opensocial_owner_id="x", opensocial_viewer_id="x", opensocial_app_id="x", opensocial_app_url="x", xoauth_signature_publickey="x", xoauth_public_key="x", oauth_version="1.0", oauth_timestamp="x", oauth_nonce="x", opensocial_container="x", oauth_token="x", oauth_consumer_key="x", oauth_signature_method="RSA-SHA1", oauth_signature="x"
Content-Length: 433
Content-Range: bytes 0-433/433
Content-Type: application/x-javascript
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79 Safari/535.11,gzip(gfe)
X-Forwarded-For: 74.203.139.139
X-shindig-dos: on
X-Upload-Content-Length: 433
X-Upload-Content-Type: application/x-javascript
[bytes 0-433]
==== Received response 1:
HTTP/1.1 400
Cache-Control: no-cache, no-store, must-revalidate
Content-Length: 15
Content-Type: text/html; charset=UTF-8
Date: Thu, 05 Apr 2012 19:49:48 GMT
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Server: HTTP Upload Server Built on Apr 2 2012 11:47:06 (1333392426)
Via: HTTP/1.1 GWA
X-Google-Cache-Control: remote-fetch
Invalid Request
====
errors: 400 Error
Edit
Binary code handling data:
// -- File Upload functions --
function constructContent(docTitle) {
var atom = ['<?xml version=\"1.0\" encoding=\"UTF-8"?>',
'<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:docs=\"http://schemas.google.com/docs/2007\">',
'<title>' + docTitle + '</title>',
'</entry>'].join('');
return atom;
}
function writeFile(file, under512) {
var body = constructContent(file.name.toString());
var params = {};
console.log(body);
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
params[gadgets.io.RequestParameters.POST_DATA] = body;
params[gadgets.io.RequestParameters.HEADERS] = {
"Content-Type" : "application/atom+xml",
"Content-Length" : "359",
"X-Upload-Content-Type" : file.type.toString(),
"X-Upload-Content-Length" : file.size.toString()
};
var data;
submitRequest(resumableLink + '&convert=false', params, function(requestSuceeded, data) {
if ( data.rc == 200 ) {
console.log(data);
console.log(data.headers.location[0].toString());
if ( under512 == true ) {
continueFile(data.headers.location[0].toString(), file, ('0-' + file.size + '/' + file.size).toString(), under512, params);
}
else {
continueFile(data.headers.location[0].toString(), file, ('0-524287/' + file.size).toString(), under512, params);
}
}
});
}
// recursive
function continueFile(location, file, contentRange, under512) {
console.log('location: ' + location);
var contentLength = "0";
var reader = new FileReader();
var blob;
var start = contentRange.split('-')[0].toString();
var stop = contentRange.split('-')[1].split('/')[0].toString();
console.log(file.size);
console.log(file.type);
console.log('start: ' + start);
console.log('stop: ' + stop);
console.log(("bytes " + contentRange).toString());
console.log(contentRange);
( under512 == true ) ? contentLength = contentRange.split('/')[1].toString() : contentLength = "524288";
if ( file.webkitSlice ) {
blob = file.webkitSlice(start, stop+1);
}
else {
blob = file.mozSlice(start, stop+1);
}
reader.readAsBinaryString(blob);
reader.onloadend = function(evt) {
if ( evt.target.readyState == FileReader.DONE ) {
console.log(evt.target.result);
var b = showResult(reader);
// console.log('binary: ' + b);
var params = {};
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.PUT;
params[gadgets.io.RequestParameters.POST_DATA] = evt.target.result;
params[gadgets.io.RequestParameters.HEADERS] = {
"Content-Length" : (contentLength).toString(),
"Content-Type" : file.type.toString(),
"Content-Range" : ("bytes " + contentRange).toString().trim()
// "GData-Version" : "3.0"
};
submitRequest(location.toString().trim(), params, function(requestSucceeded, data) {
if ( data.rc == 308 ) {
var newStart = start + 524288;
var newEnd;
( end + 524288 > file.size ) ? newEnd = file.size : newEnd = end + 524288;
var range = (newStart + '-' + newEnd + '/' + file.size).toString().trim();
continueFile(data.headers.location.toString().trim(), file, range, under512);
}
else if ( data.rc == 201 ) {
console.log('done!');
}
else {
console.log('terrible error.');
console.log(data);
writeObj(data);
}
});
}
};
}
function uploadFile() {
var file = document.getElementById('uploads').files[0];
var under512 = false;
( file.size < 524287 ) ? under512 = true : under512 = false;
writeFile(file, under512);
}
As described in the docs the continuation requests don't have the X-Upload-Content-* headers, and should look more like:
PUT [next location]
Content-Length: 524288
Content-Type: application/pdf
Content-Range: bytes 0-524287/1073741824
Additionally, in your log 0-433 is 434 bytes, not 433.