POST Streaming Audio over HTTP/2 in Android - java

Some background:
I am trying to develop a voice-related feature on the android app where a user can search using voice and the server sends intermediate results while user is speaking (which in turn updates the UI) and the final result when the query is complete. Since the server accepts only HTTP/2 single socket connection and Android HTTPUrlConnection doesn't support HTTP/2 yet, I am using Retrofit2.
I have looked at this, this and this but each example has fixed length data or the size can be determined beforehand... which is not the case for audio search.
Here's what my method for POST looks like:
public interface Service{
#Streaming
#Multipart
#POST("/api/1.0/voice/audio")
Call<ResponseBody> post(
#Part("configuration") RequestBody configuration,
#Part ("audio") RequestBody audio);
}
The method sends configuration file(containing audio parameters - JSON structure) and streaming audio in the following manner. (Expected POST request)
Content-Type = multipart/form-data;boundary=----------------------------41464684449247792368259
//HEADERS
----------------------------414646844492477923682591
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name="configuration"
//JSON data structure with different audio parameters.
----------------------------414646844492477923682591
Content-Type: audio/wav; charset=utf-8
Content-Disposition: form-data; name="audio"
<audio_data>
----------------------------414646844492477923682591--
Not really sure about how to send streaming(!!) <audio_data> . I tried using Okio to create multipart for audio in this way (From: https://github.com/square/okhttp/wiki/Recipes#post-streaming)
public RequestBody createPartForAudio(final byte[] samples){
RequestBody requestBody = new RequestBody() {
#Override
public MediaType contentType() {
return MediaType.parse("audio/wav; charset=utf-8");
}
#Override
public void writeTo(BufferedSink sink) throws IOException {
//Source source = null;
sink.write(samples);
}
};
return requestBody;
}
This didn't work of course. Is this a right way to keep on writing audio samples to ResponseBody? Where exactly should I call Service.post(config, audio) method so that I don't end up posting configuration file every time there is something in the audio buffer.
Also, since I have to keep on sending streaming audio, how can I keep the same POST connection open and not close it until user has stopped speaking?
I am basically new to OkHttp and Okio. If I have missed anything or part of the code is not clear please let me know and I'll upload that snippet. Thank you.

You might be able to use a Pipe to produce data from your audio thread and consume it on your networking thread.
From a newly-created OkHttp recipe:
/**
* This request body makes it possible for another
* thread to stream data to the uploading request.
* This is potentially useful for posting live event
* streams like video capture. Callers should write
* to {#code sink()} and close it to complete the post.
*/
static final class PipeBody extends RequestBody {
private final Pipe pipe = new Pipe(8192);
private final BufferedSink sink = Okio.buffer(pipe.sink());
public BufferedSink sink() {
return sink;
}
#Override public MediaType contentType() {
...
}
#Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeAll(pipe.source());
}
}
This approach will work best if your data can be written as a continuous stream. If it can’t, you might be better off doing something similar with a BlockingQueue<byte[]> or similar.

Related

Browsers do not automatically display an inline file returned from a back-end java HTTP response

