Multipart form upload on Android

Android 1.5 includes Apache HttpClient 4 for the purposes of making HTTP requests. Unfortunately HttpClient 4 (or the version included with Android anyway) doesn’t appear to support multipart form uploads.

As it happens writing a solution from scratch (at the HTTP layer that is) is pretty straight forward. The main challenge is understanding how the POST request should be structured. Once you know that it’s simply a matter of putting all the pieces together.

The connection is managed using the java.net.HttpURLConnection class. There’s a few properties that need to set on the connection to ensure it can be written to and read from,

HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);

A special header informs the server that this will be a multipart form submission.

conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=xxxxxxxxxx");

The “boundry” can be any string. In this example it’s xxxxxxxxxx. It’s used in the body of the request to seperate each field being submitted.

Next comes the main body of the request. Each field being submitted follows the same pattern. For example, to submit a text file called “helloworld.txt” with the contents “Hello World” and using the boundry string xxxxxxxxxx here’s what the request would look like,

--xxxxxxxxxx
Content-Disposition: form-data; name="filetoupload"; filename="helloworld.txt"
Content-Type: text/plain

Hello World
--xxxxxxxxxx--

The final -- is important. It tells the server it’s the end of the submission.

Here’s a full method that’s used to upload a file to a URL. It can optionally take a username and password which is used to perform BASIC authentication.

    public static void put(String targetURL, File file, String username, String password) throws Exception {

        String BOUNDRY = "==================================";
        HttpURLConnection conn = null;

        try {

            // These strings are sent in the request body. They provide information about the file being uploaded
            String contentDisposition = "Content-Disposition: form-data; name=\"userfile\"; filename=\"" + file.getName() + "\"";
            String contentType = "Content-Type: application/octet-stream";

            // This is the standard format for a multipart request
            StringBuffer requestBody = new StringBuffer();
            requestBody.append("--");
            requestBody.append(BOUNDRY);
            requestBody.append('\n');
            requestBody.append(contentDisposition);
            requestBody.append('\n');
            requestBody.append(contentType);
            requestBody.append('\n');
            requestBody.append('\n');
            requestBody.append(new String(Util.getBytesFromFile(file)));
            requestBody.append("--");
            requestBody.append(BOUNDRY);
            requestBody.append("--");

            // Make a connect to the server
            URL url = new URL(targetURL);
            conn = (HttpURLConnection) url.openConnection();

            // Put the authentication details in the request
            if (username != null) {
                String usernamePassword = username + ":" + password;
                String encodedUsernamePassword = Base64.encodeBytes(usernamePassword.getBytes());
                conn.setRequestProperty ("Authorization", "Basic " + encodedUsernamePassword);
            }

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDRY);

            // Send the body
            DataOutputStream dataOS = new DataOutputStream(conn.getOutputStream());
            dataOS.writeBytes(requestBody.toString());
            dataOS.flush();
            dataOS.close();

            // Ensure we got the HTTP 200 response code
            int responseCode = conn.getResponseCode();
            if (responseCode != 200) {
                throw new Exception(String.format("Received the response code %d from the URL %s", responseCode, url));
            }

            // Read the response
            InputStream is = conn.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] bytes = new byte[1024];
            int bytesRead;
            while((bytesRead = is.read(bytes)) != -1) {
                baos.write(bytes, 0, bytesRead);
            }
            byte[] bytesReceived = baos.toByteArray();
            baos.close();

            is.close();
            String response = new String(bytesReceived);

            // TODO: Do something here to handle the 'response' string

        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

    }

There’s nothing in this code particular to Android so it will work in any java application.