multipart/mixed and application/octet-stream - java

I have a jersey based web service which produces a "multipart/mixed" RESPONSE as follows:
The method reads a file, and should return it in octet format.
#GET
#Produces("multipart/mixed")
public byte[] getDocumentContents(#Context HttpHeaders header){
....
....
....
os = new ByteArrayOutputStream();
....
....
....
return os;
}
My question is: how do I make sure that the response is in OCTET-STREAM type? I know I could also just annotate the above method as:
#Produces("application/octet-stream")
But I specifically require to set the RESPONSE content-Type as "multipart/mixed" while sending a file in octet-stream format.
Does the above method do that ?
My assumption is it does but I have not a concrete reason on how.
thank you in advance!

I do not think "multipart/mixed" is a valid media type to be returned by a REST method
In my opinion, the correct way would be:
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getDocumentContents(#HeaderParam("your header param") final YourHeaderParamUserType headerParam) {
byte[] outByteArray = ... obtain byte array
return Response.ok()
.entity(outByteArray)
.build();
}
Pay attention to:
#Produces(MediaType.APPLICATION_OCTET_STREAM)
The param you might want to "extract" from the header could be getted using a param in the function like:
#HeaderParam("your header param") final YourHeaderParamUserType headerParam
The only thing you don't have to forget in "YourHeaderParamUserType" is to:
Include a constructor from a string
... or include a fromString(String) static method
... or include a valueOf(String) static method

Related

How to use RESTEasy Client-Proxy with MultipartFile

I tried for several days to use the RESTEasy Client-Proxy with Multipart forms.
In the best scenario, I would like to pass a MultipartFile into the Proxy.
E.g.
//client:
//Resteasy proxy creation left out for brevity
public Response add(MultipartFile versionFile) {
proxy.add(versionFile);
}
//server (interface):
#POST
#Consumes({MediaType.MULTIPART_FORM_DATA})
FormularDTO add(MultipartFile versionFile);
This always ends in an Exception.
could not find writer for content-type multipart/form-data type: org.springframework.web.multipart.support
As suggested by the Docs, there a two ways to handle Multipart-Files:
a) MultipartOutput/MultipartInput:
What should I send via the Proxy? If I send a MultipartOutput, I get the same Exception. MultipartInput is Abstract.
b) Use DTO with #MultipartForm
The solution currently used in the project, but requires to map all File-Metadata, create a new DTO, etc.
See Example below:
//DTO
public class MultipartFileDataDTO {
#FormParam("file")
#PartType(MediaType.APPLICATION_OCTET_STREAM)
private InputStream file;
#FormParam("contentType")
#PartType(MediaType.TEXT_PLAIN)
private String contentType;
...
}
//Server-Interface
#POST
#Consumes({MediaType.MULTIPART_FORM_DATA})
FormularDTO add(#MultipartForm MultipartFileDataDTO versionFile);
//Client-Mapping
MultipartFileDataDTO upload = new MultipartFileDataDTO();
upload.setFile(versionFile.getInputStream());
upload.setContentType(versionFile.getContentType());
...
My Question: What is the easiest way to "pass" a MultipartFile via a generated RESTEasy-Client-Proxy?
I think the easiest way to do would be to create a simple MultiplartFormDataOutput object and send it to the proxy.
Here is a simple example:
MultipartFormDataOutput output = new MultipartFormDataOutput();
// It is possible to pass a File object or a InputStream in the addFormData
output.addFormData("file", fileObject, MediaType.APPLICATION_OCTET_STREAM_TYPE, filename);
proxy.add(output)

Converting a POJO to Form Data in Java

I have a POJO of the form:
#Data
public class BaseRequest {
private String type;
private Map<String, Object> details;
private Map<String, Object> signature;
}
I have a service running which only accepts Content Type: "application/x-www-form-urlencoded".
I have written a client in Java which uses Spring's RestTemplate to make calls.
public String getInvoice(BaseRequest req, String url) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req, headers);
String response = this.restTemplate.postForObject(url, httpEntity, String.class);
return response;
}
However, it throws an error:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.x.y.z.BaseRequest] and content type [application/x-www-form-urlencoded]
It works if I set the content type as JSON:
headers.setContentType(MediaType.APPLICATION_JSON);
I know it works for JSON because I have configured my RestTemplate Bean with JacksonHTTPMessageConverter. So I can easily convert POJOs to application/json. However, I am not able to figure out how to do that with application/x-www-form-urlencoded.
I've been searching this for awhile now, and the only solution which I've found is to write my own converter to convert my BaseRequest class to Spring's MultiValueMap, and then Spring's FormHttpMessageConverter will automatically handle it. But I want to avoid doing that. Is there any other way around this?
Any leads would be appreciated. Thanks!
EDIT:
My question is different from #JsonProperty not working for Content-Type : application/x-www-form-urlencoded. The conversion happening there is about accepting data in application/x-www-form-urlencoded and converting it to a POJO. My question is about converting a POJO to application/x-www-form-urlencoded while using Spring's resttemplate to make calls. And like I mentioned, I know I can achieve this by writing my own converter to convert my POJO to Spring's MultiValueMap. However, I want to know if I can avoid doing this.
EDIT:
Dump of $_POST on the API when I send my data as MultiValueMap<String, Object>:
"array(0) {
}"
Dump of $_POST on the API when I send my data through Postman in the correct format:
"array(2) {
["type"]=>
string(16) "abcd"
["details"]=>
array(1) {
["template_file"]=>
string(16) "x.html"
}
}"
Try to convert your nested object in request payload to the org.springframework.util.MultiValueMap. Add and implement converter method in your POJO
public class BaseRequest {
// ...
public MultiValueMap<String, Object> toMap() {
MultiValueMap<String, Object> result = new LinkedMultiValueMap<>();
result.add("type", type);
result.put("details", details);
result.put("signature", signature);
return result;
}
}
Now use it during request creation
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req.toMap(), headers);
That is caused because inside FormHttpMessageConverter which performs actual conversion method canRead(Class<?>, MediaType) checks if MultiValueMap.class.isAssignableFrom(clazz) where clazz is your payload object. In your case it failed, so FormHttpMessageConverter skipped.
Hope it helps!