I created a pdf on the fly with pdfbox, did corresponding tests and it is well formatted, I can save it, send it by email - and everything is working as expected.
Now that same pdf(without saving it), I returns it as a client hit a button; the respective request/response succeeds but the browser (any) do not display it.
Some context:
angularJS 1.6 on the front-end
jersey 1.9 as a rest api
HTTP POST method
No errors
It just stays on the current page
My code
final ByteArrayOutputStream pdfStream = (ByteArrayOutputStream) generatePricePDF((Price) services.includeClient(price), null);
StreamingOutput streamingOutput = new StreamingOutput() {
#Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
pdfStream.writeTo(outputStream);
pdfStream.flush();
pdfStream.close();
}
};
return Response.ok(streamingOutput, MediaType.valueOf("application/pdf"))
.header("Content-Length", pdfStream.toString().length())
.header("Content-Disposition", "inline; filename=price").build();
}
As I mentioned it above, I can copy the encoded response:
JVBERi0xLjQKJfbk/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzMgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZQovTWVkaWFCb3ggWzAuMCAwLjAgNjEyLjAgNzkyLjBdCi9QYXJlbnQgMiAwIFIKL0NvbnRlbnRzIDQgMCBSCi9SZXNvdXJjZXMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0xlbmd0aCA0MzEKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtDQp4nO1Xy27bMBC86yv2mByUcvmUiiBA7Ui3nqofIFQ6UKFaqCQDSb++I1J20cfFQG4W4MNydrg7JAcLa9dkH2omltQcMsOCnET4Nbszpryn5luWq9JQbhK4H1qft0PvY0oZQSLi9djRZz+SkMTFRyHwo/2XhqTgYq0Cbs5FZH96OXU92Ds/j2FKpZxcS2nFVjPnLuJVk+2azBgyLqWZTUxok/iP2jjltGUpLCIpnLFVXOllbVHYlk5YZcta2Qq4A8LgIA92mZhWgi0r7MMeRECtrcF8tuyWPEQtVVGJl/1Pi4blREmEY7RFObRZiXZfPaOJiXFl6yivQN6c8yivkuil6CIwIpB43ZH+kvI+50N05ZH+lHHXDxOFPrTzOIRjGw6+H15Gf+imefUOHlBdnvRBiIgau7qAlZQX9LcJ5GaCmzeBvkyCYjPBLZgAo/9fE2yT4LZM8L9JoLb/BJsJ1DYJNhPIbRJsJpA3MwmeznfEV90RS+zQKn3Jd+E4DzSFbqLvXU9tWrcnPyLw9Abk2A704xTmn74PeK9X387D9LB+rzOdrxvSNDqdG/0CJBiXHA0KZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8Ci9Gb250IDYgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9GMSA3IDAgUgo+PgplbmRvYmoKNyAwIG9iago8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL0Jhc2VGb250IC9UaW1lcy1Sb21hbgovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDAwMDE1IDAwMDAwIG4NCjAwMDAwMDAwNzggMDAwMDAgbg0KMDAwMDAwMDEzNSAwMDAwMCBuDQowMDAwMDAwMjQ3IDAwMDAwIG4NCjAwMDAwMDA3NTIgMDAwMDAgbg0KMDAwMDAwMDc4NSAwMDAwMCBuDQowMDAwMDAwODE2IDAwMDAwIG4NCnRyYWlsZXIKPDwKL1Jvb3QgMSAwIFIKL0lEIFs8RkI2MUI4MDM5Q0RFQzU0OTkwNDFFMENBMDc0RTNDREU+IDxGQjYxQjgwMzlDREVDNTQ5OTA0MUUwQ0EwNzRFM0NERT5dCi9TaXplIDgKPj4Kc3RhcnQ=
and I can convert it (using an online converted), So there it is my pdf.
I have reviewed Content-Disposition
and it seems that the header are ok.
I was not managing the response accordingly, this is the code added in the AngularJs 1.x Controller:
$http.post('rest/processprice', $scope.price, {responseType: "blob"})
.then(function(response) {
var file = new Blob([response.data], {type: "application/pdf"});
var fileURL = URL.createObjectURL(file);
$window.open(fileURL, '_blank');
});
Managing the response this way, the pdf file opens up automatically.

