6
votes

I'm trying implement an HTTP client that makes multipart requests for uploading files to a HTTP server. The HTML form has three input fields: one for the username, one for the password and one for the file. The server side looks as follows.

<html>
<head>
<title>Uploader</title>
</head>

<body>
   <div id="header">
         <h1>Uploader</h1>
   </div>
   <div id="content">
         <form id="uploadformular" action="upload" method="post"
                enctype="multipart/form-data" accept-charset="utf-8">
                <div class="block">
                       <label for="user">Username</label> <input type="text" id="user"
                              name="myuser" required />
                </div>
                <div class="block">
                       <label for="password">Password</label> <input type="password" id="pin"
                              name="mypassword" required />
                </div>
                <div class="block">
                       <label for="file">ZIP File</label> <input type="file" id="file"
                              name="myfile" required />
                </div>
                <div>
                       <input type="submit" value="Upload" />
                </div>
         </form>
   </div>

</body>
</html>

My implementation is as follows.

public class MultipartUploader {

    private static final String CHARSET = "UTF-8";

    private static final String CRLF = "\r\n";

    public String httpUpload(String url, String filename, byte[] byteStream)
        throws MalformedURLException, IOException {

        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        final String boundary = Strings.repeat("-", 15) + Long.toHexString(System.currentTimeMillis());

        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setUseCaches(false);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Connection", "Keep-Alive");
        connection.setRequestProperty("Cache-Control", "no-cache");
        connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

        OutputStream directOutput = connection.getOutputStream();
        PrintWriter body = new PrintWriter(new OutputStreamWriter(directOutput, CHARSET), true);

        body.append(CRLF);
        addSimpleFormData("myuser", "myUserName", body, boundary);
        addSimpleFormData("mypassword", "mySecretPassword", body, boundary);
        addFileData("myfile", filename, byteStream, body, directOutput, boundary);
        addCloseDelimiter(body, boundary);

        int responseCode = connection.getResponseCode();
        String responseMessage = connection.getResponseMessage();

        String payload = CharStreams.toString(new InputStreamReader(connection.getInputStream()));
        return payload;
    }

    private static void addSimpleFormData(String paramName, String wert, PrintWriter body,
        final String boundary) {

        body.append(boundary).append(CRLF);
        body.append("Content-Disposition: form-data; name=\"" + paramName + "\"").append(CRLF);
        body.append("Content-Type: text/plain; charset=" + CHARSET).append(CRLF);
        body.append(CRLF);
        body.append(wert).append(CRLF);
        body.flush();
    }

    private static void addFileData(String paramName, String filename, byte[] byteStream, PrintWriter body,
        OutputStream directOutput, final String boundary) throws IOException {

        body.append(boundary).append(CRLF);
        body.append("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + filename + "\"")
            .append(CRLF);
        body.append("Content-Type: application/octed-stream").append(CRLF);
        body.append("Content-Transfer-Encoding: binary").append(CRLF);
        body.append(CRLF);
        body.flush();

        directOutput.write(byteStream);
        directOutput.flush();

        body.append(CRLF);
        body.flush();
    }

    private static void addCloseDelimiter(PrintWriter body, final String boundary) {

        body.append(boundary).append("--").append(CRLF);
        body.flush();
    }
}

The server responds with 200 OK. The problem I have is that somehow the HTTP body is not correctly created so that the response I get from the server says that not all fields of the form are set. The server doesn't say which field it is. So my question is do you see any problem with this code? Do I create the multipart request correctly?

I also tried to upload a file using cURL with the following command and it worked.

cURL -F "myuser=myUserName" -F "mypassword=mySecretPassword" -F "myfile=@/path/to/my/file.zip" "http://abcdef.gh:1234/path/to/uploader"
2
Well, what are all the fields of form? Can you show the server side?Evan Knowles
@EvanKnowles I just updated the question.Javiator
I suggest to use a more high level http client that takes care of constructing request for you. For example, square.github.io/okhttpDevstr
@Devstr I would have done that if it were possible, but it is not an option in my situation.Javiator
When I look into the HTTP body that is sent to the server, I can see that there are actually no line breaks between the MIME-part-headers. What could be the reason for this? I do add line breaks as you can see above. Or maybe they are just not shown by the editor.Javiator

2 Answers

5
votes

Your boundaries between parts of data are missing extra two dashes in the beginning: --

I've found this by capturing requests to http://httpbin.org/post made via your program and curl and comparing them via diff tool. I used Wireshark for capturing the requests.

Here's how you can fix this:

private static void addSimpleFormData(String paramName, String wert, PrintWriter body,
                                      final String boundary) {

    body.append("--").append(boundary).append(CRLF);
    body.append("Content-Disposition: form-data; name=\"" + paramName + "\"").append(CRLF);
    body.append("Content-Type: text/plain; charset=" + CHARSET).append(CRLF);
    body.append(CRLF);
    body.append(wert).append(CRLF);
    body.flush();
}

private static void addFileData(String paramName, String filename, byte[] byteStream, PrintWriter body,
                                OutputStream directOutput, final String boundary) throws IOException {

    body.append("--").append(boundary).append(CRLF);
    body.append("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + filename + "\"")
            .append(CRLF);
    body.append("Content-Type: application/octed-stream").append(CRLF);
    body.append("Content-Transfer-Encoding: binary").append(CRLF);
    body.append(CRLF);
    body.flush();

    directOutput.write(byteStream);
    directOutput.flush();

    body.append(CRLF);
    body.flush();
}

private static void addCloseDelimiter(PrintWriter body, final String boundary) {
    body.append("--").append(boundary).append("--").append(CRLF);
    body.flush();
}

Note extra .append("--") in the beginning of every method.


see https://gist.github.com/shtratos/8e9570a4a5591b2bcecd55ca60b3f24f for full working code

0
votes

Is there a good reason why you are writing your own implementation rather than use the Apache HttpClient?

See https://hc.apache.org/httpcomponents-client-ga/