Tuesday, June 2, 2009

Android Multipart upload

So there were many posts about multipart upload in the Android Google Groups:
One guy rolled his own using HttpURLConnection, and another which strangely relied on dependencies from two different versions of the Apache HttpClient project (commons-httpclient.jar an httpcomponents-client-4.0-alpha4.); since version 3 of Apache's HttpClient was still a commons project, and the latter dependency is a version 4 alpha. The sample code contained no import statements, so it was difficult to know which version of the library was being used for what.

My confusion was deepened when I found a discussion in an Apache forum suggesting that the multipart support had not been included in the move from version 3 to version 4. There was yet another Google Groups discussion on the multipart upload using a different set of Apache libraries. I also found a discussion of the relative merits of using HttpClient versus UrlConnection in Android. I had heard before that some people have experienced odd issues with UrlConnection, which is why I switched over to HttpClient a while back (actually using an AndroidHttpClient class from the zxing project). It is not clear if the URLConnection issues have been resolved with latest code release. Either which way it is somewhat confusing that while there is some of the Apache HttpClient code in Android by default, it is not all there, or at least some of the latest parts of the library are not.

I ended up downloading the latest version of the Android HttpClient library which appeared to be version 4 beta 2 and was pleasantly surprised to find a code sample for a multi-part upload, despite the suggestion I had found that multi-part uploads had not been ported into this version. I modified this example slightly for Android and I was even more pleasantly surprised when it worked (abridged code below):

  
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("http://api.whatever.com/something");
FileBody bin = new FileBody(file);

StringBody media_entity_part = new StringBody(media_entity);

MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("bin", bin);
reqEntity.addPart("media_entity", media_entity_part);

post.setEntity(reqEntity); HttpResponse response = client.execute(post);

HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
resEntity.consumeContent();
}

The dependencies I had were the jar files that came with the latest HttpClient download:

apache-mime4j-0.5.jar
httpclient-4.0-beta2.jar
httpcore-4.0-beta3.jar
httpmime-4.0-beta2.jar

Of course I may have some overlap there too, but when it starts working you don't ask questions - at least while there doesn't seem to be a critical space issue for my android project at present.

Two feelings I come away with are:

1. Is the StringBody, FileBody class really necessary? Couldn't we just have reqEntity.addFilePart and reqEntity.addStringPart and avoid the extra line and class definitions? Seems like that just doesn't need to be seen by the API user.
2. The discussion of all this multi-part upload stuff is really scattered over the google groups for Android. I can't help but think they might get better convergence if the discussion was organised about the API itself, e.g. that questions about multi-part uploads all appeared linked with the Android docs: http://developer.android.com/reference/org/apache/http/client/HttpClient.html
MySQL, PHP and others have great embedded discussions in their docs - I don't know why Java and Ruby projects can't have similar ...

27 comments:

Andy Wolpert said...

I have written a simple Android app based on this code, and almost have it working but when I execute my HTTPPost method I get a 403 / forbidden result.

How can I diagnose this issue? Is my web hoster (GoDaddy saying HTTP post is not allowed?).

Sam Joseph said...

Hi Andy,

403 Forbidden is an http status code:

http://en.wikipedia.org/wiki/HTTP_403

You do need to have something running on the server side to receive the data you are sending and do something with it; and that script needs to have appropriate permissions. It is also possible your service provider blocks certain activity.

You may also need some form of authorization:

http://en.wikipedia.org/wiki/Basic_access_authentication

which I achieve like this:

HttpPost post = new HttpPost("http://you.com/whatever.cgi");

String auth = "username:passowrd";
byte[] bytes = auth.getBytes();
post.setHeader("Authorization", "Basic " + new String(Base64.encodeBase64(bytes)));

and you'll need commons-codec-1.3.jar in your build config for the Base64 encoding.

Best of luck
CHEERS> SAM

p.s. would be good if this post and comments were automatically in android google groups and even linked to from the Google documentation - at the moment there doesn't seem to be any way to avoid multiple diverging conversations ...

Klondike said...

Thank you so much for posting this, I'm trying to do this and I don't understand why it's so hard!

So in this example, what's the "media_entity" and what's its relation to "file"?

Sam Joseph said...

Hi Klondike,

In this example 'media_entity' is just a string like "image/jpg" or something. It's the String component of the multipart upload that consists of one String and one File.

Sorry for any confusion and good luck.

Austin H said...

Where exactly can I find these .jar files? I'm running Windows so getting the source and compiling it isn't exactly the easiest thing to do (seemingly).

