In Java Http request, we can do this to make multipart HTTP POST.
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
FileBo
The only solution for now, without relying to external libraries, seems to be creating the Multipart Form Data request manually. This is an example how it can be done, using play.libs.WS.url
:
WSRequestHolder wsRequestHolder = WS.url(URL);
String boundary = "--XYZ123--";
String body = "";
for (String key : data.keySet()) {
body += "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\""
+ key + "\"\r\n\r\n"
+ data.get(key) + "\r\n";
}
body += "--" + boundary + "--";
wsRequestHolder.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
wsRequestHolder.setHeader("Content-length", String.valueOf(body.length()));
wsRequestHolder.post(body);
data
would be a java.util.Map<String, String>
containing all the name/value pairs you want to pass as form parameters. randomString
is a randomized value to make the boundary change from request to request. Adding binary data would work the same way.
This http://www.htmlcodetutorial.com/forms/form_enctype.html is a good place to refer to for understanding the specs.
As Romain Sertelon suggested, you can write a Writeable to handle this case. Here's one I wrote:
package utilities
import java.io.{ByteArrayOutputStream, File}
import com.ning.http.client.FluentCaseInsensitiveStringsMap
import com.ning.http.multipart.{MultipartRequestEntity, FilePart, StringPart}
import play.api.http.HeaderNames._
import play.api.http.{ContentTypeOf, Writeable}
import play.api.mvc.{Codec, MultipartFormData}
object MultipartFormDataWriteable {
implicit def contentTypeOf_MultipartFormData[A](implicit codec: Codec): ContentTypeOf[MultipartFormData[A]] = {
ContentTypeOf[MultipartFormData[A]](Some("multipart/form-data; boundary=__X_PROCESS_STREET_BOUNDARY__"))
}
implicit def writeableOf_MultipartFormData(implicit contentType: ContentTypeOf[MultipartFormData[File]]): Writeable[MultipartFormData[File]] = {
Writeable[MultipartFormData[File]]((formData: MultipartFormData[File]) => {
val stringParts = formData.dataParts flatMap {
case (key, values) => values map (new StringPart(key, _))
}
val fileParts = formData.files map { filePart =>
new FilePart(filePart.key, filePart.ref, filePart.contentType getOrElse "application/octet-stream", null)
}
val parts = stringParts ++ fileParts
val headers = new FluentCaseInsensitiveStringsMap().add(CONTENT_TYPE, contentType.mimeType.get)
val entity = new MultipartRequestEntity(parts.toArray, headers)
val outputStream = new ByteArrayOutputStream
entity.writeRequest(outputStream)
outputStream.toByteArray
})(contentType)
}
}
Here's how to use it:
import utilities.MultipartFormDataWriteable._
...
val url = "https://example.com"
val dataParts = Map(
"foo" -> Seq("bar"),
"alice" -> Seq("bob")
)
val file = new jave.io.File(... path to a jpg ...)
val fileParts = Seq(new FilePart("attachment", "foo.jpg", Some("image/jpeg"), file)
val multipartFormData = MultipartFormData(dataParts, fileParts, Seq(), Seq())
WS.url(url).post(multipartFormData)
It seems, based on play API documentation, that there is no built-in for multipart POST bodies.
However, it may be possible to create your own multipart body using the method
post[T](body: T)(implicit wrt: Writeable[T], ct: ContentTypeOf[T]): Future[Response]
with a type T of your choice, and the corresponding Writeable and ContentTypeOf types too.
But this would imply digging in how multipart bodies work with HTTP.
The accepted answer didn't work with play 2.5. Also the answer in play 2.6 documentation didn't work for 2.5.
The below worked fine:
Http.MultipartFormData.FilePart part = new Http.MultipartFormData.FilePart("fileKey",
"abc.zip", "multipart/form-data",
FileIO.fromFile(new File("/home/testData/abc.zip")));
List<Http.MultipartFormData.Part<Source<ByteString, ?>>> data = Arrays.asList(part);
Http.RequestBuilder requestBuilder = AuthFakeRequest.getAuthFakeRequest(routes.MyController.uploadZip()).method(POST)
.bodyMultipart(data, mat);
Result result = route(app, requestBuilder);
For mat
and app
objects they are obtained when inheriting play.test.WithApplication
class.
This is sloppy, and can definitely be cleaned up, but here's what I did to get it working. Feel free to make this so much better.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import play.libs.WS;
import com.ning.http.multipart.FilePart;
import com.ning.http.multipart.MultipartRequestEntity;
import com.ning.http.multipart.Part;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// Build up the Multiparts
List<Part> parts = new ArrayList<>();
parts.add(new FilePart("file", new File(filename)));
Part[] partsA = parts.toArray(new Part[parts.size()]);
// Add it to the MultipartRequestEntity
MultipartRequestEntity reqE = new MultipartRequestEntity(partsA, null);
reqE.writeRequest(bos);
InputStream reqIS = new ByteArrayInputStream(bos.toByteArray());
WS.WSRequestHolder req = WS.url(InterchangeConfig.conflateUrl+"dataset")
.setContentType(reqE.getContentType());
req.post(reqIS).map(...);
// or req.post(reqIS).get();
This is all using pieces already in the Play 2.0 framework.
Working example for play 2.3 using above approach, also added contentType while uploading the file.
public Promise<WSResponse> upload(Http.MultipartFormData.FilePart policyFilePart, String contentType) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
List<Part> parts = new ArrayList<>();
try {
parts.add(new FilePart("file", policyFilePart.getFile(), contentType, null));
parts.add(new StringPart("param1", "value1"));
parts.add(new StringPart("param2", "value2"));
Part[] partsA = parts.toArray(new Part[parts.size()]);
// Add it to the multipart request entity
MultipartRequestEntity requestEntity = new MultipartRequestEntity(partsA, new FluentCaseInsensitiveStringsMap());
requestEntity.writeRequest(bos);
InputStream reqIS = new ByteArrayInputStream(bos.toByteArray());
return WS.url(baseUrl + "upload")
.setContentType(requestEntity.getContentType())
.post(reqIS).map(new Function<WSResponse, WSResponse>() {
@Override
public WSResponse apply(WSResponse wsResponse) throws Throwable {
return wsResponse;
}
});
} catch (IOException e) {
e.printStackTrace();
return null;
}
}