File upload along with other object in Jersey restful web service

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call.
I provide below the structure. Please help me how to do in this regard.
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
Employee emp) {
//..... business login
}
Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.
{
"Name": "John",
"Age": 23,
"Email": "john#gmail.com",
"Adrs": {
"DoorNo": "12-A",
"Street": "Street-11",
"City": "Bangalore",
"Country": "Karnataka"
}
}
However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.
Request you to help in this regard.
You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a #FormDataParam("emp") for the Employee.
#FormDataParam("emp") Employee emp) { ...
Here's the class I used for testing
#Path("/multipart")
public class MultipartResource {
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("emp") Employee emp) throws Exception{
Image img = ImageIO.read(fileInputStream);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
System.out.println(cdh.getName());
System.out.println(emp);
return Response.ok("Cool Tools!").build();
}
}
First I just tested with the client API to make sure it works
#Test
public void testGetIt() throws Exception {
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");
FileDataBodyPart filePart = new FileDataBodyPart("file",
new File("stackoverflow.png"));
// UPDATE: just tested again, and the below code is not needed.
// It's redundant. Using the FileDataBodyPart already sets the
// Content-Disposition information
filePart.setContentDisposition(
FormDataContentDisposition.name("file")
.fileName("stackoverflow.png").build());
String empPartJson
= "{"
+ " \"id\": 1234,"
+ " \"name\": \"Peeskillet\""
+ "}";
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
Response response = t.request().post(
Entity.entity(multipartEntity, multipartEntity.getMediaType()));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.
I'm not too familiar with Postman, so I saved that testing for last :-)
It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.
If we look at the Preview window, we'll see the problem
There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.
See UPDATE below for solution
And just for completeness...
See here for more about MultiPart with Jersey.
Basic configurations:
Dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey2.version}</version>
</dependency>
Client config:
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
Server config:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.multipart")
.register(MultiPartFeature.class);
If you're having problems with the server configuration, one of the following posts might help
What exactly is the ResourceConfig class in Jersey 2?
152 MULTIPART_FORM_DATA: No injection source found for a parameter of type public javax.ws.rs.core.Response
UPDATE
So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).
We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example
#POST
#Path("upload2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(#FormDataParam("emp") FormDataBodyPart jsonPart,
#FormDataParam("file") FormDataBodyPart bodyPart) {
jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Employee emp = jsonPart.getValueAs(Employee.class);
}
It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.
Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).
Asides
There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.
You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.
#POST
#Path("/UpdateProfile")
#Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
#Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
#FormDataParam("ProfileInfo") String ProfileInfo,
#FormDataParam("registrationId") String registrationId) {
String filePath= "/filepath/"+contentDispositionHeader.getFileName();
OutputStream outputStream = null;
try {
int read = 0;
byte[] bytes = new byte[1024];
outputStream = new FileOutputStream(new File(filePath));
while ((read = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch(Exception ex) {}
}
}
}
When I tried #PaulSamsotha's solution with Jersey client 2.21.1, there was 400 error. It worked when I added following in my client code:
MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
contentType = Boundary.addBoundary(contentType);
Response response = t.request()
.post(Entity.entity(multipartEntity, contentType));
instead of hardcoded MediaType.MULTIPART_FORM_DATA in POST request call.
The reason this is needed is because when you use a different Connector (like Apache) for the Jersey Client, it is unable to alter outbound headers, which is required to add a boundary to the Content-Type. This limitation is explained in the Jersey Client docs. So if you want to use a different Connector, then you need to manually create the boundary.
Your ApplicationConfig should register the MultiPartFeature.class from the glassfish.jersey.media.. so as to enable file upload
#javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
//register the necessary headers files needed from client
register(CORSConfigurationFilter.class);
//The jackson feature and provider is used for object serialization
//between client and server objects in to a json
register(JacksonFeature.class);
register(JacksonProvider.class);
//Glassfish multipart file uploader feature
register(MultiPartFeature.class);
//inject and registered all resources class using the package
//not to be tempered with
packages("com.flexisaf.safhrms.client.resources");
register(RESTRequestFilter.class);
}
I used file upload example from,
http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/
in my resource class i have below method
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response attachupload(#FormDataParam("file") byte[] is,
#FormDataParam("file") FormDataContentDisposition fileDetail,
#FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}
in my attachService.java i have below method
public void saveAttachment(String flename, byte[] is) {
// TODO Auto-generated method stub
attachmentDao.saveAttachment(flename,is);
}
in Dao i have
attach.setData(is);
attach.setFileName(flename);
in my HBM mapping is like
<property name="data" type="binary" >
<column name="data" />
</property>
This working for all type of files like .PDF,.TXT, .PNG etc.,
The request type is multipart/form-data and what you are sending is essentially form fields that go out as bytes with content boundaries separating different form fields.To send an object representation as form field (string), you can send a serialized form from the client that you can then deserialize on the server.
After all no programming environment object is actually ever traveling on the wire. The programming environment on both side are just doing automatic serialization and deserialization that you can also do. That is the cleanest and programming environment quirks free way to do it.
As an example, here is a javascript client posting to a Jersey example service,
submitFile(){
let data = new FormData();
let account = {
"name": "test account",
"location": "Bangalore"
}
data.append('file', this.file);
data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
data.append("device", "test001");
data.append("account", JSON.stringify(account));
let url = "http://localhost:9090/sensordb/test/file/multipart/upload";
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.post(url, data, config).then(function(data){
console.log('SUCCESS!!');
console.log(data.data);
}).catch(function(){
console.log('FAILURE!!');
});
},
Here the client is sending a file, 2 form fields (strings) and an account object that has been stringified for transport. here is how the form fields look on the wire,
On the server, you can just deserialize the form fields the way you see fit. To finish this trivial example,
#POST
#Path("/file/multipart/upload")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(#Context ContainerRequestContext requestContext,
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("accountKey") String accountKey,
#FormDataParam("account") String json) {
System.out.println(cdh.getFileName());
System.out.println(cdh.getName());
System.out.println(accountKey);
try {
Account account = Account.deserialize(json);
System.out.println(account.getLocation());
System.out.println(account.getName());
} catch (Exception e) {
e.printStackTrace();
}
return Response.ok().build();
}

