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"
The standard way to upload files in a web application is to use a form with a special multipart/form-data encoding, which lets you mix standard form data with file attachment data. Note: The HTTP method used to submit the form must be POST (not GET ). The getRef() method gives you a reference to a TemporaryFile .
Follow this rules when creating a multipart form: Specify enctype="multipart/form-data" attribute on a form tag. Add a name attribute to a single input type="file" tag. DO NOT add a name attribute to any other input, select or textarea tags.
CloseableHttpClient is the base class of the httpclient library, the one all implementations use. Other subclasses are for the most part deprecated. The HttpClient is an interface for this class and other classes. You should then use the CloseableHttpClient in your code, and create it using the HttpClientBuilder .
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With