I'm trying to call this specific method http://developers.box.com/docs/#files-upload-a-file in Box API with the help of Google HTTP Client library v1.14.1. Currently I see no way of doing this.
If I was using http://hc.apache.org/httpclient-3.x/methods/multipartpost.html, I would add 2 items of StringPart and 1 item of FilePart.
In Google HTTP Client library I see only MultipartContent and Part classes that do not seem to be able to handle pure name/value pairs, as StringPart referenced above.
Here is an excerpt from Apache HTTP Client examples:
HttpPost httppost = new HttpPost("http://localhost:8080" +
"/servlets-examples/servlet/RequestInfoExample");
FileBody bin = new FileBody(new File(args[0]));
StringBody comment = new StringBody("A binary file of some kind");
MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("bin", bin);
reqEntity.addPart("comment", comment);
httppost.setEntity(reqEntity);
I want to accomplish similar thing, but using Google HTTP Client. Any suggestions would be welcome!
After some investigation I found that I needed Content-Type: multipart/form-data for the Box API and appropriately build the request. It was not possible with the version of Google HTTP Client I was using, so I implemented MultipartFormDataContent class myself and it fits perfectly to the library. Here is the full listing of the class. Maybe it can be included into the library.
/*
* Copyright (c) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
/**
* This is a modification of com.google.api.client.http.MultipartContent from
* Google HTTP Client library to support multipart/form-data requests.
*
* The original author is Yaniv Inbar.
*/
public class MultipartFormDataContent extends AbstractHttpContent {
private static final String NEWLINE = "\r\n";
private static final String TWO_DASHES = "--";
private ArrayList<Part> parts = new ArrayList<Part>();
public MultipartFormDataContent() {
super(new HttpMediaType("multipart/form-data").setParameter("boundary", "__END_OF_PART__"));
}
@Override
public void writeTo(OutputStream out) throws IOException {
Writer writer = new OutputStreamWriter(out, getCharset());
String boundary = getBoundary();
for (Part part : parts) {
HttpHeaders headers = new HttpHeaders().setAcceptEncoding(null);
if (part.headers != null) {
headers.fromHttpHeaders(part.headers);
}
headers.setContentEncoding(null)
.setUserAgent(null)
.setContentType(null)
.setContentLength(null);
// analyze the content
HttpContent content = part.content;
StreamingContent streamingContent = null;
String contentDisposition = String.format("form-data; name=\"%s\"", part.name);
if (part.filename != null) {
headers.setContentType(content.getType());
contentDisposition += String.format("; filename=\"%s\"", part.filename);
}
headers.set("Content-Disposition", contentDisposition);
HttpEncoding encoding = part.encoding;
if (encoding == null) {
streamingContent = content;
} else {
headers.setContentEncoding(encoding.getName());
streamingContent = new HttpEncodingStreamingContent(content, encoding);
}
// write separator
writer.write(TWO_DASHES);
writer.write(boundary);
writer.write(NEWLINE);
// write headers
HttpHeaders.serializeHeadersForMultipartRequests(headers, null, null, writer);
// write content
if (streamingContent != null) {
writer.write(NEWLINE);
writer.flush();
streamingContent.writeTo(out);
writer.write(NEWLINE);
}
}
// write end separator
writer.write(TWO_DASHES);
writer.write(boundary);
writer.write(TWO_DASHES);
writer.write(NEWLINE);
writer.flush();
}
@Override
public boolean retrySupported() {
for (Part part : parts) {
if (!part.content.retrySupported()) {
return false;
}
}
return true;
}
@Override
public MultipartFormDataContent setMediaType(HttpMediaType mediaType) {
super.setMediaType(mediaType);
return this;
}
/**
* Adds an HTTP multipart part.
*
* <p>
* Overriding is only supported for the purpose of calling the super
* implementation and changing the return type, but nothing else.
* </p>
*/
public MultipartFormDataContent addPart(Part part) {
parts.add(Preconditions.checkNotNull(part));
return this;
}
/**
* Sets the boundary string to use.
*
* <p>
* Defaults to {@code "END_OF_PART"}.
* </p>
*
* <p>
* Overriding is only supported for the purpose of calling the super
* implementation and changing the return type, but nothing else.
* </p>
*/
public MultipartFormDataContent setBoundary(String boundary) {
getMediaType().setParameter("boundary", Preconditions.checkNotNull(boundary));
return this;
}
/**
* Single part of a multi-part request.
*
* <p>
* Implementation is not thread-safe.
* </p>
*/
public static final class Part {
private String name;
private String filename;
private HttpContent content;
private HttpHeaders headers;
private HttpEncoding encoding;
public Part setContent(HttpContent content) {
this.content = content;
return this;
}
public Part setHeaders(HttpHeaders headers) {
this.headers = headers;
return this;
}
public Part setEncoding(HttpEncoding encoding) {
this.encoding = encoding;
return this;
}
public Part setName(String name) {
this.name = name;
return this;
}
public Part setFilename(String filename) {
this.filename = filename;
return this;
}
}
}
I'd been struggling with this small limitation in the google-http-java-client for months and I came up time ago with an ugly and redundant version of the old MultipartRelatedContent class.
Now I updated the Google library to the latest 1.15.0-rc and I've found out that it was quite easy to subclass the MultipartContent and make something decent that should work with any kind of "multipart/form-data" content.
At the moment you can find it here:
https://github.com/marcosalis/kraken/blob/master/kraken_lib/src/main/java/com/google/api/client/http/MultipartFormDataContent.java
A short usage example:
File file = new File("/path/image.jpg");
FileContent fileContent = new FileContent("application/octet-stream", file);
MultipartFormDataContent multipart = new MultipartFormDataContent();
multipart.addPart(new Part(fileContent), "image", file.getName());
request.setContent(multipart); // sets the content to the request
"image" will be the value of the "name" key in the "content-disposition" header, and the file name (image.jpg in this case) will be the (optional) value for the "filename" key.
In the same way you can add as many different parts as you want (also nesting other "multipart/mixed" contents, just making sure you use a different boundary string) to the first level multipart content.