Friday, October 24, 2008

Java: Multi-Part Form Request using Apache Commons HttpClient

I previously wrote about how to do a simple HTTPUrlConnection request to a web page. This has served me well for quite some time. However, I ran into an issue on a recent project where I needed to do POST request and do a Multi-Part form to post large XML feeds to a service an a web appliance. This is where using HTTPUrlConnection broke down for my needs. While it is possible to do a Multi-Part POST using these classes, it turned out to be more trouble than it was worth.

This is where the Apache Commons HttpClient libraries came into play. Using the more robust Apache Commons libraries, I was easily able to throw together a Multi-Part form POST. There is one slight caveat when using HttpClient, you will need to include the Apache Commons Logging and Apache Commons Codec libraries in your classpath. Even if you do not use them in your implementation, HttpClient apparently uses them internally.

Below is a simplified example of using the HttpClient libraries to do a Multi-Part form POST. I commented out the sections relating to Cookies and posting Files since I was using XML strings.



package com.digiassn.blogspot;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;

public class ApacheHttpClientRequest {
private URL serverAddress;
private String multiPart;
private String result;
private String originalURL;

/**
* Main constructor
*
* @param urlString
* @param queryString
* @param mainReferenceIn
*/
public ApacheHttpClientRequest(String urlString)
{
try {
//This is only used to test the URL as a valid URL, serverAddress is not actually used anywhere
serverAddress = new URL(urlString);
originalURL = urlString;
} catch (MalformedURLException e) {
e.printStackTrace();
}
}

/**
* This method will connect to server, and return the results of the push
* You must set Query first, which is the contents of your XML file
*/
private void executeQuery()
{
PostMethod filePost = null;
HttpClient client = null;
try {
//create new post method, and set parameters
filePost = new PostMethod(originalURL);
filePost.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE,
true);


if ((multiPart.equals("")) || (multiPart == null))
{
throw new Exception("Query is null");
}

//create bytearray multi-part source from the query
ByteArrayPartSource targetFile = new ByteArrayPartSource("data", multiPart.getBytes());

//instead of a ByteArrayPartSource, the following could just as easily be used
//to include a file
//File f = new File("");
//FilePartSource fileSource = new FilePartSource(f);

Part[] parts = {
new StringPart("firstParameter", "value"),
new StringPart("secondParameter", "value"),
new FilePart("data", targetFile)
};

//Create the multi-part request
filePost.setRequestEntity(
new MultipartRequestEntity(parts, filePost.getParams())
);

//connect to server
client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);

//the Cookie Handler, if your using manual cookies
//filePost.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
//filePost.setRequestHeader("Cookie", "name=value");

//execute and return status
int status = client.executeMethod(filePost);
if (status == HttpStatus.SC_OK) {
System.out.println("Upload success");
} else {
System.out.println("Upload failed: " + HttpStatus.getStatusText(status));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
//set objects to null and garbage collect, not normally necessary but
//some issues popped up in the multi-threaded environment with memory consumption if this
//was not included
filePost = null;
client = null;

//force garbage collection. Expect to take a hit here in performance, may not be necessary
Runtime r = Runtime.getRuntime();
r.gc();
}
}

//below here are the gets and sets
public void setMultiPartString(String query) {
this.multiPart = query;
}

public String getResult() {
return result;
}

public URL getServerAddress() {
return serverAddress;
}

public void setServerAddress(URL serverAddress) {
this.serverAddress = serverAddress;
}
}

2 comments:

Anonymous said...

Hi, I found your article and very happy because this is exactly what I need. I tried it, but somehow it doesn't work for me. The script to handle the martipart form request is a Perl script. Don't know wht the parameter parts (firstParameter & secondParameter) are missing. It seems the picture can be retrived fine, but not the parameters. I searched on the we and saw another guy had the same problem. Do you know what could cause the problem? Thank you.

-Tian

Jose Sandoval said...

Thanks!