How to set my own value as boundary, sending multipart data with jersey?

I'm sending multipart data with jersey-client and jersey client generates boundary itself.
And my problem is that the consuming server parses incoming TCP stream as raw data, using his own constant value as boundary. Sounds weird, I know :) But I can't do something this server side.
So I need to set boundary myself, but can't find any suitable method for it in FormDataMultiPart. How can I solve it? Is it even possible?
Make your method return Response and set the media type of the response yourself:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(
#FormDataParam("part") String s,
#FormDataParam("part") FormDataContentDisposition d) {
final Map<String, String> parameters = Maps.newHashMap();
parameters.put("boundary", "myboundary");
final MediaType mediaType = new MediaType("multipart", "form-data", parameters);
return Response
.ok(s + ":" + d.getFileName(), mediaType)
.build();
}

Java Servlet 3.0 server push: Sending data multiple times using same AsyncContext

Lead by several examples and questions answered here ( mainly
http://www.javaworld.com/javaworld/jw-02-2009/jw-02-servlet3.html?page=3 ), I want to have server sending the response multiple times to a client without completing the request. When request times out, I create another one and so on.
I want to avoid long polling, since I have to recreate request every time I get the response. (and that quite isn't what async capabilities of servlet 3.0 are aiming at).
I have this on server side:
#WebServlet(urlPatterns = {"/home"}, name = "async", asyncSupported = true)
public class CometServlet extends HttpServlet {
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
AsyncContext ac = request.startAsync(request, response);
HashMap<String, AsyncContext> store = AppContext.getInstance().getStore();
store.put(request.getParameter("id"), ac);
}
}
And a thread to write to async context.
class MyThread extends Thread {
String id, message;
public MyThread(String id, String message) {
this.id = id;
this.message = message;
}
public void run() {
HashMap<String, AsyncContext> store = AppContext.getInstance().getStore();
AsyncContext ac = store.get(id);
try {
ac.getResponse().getWriter().print(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
But when I make the request, data is sent only if I call ac.complete(). Without it request will always timeout. So basically I want to have data "streamed" before request is completed.
Just to make a note, I have tried this with Jetty 8 Continuation API, I also tried with printing to OutputStream instead of PrintWriter. I also tried flushBuffer() on response. Same thing.
What am I doing wrong?
Client side is done like this:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8080/home', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 3 || xhr.readyState == 4) {
document.getElementById("dynamicContent").innerHTML = xhr.responseText;
}
}
xhr.send(null);
Can someone at least confirm that server side is okay? :)
Your server-side and client-side code is indeed ok.
The problem is actually with your browser buffering text/plain responses from your web-server.
This is the reason you dont see this issue when you use curl.
I took your client-side code and I was able to see incremental responses, with only just one little change:
response.setContentType("text/html");
The incremental responses showed up immediately regardless of their size.
Without that setting, when my output was a small message, it was considered as text/plain and wasnt showing up at the client immediately. When I kept adding more and more to the client responses, it got accumulated until the buffer size reached about 1024 bytes and then the whole thing showed up on the client side. After that point, however, the small increments showed up immediately (no more accumulation).
I know this is a bit old, but you can just flushBuffer on the response as well.

How to Unit Test handling of incoming Jersey MultiPart requests

We have a REST service that accepts MultiPart POST requests containing BodyParts that hold InputStreams. Inside the REST service a file might be created based on the provided data.
Task
We want to unit test the class that does the file operations based on its MultiPart input. Note: Wo do NOT want to use Jersey-Test! Grizzly does not load our spring application context which we need to inject DAO and fileHandler services into our REST service class. We explicitly want to test how our fileHandler service processes multiPart data.
The problem however is that the MultiPart that is sent out from the REST Client is not the same as the one received by the REST Server as jersey probably does something with the data to stream it or whatever. Trying to test (see below) the following setup will result in an
IllegalArgumentException [B cannot be cast to com.sun.jersey.multipart.BodyPartEntity
REST Client - sending a MultiPart
(just snippets, I omitted the obvious stuff):
byte[] bytes = FileManager.readImageFileToArray(completePath, fileType);
MultiPart multiPart = new MultiPart().
bodyPart(new BodyPart(bytes, MediaType.APPLICATION_OCTET_STREAM_TYPE)).
bodyPart(new BodyPart(fileName, MediaType.APPLICATION_XML_TYPE)).
bodyPart(new BodyPart(senderId, MediaType.APPLICATION_XML_TYPE));
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(MultiPartWriter.class);
Client client = Client.create(cc);
WebResource webResource = client.resource(requestUrl);
Builder builder = webResource.type(MediaType.MULTIPART_FORM_DATA_TYPE);
builder = addHeaderParams(builder, headerParams);
ClientResponse response = builder.post(ClientResponse.class, multiPart);
Server Side - receiving a MultiPart
REST:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Response create(MultiPart multiPart) {
try {
multiPartReader.saveFile(multiPart);
Server Side MultiPartReader to save file from multipart
public class MultiPartReader {
public void saveFile(MultiPart multiPart) throws IOException {
BodyPartEntity bpe = (BodyPartEntity) multiPart.getBodyParts().get(0).getEntity();
InputStream inputStream = bpe.getInputStream();
// ...
BufferedImage bi = ImageIO.read(inputStream);
String fileName = getFileNameFromMultiPart(multiPart);
File file = new File(filename);
if (file.isDirectory()) {
ImageIO.write(bi, formatName, file);
} else {
file.mkdirs();
ImageIO.write(bi, formatName, file);
}
bpe.close();
}
Test - handling an incoming MultiPart in isolation
Now I want to test the MultiPartReader:
#Test
public void saveFile_should_Create_file() throws IOException {
byte[] bytes = IOUtils.toByteArray(this.getClass().getResourceAsStream(fileResource));
MultiPart multiPart = new MultiPart().
bodyPart(new BodyPart(bytes, MediaType.APPLICATION_OCTET_STREAM_TYPE)).
bodyPart(new BodyPart(fileName, MediaType.APPLICATION_XML_TYPE)).
bodyPart(new BodyPart(senderId, MediaType.APPLICATION_XML_TYPE));
multiPartReader.saveFile(multiPart);
file = new File(fileName);
Assert.assertNotNull(file);
Assert.assertTrue(file.getTotalSpace() > 0);
file.delete();
}
But, like I said I get a
IllegalArgumentException [B cannot be cast to com.sun.jersey.multipart.BodyPartEntity
at
BodyPartEntity bpe = (BodyPartEntity) multiPart.getBodyParts().get(0).getEntity();
So what can I do to emulate the send/receive handled by jersey so that my test will get the same data as my REST service does deployed on a server and requested by a REST client?
EDIT
Using
BodyPartEntity bpe = multiPart.getBodyParts().get(0).getEntityAs(BodyPartEntity.class);
will throw a
IllegalStateException: Entity instance does not contain the unconverted content
Further pointer, I think, towards having to convert the test-generated MultiPart in some way, before calling my MultiPartReader..
There has to be some method in jersey, I can call that will do this converting just the way it does, when it sends out a MultiPart request on a deployed system or maybe it is the receiving end that does some parsing when receiving the HTTP request..?
Looking at the jersey-multipart docs I see:
"It is not currently possible to know ahead of time what Java class the application would prefer to use for each individual body part, so an appropriate Provider cannot be selected. Currently, the unparsed content of each body part is returned (as a byte array) in the entity property of the returned BodyPart} instance, and the application can decide what further steps are needed based on the headers included in that body part. The simplest technique is to examine the received BodyPart, and then call the getEntityAs() method once you know which implementation class you would prefer."
It looks like you need to follow that suggestion. Examine the byte array returned in your Server Side MultiPartReader code:
multiPart.getBodyParts().get(0).getEntity();
...and call getEntityAs() on the BodyPart.

Categories