Sam Joseph said...

They are from the apache HTTPClient project:

http://hc.apache.org/httpclient-3.x/

Paul Neve said...

Just wanted to thank you for this. I didn't want this for an Android app at all (although I have done Android hacking - those of you who use Donut custom ROMs are almost certainly using my BetterBrowser :) but it seems this is the only resource on the intertubes that gives a simple code example for doing multipart posts in httpclient 4.x. It beggars belief that all the resources, even from Apache themselves, suggest it's not possible without a huge amount of faffing about.

Unknown said...

According to internet note, you can't use localhost or 127.0.0.1 on Android Emulator or you get a connection refused.

Also, how do you pass in arguments using this stuff?

mainu said...

Hi Sam,
I am doing file uploading in the server for my application. Your tutorial becomes very help full for me.Thanks.
Now I faced the problem for the .jar files. I am using android sdk2.1 in windows with eclipse(Ganymede).
There is no error in compilation and execution. But the file is not viewed in the server.At the debugging the log shows the following error::
eclipse.buildId=M20080911-1700
java.version=1.6.0_06
java.vendor=Sun Microsystems Inc.
BootLoader constants: OS=win32, ARCH=x86, WS=win32, NL=en_US
Command-line arguments: -os win32 -ws win32 -arch x86


"Error
Unknown javadoc format for append(char[]) [in StringBuffer [in StringBuffer.class [in java.lang [in C:\android-sdk-windows-1.5_r3\android-sdk-windows-1.5_r3\platforms\android-2.1\android.jar]]]]"
please help..

mainu said...

Hi Sam

I am able to overcome the problem.

Thanks

Unknown said...
This comment has been removed by the author.
Unknown said...
This comment has been removed by the author.
Unknown said...

hi ,

please mainu or any body if you can tel me how you 're

resolving this problem ,because i try the multipart uoload without errors but is not arrived to the destination

Unknown said...
This comment has been removed by the author.
mainu said...

Hi if5_2009_20010
To upload the file in the server a php file must be created in the server.Just check it, because it might be already created there.U can find the format in developers blogs.
I can give you a link:http://www.mail-archive.com/android-beginners@googlegroups.com/msg17389.html
.Now here the the variable name given for the uploaded file in the serever must be same with the variable u used in the line.. dos.writeBytes("Content-Disposition: form-data; name=\"userfile\";filename=\"" + exsistingFileName +"\"" + lineEnd);
Here I change the file name as "userfile\" because this is used in my php file. Thats enough.Try it.

Best of luck

Unknown said...

thanks for your answer mainu ,it's clear for me now,
but can i creat the file in the serveur using JSp or servlet because i have not practising php language well.

Unknown said...

I already develop a servlet class that receives the image
but nothing sent by using this technique (multipart upload)
and with other (BasicNameValuePair) it works.
i don't know the raisons

mainu said...

hi if5_2009_20010,
Yes u can. The main issue is that u must kept a file in the server to receive your file.
Thats all.
Thanks

Simon said...

This was incredibly useful, thanks a lot!

Simon said...

This was incredibly useful, thanks a lot!

Priyanka said...

Hi,

For some reason I get a red wiggly line under my httpPost(url);
says there is no constructor to support this.How can I get around this.
plz help

William Arias said...

it works fine... thanks a lot!!!

Sam Joseph said...

Hi...
Thank you very much for the post. I surfed in net and got to see so many blogs but none were as simple and easy as yours. Great work... Again thanks ..

For those of you who are struggling to find the proper jars, here is the link: http://www.findjar.com/index.x?query=apache-mime4j-0.5.jar

Posted by Santhosh Horatti to Sam's Technical Blog at June 1, 2011 3:04 AM -

I accidentally deleted this when trying to publish it ... Damn iPhone links ...

abc said...

can you show an server side code that save the data?

I am actually using a asp.net/C# web service to receive the file, but don't know how.

Unknown said...

Hi Sam,

I am getting "HTTP/1.1 415 Unsupported Media Type" in response,

please suggest me to solve this thing.

Thanks

Sam Joseph said...

Sorry guys, haven't worked on this in a long time - if you'd like to learn more about mobile from me, please check out my online mobile class:

https://sites.google.com/site/hpumobilespring2013

and send me your gmail address if you'd like to take part

Sam Joseph said...

some of the functions are deprecated from multipart in mime

org.apache.http.entity.mime.MultipartEntity;

import org.apache.http.entity.mime.content.ContentBody;



how to solve ?