1

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Teemu Terho
  • 306
  • 4
  • 13
  • There should be some way to disable request validation. If you stop doing validating request. it won't check for content length. – AZ_ Jul 14 '11 at 12:22
  • Algo, I tried to figure out a way to disable request validation, but was not able to find one. The validation is done on Google's end, so I really don't have control over the validation. If there is some way around that you know of, please inform me since that could enable a working application. Even though it would be a somewhat "dirty" way of fixing this, I'd be glad to use it for now. – Teemu Terho Jul 14 '11 at 15:15
  • I think you should manually set some content length. Or please go to google forums and report is as bug or anything I am sure you will get reply soon. and please share your solution with me too. – AZ_ Jul 15 '11 at 04:23
  • I don't understand what you mean by "manually setting" content-length. The most manual way I know of is what I am doing now (hard-coding the length to be 0). I've also tried to set some content (one JSON key-value pair) and reading the content-length from that using both defaultheaders and googleheaders. No success. So can you please clarify what you mean by "manually setting" content-length if it is not the method I am using. To report this as a bug I would like to hear more opinions if I am doing this the right way or not, and whether to report this as a bug with Android or GAJC. – Teemu Terho Jul 15 '11 at 08:15
  • I can't say anything without looking at your code. paste some parts of your code. – AZ_ Jul 15 '11 at 11:10
  • I have no idea what part of the code other than the ones already posted would be helpful in analyzing this. If you suspect a defect in some of the mentioned libraries for Http or my usage of them, I'll be happy to post the part. But pasting "some parts" of the code would be more like shooting in the dark in my opinion. – Teemu Terho Jul 15 '11 at 11:54
  • No idea about your problem dude, but I have solved one of mine using HTTP Client for android. I was previously using URLConnection. – AZ_ Jul 19 '11 at 05:24
  • Okay good to know, I have noticed some other people also fixing strange problems by changing the libraries they use for HTTP on Android. That is probably the next step for me too if I'm unable to find a fix . – Teemu Terho Jul 19 '11 at 07:14
  • I will recommend you to use http://developer.android.com/reference/org/apache/http/client/HttpClient.html – AZ_ Jul 19 '11 at 08:47

2 Answers2

3

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.

sjngm
  • 12,423
  • 14
  • 84
  • 114
pbk
  • 46
  • 1
  • I will be testing this tomorrow. I have actually seen that recommendation somewhere, but in my descent to madness trying to solve this I haven't been able to connect the dots. I'll report the results and accept if this is the solution. – Teemu Terho Jul 19 '11 at 17:55
  • This was the correct solution. There are still complications: on Gingerbread and higher, the content-length must be set in the code, on lower versions it must not be set or there is a "multiple headers of the same name" -error. AndroidHttp.isGingerbreadOrHigher() makes this simple enough to fix. Also, for some reason the slugs are failing on all versions, but hopefully it is an easy fix. At least the original problem is now solved. Thank you very much pbk (and @ddewaele). – Teemu Terho Jul 20 '11 at 08:13
0

You could try using the ApacheHttpTransport in an Android environment instead of the NetHttpTransport. There are several issues with the NetHttpTransport, including the one you encountered here, especially pre 2.3.

According to the javadocs :

Starting with SDK 2.3, strongly recommended to use com.google.api.client.javanet.NetHttpTransport. Their Apache HTTP Client implementation is not as well maintained.

For SDK 2.2 and earlier, use com.google.api.client.apache.ApacheHttpTransport com.google.api.client.javanet.NetHttpTransport is not recommended due to some bugs in the Android SDK implementation of HttpURLConnection.

I've been using the ApacheHttpTransport in an Android 2.3 environment without any issues. If you want to support multiple API levels AND want to follow the guidelines in the javadocs, you'll need to implement some kind of factory capable of delivering the correct transport based on the API level where your code is running.

ddewaele
  • 22,363
  • 10
  • 69
  • 82