How to consume json parameter in java restful service

How can i consume json parameter in my webservice, I can able to get the parameters using #PathParam but to get the json data as parameter have no clue what to do.
#GET
#Path("/GetHrMsg/json_data")
#Consumes({ MediaType.APPLICATION_JSON })
#Produces(MediaType.APPLICATION_JSON)
public String gethrmessage(#PathParam("emp_id") String empid) {
}
What to use in place of #PathParam and how to parse it later.
I assume that you are talking about consuming a JSON message body sent with the request.
If so, please note that while not forbidden outright, there is a general consensus that GET requests should not have request bodies. See the "HTTP GET with request body" question for explanations why.
I mention this only because your example shows a GET request. If you are doing a POST or PUT, keep on reading, but if you are really doing a GET request in your project, I recommend that you instead follow kondu's solution.
With that said, to consume a JSON or XML message body, include an (unannotated) method parameter that is itself a JAXB bean representing the message.
So, if your message body looks like this:
{"hello":"world","foo":"bar","count":123}
Then you will create a corresponding class that looks like this:
#XmlRootElement
public class RequestBody {
#XmlElement String hello;
#XmlElement String foo;
#XmlElement Integer count;
}
And your service method would look like this:
#POST
#Path("/GetHrMsg/json_data")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public void gethrmessage(RequestBody requestBody) {
System.out.println(requestBody.hello);
System.out.println(requestBody.foo);
System.out.println(requestBody.count);
}
Which would output:
world
bar
123
For more information about using the different kinds of HTTP data using JAXB, I'd recommend you check out the question "How to access parameters in a RESTful POST method", which has some fantastic info.
Bertag is right about the comment on the GET. But if you want to do POST request that consumes json data, then you can refer to the code below:
#POST
#Path("/GetHrMsg/json_data")
#Consumes(MediaType.APPLICATION_JSON)
public Response gethrmessage(InputStream incomingData) {
StringBuilder crunchifyBuilder = new StringBuilder();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(incomingData));
String line = null;
while ((line = in.readLine()) != null) {
crunchifyBuilder.append(line);
}
} catch (Exception e) {
System.out.println("Error Parsing: - ");
}
System.out.println("Data Received: " + crunchifyBuilder.toString());
// return HTTP response 200 in case of success
return Response.status(200).entity(crunchifyBuilder.toString()).build();
}
For referencing please click here
#PathParam is used to match a part of the URL as a parameter. For example in an url of the form http:/example.com/books/{bookid}, you can use #PathParam("bookid") to get the id of a book to a method.
#QueryParam is used to access key/value pairs in the query string of the URL (the part after the ?). For example in the url http:/example.com?bookid=1, you can use #QueryParam("bookid") to get the value of `bookid.
Both these are used when the request url contains some info regarding the parameters and you can use the data directly in your methods.
Please specify the problem in detail if this post doesn't help you.

Jersey (javax) REST MissingAnnotationException

