Search code examples
androidgoogle-apigoogle-docs-apifragmentationgoogle-api-java-client

"411 Length required"-response from Google Docs Api using Android API 10 and below


I am developing an application for Android devices, and one part of it is enabling downloading and uploading between the users Google Docs and the device storage. The problem that I have is I'm getting different behavior between different versions of Android API. I've been doing most of the development on API lvl 10 (Android 2.3.3). No problems on a virtual device (I don't have a real device to test with this API lvl or higher). On device and emulator of API lvl 8 (2.2.x) and below I run into a 411 Length required error from the Google Docs Api when requesting to start a resumable upload session. This does not happen when running the same application on emulator of API lvl 10.

I am developing with Eclipse and using Google API Java Client 1.4.1-beta to communicate with Docs Api. The documentation I follow for Google Docs API lies here: http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html

According to said documentation, to start a resumable upload session one is to send an empty POST request. For the "root" of a users Google Docs the address is "https://docs.google.com/feeds/upload/create-session/default/private/full" . This is how i set the headers for the (empty-bodied) request:

        GoogleHeaders headers = new GoogleHeaders();

        headers.contentLength = "0";
        headers.gdataVersion = "3";
        headers.setGoogleLogin(authToken);                  
        //headers.contentType = getMimeType(file);
        headers.setSlugFromFileName(file.getName());
        headers.setApplicationName("test");
        headers.set("X-Upload-Content-Length", file.length());
        headers.set("X-Upload-Content-Type", getMimeType(file));

        request.headers = headers;

I should also mention that the libraries that I use for HTTP are the following:

com.google.api.client.googleapis.GoogleHeaders;
com.google.api.client.http.HttpRequest;
com.google.api.client.http.HttpRequestFactory;
com.google.api.client.http.HttpRequestInitializer;
com.google.api.client.http.HttpTransport;
com.google.api.client.http.HttpResponse;
com.google.api.client.http.HttpContent;
com.google.api.client.http.javanet.NetHttpTransport;

I did some packet sniffing to actually see the headers, and lo and behold, on different versions of Android the headers are actually not set the same way. If you noticed, by default the request is supposed to be sent using https, so I changed it to use http to see the headers from the packet. Here are the results:

Using emulator of Android API lvl 10:

POST /feeds/upload/create-session/default/private/full?convert=false HTTP/1.1
Accept-Encoding: gzip
Authorization: **REMOVED**
Content-Length: 0
Content-Type: text/plain
GData-Version: 3
Slug: 5mbfile.txt
User-Agent: test Google-API-Java-Client/1.4.1-beta
X-Upload-Content-Length: 5242880
X-Upload-Content-Type: text/plain
Host: docs.google.com
Connection: Keep-Alive

Using emulator of API lvl 7:

POST /feeds/upload/create-session/default/private/full?convert=false HTTP/1.1
accept-encoding: gzip
authorization: **REMOVED**
content-type: text/plain
gdata-version: 3
slug: 5mbfile.txt
user-agent: test Google-API-Java-Client/1.4.1-beta
x-upload-content-length: 5242880
x-upload-content-type: text/plain
Host: docs.google.com
Connection: Keep-Alive

Notice the missing content-length header, also the case is different. This explains why I get the 411 response, but how to solve this? Obviously my aim is to get the same behavior on all devices (excluding ones with Android 1.x for reasons that are not relevant to this problem), preferably not using version specific code.

I honestly can't think of many suitable solutions I could apply in my code. The only one I could think of:

transport = new NetHttpTransport();
transport.defaultHeaders.contentLength = Integer.toString(0);

Setting the headers differently with a (deprecated) method in the API, to no avail. The actual headers in the requests are still the same.

Setting the property with "0" or Integer.toString(0) makes no difference either (obviously, I am getting a bit desperate here).

So any help or suggestions that aim in finding a solution are very welcome. I will provide more code if specifically requested and also packet sniffing is possible to test different solutions. There is also the high possibility that this is a bug in the Google Api Java Client or Android. But which one? If no solution is found, I don't have a deep enough knowledge of Android to figure out where to report this (suspected) bug. So if you suspect that the culprit is indeed not my code, then share your thought on which component is causing the headers to be set in a different way.

Edit - Algo asked for the stack trace, here it is: http://pastebin.com/yDCCLB2P

Edit - I was setting a mimetype for the content, although the body is empty. I tried removing the content-type header, no improvement. The line is now commented out in the code above.

Edit - I've been trying to troubleshoot this more. This is the stack trace when trying to set the headers in two different ways in the same code: http://pastebin.com/NkmFjYB3

headers.contentLength = "0";
headers.set("Content-length", "0");

When used at the same time, they collide with each other. Removing either one will produce similar results as before (411 Length Required from Docs and in the request the content-length is absent). A collision doesn't happen (or does not show up in stack trace) when using one of these and the deprecated way ( transport.defaultHeaders.contentLength = "0"; ) to create the request. With any combination of any of the aforementioned methods, either there is a dual-header collision or the request doesn't have the content-length.


Solution

  • Use this instead:

    HttpTransport transport = AndroidHttp.newCompatibleTransport();
    

    That's what Google recommends for compatibility with all API levels as it picks the right implementation based on the version of the API. You don't have to implement this yourself. I've tested it - works for both 2.2 and 2.3.