I have an HTML form with several fields and a file upload.
On the Java side, I'm receiving the form as follows, and it works:
#Component("org.phenotips.metabolites.FileUploaderResource")
#Path("/metabolites")
public class FileUploaderResource extends XWikiResource
{
#GET
#Path("/test")
public Response test() {
String response = "<html>Successful</html>";
return Response.ok(response).build();
}
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
#FormDataParam("filepath") InputStream uploadedInputStream
)
{
try {
String errorMessage = "This is an error response";
URI redirect = new URI("http://localhost:8080/");
return Response.temporaryRedirect(redirect).header("error_msg", errorMessage).build();
} catch (Exception ex) {
return Response.serverError().build();
}
}
}
But that's not what I need, as exemplified by what uploadedInputStream contains (example)
-----------------------------184640265716083967621914753489
Content-Disposition: form-data; name="id"
0000002
-----------------------------184640265716083967621914753489
Content-Disposition: form-data; name="column_order"
yes, no,
-----------------------------184640265716083967621914753489
Content-Disposition: form-data; name="date"
05/02/2015
-----------------------------184640265716083967621914753489
Content-Disposition: form-data; name="filepath"; filename="phenotips_2014-08-28_10-35.csv"
Content-Type: text/csv
"History (code)","History","History (code and name)","observed"
"","","","HP:0001622"
-----------------------------184640265716083967621914753489--
As you can see there are more fileds to the form than just the file.
But if I change the uploadFile's signature to
public Response uploadFile(
#FormDataParam("date") String date,
#FormDataParam("filepath") InputStream uploadedInputStream
)
I get the following error:
The root resource class org.phenotips.metabolites.FileUploaderResource is not a valid root resource class: The entity is already read. The 1. parameter requires one of the following annotations: [interface javax.ws.rs.core.Context, interface javax.ws.rs.HeaderParam, interface javax.ws.rs.MatrixParam, interface javax.ws.rs.QueryParam, interface javax.ws.rs.PathParam, interface javax.ws.rs.CookieParam]
Changing to #FormParam("date") also does not help, as it does not end up being found and returns a NullPointerException
EDIT. I like the guess proposed in the answer below.
I did indeed decide not to mention something directly (you can see it in the code though) - I am using a custom framework, XWiki. It is entirely possible that the body is read, and then there's nothing left to read.
This is not an answer: Just seeing if maybe it's something the OP is not showing us or telling us
I have tested this with Postman, and I have no problem. Maybe it's something else you're not showing us.
#Path("/multipart")
public class MutlipartResource {
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postMultiPart(#FormDataParam("date") String date,
#FormDataParam("filepath") InputStream stream)
throws Exception {
ImageIO.read(stream);
return Response.ok(date).build();
}
}
Here is my interpretation of the error, though I may be wrong, it's just a guess. It seems like something is reading the body part prior to reaching the method, so there is nothing left to be read. In which case the error maybe saying that since it is not a readable part, it should not be defined as a multipart part put instead some other form, and as you can only have one entity body, the date cannot be a body, but instead must be a query param, path param, etc (something that's not a entity body). Again this is just a guess. But something maybe you can look into.

How to calculate MD5 hash of multipart request body using Jersey

I am using Jersey and I need to calculate the MD5 hash of the body of a multipart post request. My current resource method signature looks like this:
#POST
#Consumes("multipart/form-data")
#Produces("application/json")
public String post(
#FormDataParam("name") String name,
#FormDataParam("description") String description,
#FormDataParam("iconfile") FormDataBodyPart part,
#Context HttpServletRequest hsr) {
// ...
}
I did not find a way to get the raw request body, that I need to calculate the MD5 hash. When my resource method is invoked the input stream from the HttpServletRequest (hsr.getInputStream()) is already consumed and I can not read it again.
I tried changing my method signature to the following:
#POST
#Consumes("multipart/form-data")
#Produces("application/json")
public String test(byte[] bytes) {
// ...
}
This way I get the raw bytes of the request body and I can successfully calculate the MD5 hash but I don't know how to handle the multipart request from there (split the parts, get each part, etc.). Do I have to resort to handle the raw request myself? Or can I let Jersey do the dirty job and extract the FormDataParams for me and let me calculate the MD5 hash somehow?
Thanks,
This is what I ended up doing:
I created a container request filter that consumes the entity input stream, calculates the MD5 checksum and sets the entity input stream again so it can be consumed by Jersey to handle the multipart request and extract the FormDataParams for me.
I also injected the HttpServletRequest both in my filter and my resource method to communicate data between the two.
This is the filter class:
public class MD5CheckFilter implements ContainerRequestFilter {
#Context HttpServletRequest hsr;
public ContainerRequest filter(ContainerRequest request) {
byte[] bytes = request.getEntity(byte[].class); // this consumes the entity input stream
String contentMD5 = calculateMD5(bytes);
hsr.setAttribute("contentMD5", contentMD5);
// set the entity input stream so it can be consumed again
request.setEntityInputStream(new ByteArrayInputStream(bytes));
return request;
}
}
This is the relevant section of my web.xml within the servlet section:
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>path.to.MD5CheckFilter</param-value>
</init-param>
This way I don't need to change the original method signature:
#POST
#Consumes("multipart/form-data")
#Produces("application/json")
public String post(
#FormDataParam("name") String name,
#FormDataParam("description") String description,
#FormDataParam("iconfile") FormDataBodyPart part,
#Context HttpServletRequest hsr) {
// ...
}

Categories

Resources