How to consume nested MultiParts in Jersey? - java

I am trying to write a REST endpoint that receives multiple files (as input streams) and data about those files (as Json).
Here's my client:
public void doSomething() {
MultiPart parent = new MultiPart(MultiPartMediaTypes.createMixed());
MultiPart child = new MultiPart(MultiPartMediaTypes.createMixed());
child.bodyPart(new BodyPart(someObject, MediaType.APPLICATION_JSON_TYPE));
child.bodyPart(new StreamDataBodyPart(someInputStream));
child.bodyPart(new StreamDataBodyPart(someOtherInputStream));
BodyPart bp = new BodyPart(child, MultiPartMediaTypes.createMixed());
parent.bodyPart(bp);
client.post(Entity.entity(parent, MultiPartMediaTypes.createMixed());
}
Here's my server:
#POST
#Path("something")
#Consumes(MultiPartMediaTypes.MULTIPART_MIXED)
public Response multiPart( #Context HttpServletRequest httpServletRequest,
MultiPart multiPart) {
List<BodyPart> bodyParts = multiPart.getBodyParts();
for(BodyPart bp : bodyParts) {
MultiPart multiPart = (MultiPart)bp.getEntity();
}
}
Ideally, I should be able to receive the read Entity on that bodyPart as a MultiPart. I should be able to access the underlying multipart. But I get a class cast exception (because the Entity is a BodyPartEntity).
However, on the receiving side, I get a MultiPart which contains a bodyPart whose entity is a BodyPartEntity which only provides an InputStream. The input stream contains all of the contents of the nested MultiPart, but ideally I should just be able to receive it as a MultiPart and read directly from there.
The input Stream looks something like this:
--Boundary_3_1589640004_1523988834754
Content-Type: application/json
{"someJson" : "value"}
--Boundary_3_1589640004_1523988834754
Content-Type: application/octet-stream
Content-Disposition: form-data; filename="test.txt"; name="test.txt"
(a bunch of characters representing the image)
--Boundary_3_1589640004_1523988834754
These 2 questions are asking exactly what I am asking, yet neither of them seem to work for me
How to consume a nested multipart/mixed mime type in jersey
Jersey reading nested Multipart (multipart/mixed)
How can I easily access the underlying MultiPart without having to manually parse through the InputStream? What am I missing?

Related

Get key-value pairs from multipart/form-data HTTP POST request in Camel

I have an endpoint setup using Apache Camel to receive a multipart/form-data HTTP request. Essentially I am trying to submit a data file and a configuration file for processing. The request is as follows (generated by Postman):
POST /upload HTTP/1.1
Host: localhost:8900
Content-Length: 363
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="data"; filename="data-file.json"
Content-Type: application/json
(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="config"; filename="config-file.json"
Content-Type: application/json
(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
My route is set up like so:
#Component
public class FileReceiverRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest()
.post("/upload")
.consumes(MediaType.MULTIPART_FORM_DATA_VALUE)
.bindingMode(RestBindingMode.off)
.route().routeId("upload-route")
.unmarshal().mimeMultipart()
.setHeader(Exchange.FILE_NAME, () -> UUID.randomUUID().toString())
.to("file:{{temp.input.files.dir}}")
.process(configFileProcessor)
// on to further processing
}
}
And my config file processor:
#Component
public class ConfigFileProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
AttachmentMessage attachmentMessage = exchange.getMessage(AttachmentMessage.class);
Map<String, Attachment> attachmentObjects = attachmentMessage.getAttachmentObjects();
// this fails - the key is instead the file name "config-file.json"
Attachment configAttachment = attachmentObjects.get("config");
}
}
I want to be able to retrieve the key-value pairs from the form-data and process the data and config files accordingly. Instead I get the following behaviour:
The first value in the form (in this case data-file.json) is parsed into the Camel message body, and the key seems to be discarded. The rest of the entries are parsed into an AttachmentMessage. This behaviour is documented here https://stackoverflow.com/a/67566273/11248602
The keys in the AttachmentMessage are not the original keys from the form-data request, but the filenames (e.g. config-file.json)
Is there any way to parse this request into a map or similar structure so that all the original keys and values can be accessed?
For the sake of clarity, as the proposed solution here above seems to work, a bit more explanations (although it is more a workaround than a solution):
Starting from:
Map<String, Attachment> attachmentObjects = attachmentMessage.getAttachmentObjects();
It is possible to browse all map entries, and for each found Attachment object, examine the attachment headers using getHeaderNames(), among other the 'Content-Disposition' one:
Content-Disposition: form-data; name="data"; filename="data-file.json"
which can finally be parsed to grab the form name ('data' in this example).
Not straightforward true, but this apparently works...

Java Jax-rs - get only file body in payload - remove headers

I have to upload a binary file to my server using UI/Postman client. My backend code for Rest API is:
#POST
#Produces({JSONHeaders.MEDIA_TYPE_JSONAPI, MediaType.APPLICATION_JSON})
#Path("loadLicense")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response loadLicense2(#ApiParam("load a license") File input) {
....
}
But the file I get has header details added to it which I don't need. File content is something like:
----------------------------013134317098674079511595^M
Content-Disposition: form-data; name="file"; filename="license.lic"^M
Content-Type: application/octet-stream^M
^M
^#^#^T.^#^#m.....^#^#^#^K^#^A^#^#^#^#^C^#^#^#(^#.^E^#^#^#^K^#.^#^#^#^G.^#^#^#^K^#.^#^#^#^#^B^#^#^#^K^#.^#^#^#^#^#^#^#^#^K^#.^#^#^#^#^F^#^#^#^K^#.^#^#^
^#^#^#^U^#^N^BGR^#^#^#^#^M^#^G^Bc^#^#^#^#^K^#^O^B1.0^#^#^#^#^Q^#4^Bpermanent^#^#^#^#^G^#.
^G.^V......I..^HC_.^^.^U...Y..G.^K.R.^?^O&..^.{V.Z.......h^B.<^O....w'#bk.^B]..*...8.W93...Z.\..... ..g.a+.....,^M
----------------------------013134317098674079511595--^M
But I just need the binary content. Is there any way to do that?
Note: I tried #FormParam - it doesn't work and I get this error
The #FormParam is utilized when the content type of the request entity is not application/x-www-form-urlencoded]
Tried #FormDataParam - not able to resolve it in code.
Instead of #FormParam you might need to use #FormDataParam, depending on how you send the data via Postman. They have different purposes, i.e. are for use with different MIME types:
#FormParam is intended to be used with MIME type application/x-www-form-urlencoded (constant MediaType.APPLICATION_FORM_URLENCODED)
#FormDataParam is intended to be used with multipart/form-data (constant MediaType.MULTIPART_FORM_DATA)
The following snippet expects the file being sent via form data:
#POST
#Produces({JSONHeaders.MEDIA_TYPE_JSONAPI, MediaType.APPLICATION_JSON})
#Path("loadLicense")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response loadLicense2(
#FormDataParam("file") InputStream istream,
#FormDataParam("file") FormDataContentDisposition disp) {
}
The input stream contains the binary data, the second parameter gives you some information about the uploaded file, e.g. the file name.
You need the jersey-media-multipart artifact for this:
https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-multipart
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>3.0.2</version>
</dependency>

Is it possible to send two different Content-Type for downloading file using jax-rs POST method

Is it possible to send two different Content-Type for POST method using post-man? Like, if the service is used for downloading excel file so in that case, #Consumes(MediaType.APPLICATION_JSON) is used for sending some user detail which is json structure and #Produces(MediaType.APPLICATION_OCTET_STREAM) is used for sending back the response as file.
NOTE: I don't want to use form-data, that is a constraint for this service.
On the client request, the Content-Type header is only for the type of data that is in the entity body. The Accept header is what you send for the type of data you want back.
On the server side, it is perfectly fine to do the following.
#POST
#Path("something")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response post(Model model) throws Exception {
final InputStream in = new FileInputStream("path-to-file");
StreamingOutput entity = new StreamingOutput() {
#Override
public void write(OutputStream out) {
IOUtils.copy(in, out);
out.flush();
}
};
return Response.ok(entity)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment;filename=somefile.xls")
.build();
}
The request to this endpoint should look like
POST /api/something HTTP 1.1
Content-Type: application/json;charset=utf-8
Accept: application/octet-stream
{"the":"json", "entity":"body"}
See also:
This post about purpose of #Produces and #Consumes and the role they play in content negotiation.
Aside
As an aside, consider using application/vnd.ms-excel as the Content-Type instead of application/octet-stream. This is already the standard MIME type for Microsoft Excel files1. When using StreamingOutput as the response entity type, you can make the #Produces anything you want, as there is no real conversion needed.
In Postman, when you use the "Send and download" feature, you will notice that when the Content-Type is application/octet-stream, it will suggest the file extension of .bin when saving the file. But if you have the Content-Type as application/vnd.ms-excel, then the suggested extension is .xls, which is what it should be.
1. Incomplete list of MIME types

Unable to process multipart/report data in Spring 5.0.1. It is only detecting multipart/form-data in the request

I am using Spring 5.0.1 and servlet 3.1.0
When user sends multipart/form-data in the request, spring is able to parse the request and make the parts out of it.
request.getParts() will have those provided multiparts.
But when user sends multipart/report (content-type), spring does not parse this request properly.
It does not give any exception but it does not store anything in the request parts.
request.getParts() will return empty array.
Is there any configuration that has to be done so that spring parses any kind of multipart data.
Posting my code and request payload below:
REST API does not have any restriction on the content type. It takes only request and response as parameters as shown below:
#RequestMapping (value = "/rest/external/integration/{serviceName}", method = RequestMethod.POST)
public void executeAssemblyExternal (HttpServletRequest request,
HttpServletResponse response, #PathVariable String serviceName) throws Exception
{
Parts[] requestParts = request.getParts();
}
Content-type header which is being sent with the request is :
Content-Type multipart/report; Report-Type=disposition-notification; boundary="----=_Part_82_645653877.1526452736757"
Multipart data which is being sent to the REST API is as below:
------=_Part_82_645653877.1526452736757
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
MDN for -
Message ID: <1088014046.24.1526452734879#MCBKUM03.eur.ad.sag>
From: SenderAS2
To: ReceiverAS2
Received on: 2018-05-16 at 12:08:56 (IST)
Status: processed
Comment: This is not a guarantee that the message has been completely processed or understood by the receiving translator
------=_Part_82_645653877.1526452736757
Content-Type: message/disposition-notification
Content-Transfer-Encoding: 7bit
Reporting-UA: webMethods Integration Server
Original-Recipient: rfc822; ReceiverAS2
Final-Recipient: rfc822; ReceiverAS2
Original-Message-ID: <1088014046.24.1526452734879#MCBKUM03.eur.ad.sag>
Received-content-MIC: SezQZhP0aSchqB1zCO0Dq4J0u3U=, sha1
Disposition: automatic-action/MDN-sent-automatically; processed
------=_Part_82_645653877.1526452736757--
As Deinum mentioned in a comment, servlet can not handle multipart requests other than multipart/form-data.
So Written a custom dispatcherServlet where requests having header Content-Type as multipart with subtype other than form-data will be bypassed from getting processed through MultipartResolver.
By doing this request.getInputStream() will have whole multipart data as sent by the user. Necessary actions can be performed on this inputstream at the server side.

REST - HTTP Post Multipart with JSON

I need to receive an HTTP Post Multipart which contains only 2 parameters:
A JSON string
A binary file
Which is the correct way to set the body?
I'm going to test the HTTP call using Chrome REST console, so I'm wondering if the correct solution is to set a "label" key for the JSON parameter and the binary file.
On the server side I'm using Resteasy 2.x, and I'm going to read the Multipart body like this:
#POST
#Consumes("multipart/form-data")
public String postWithPhoto(MultipartFormDataInput multiPart) {
Map <String, List<InputPart>> params = multiPart.getFormDataMap();
String myJson = params.get("myJsonName").get(0).getBodyAsString();
InputPart imagePart = params.get("photo").get(0);
//do whatever I need to do with my json and my photo
}
Is this the way to go?
Is it correct to retrieve my JSON string using the key "myJsonName" that identify that particular content-disposition?
Are there any other way to receive these 2 content in one HTTP multipart request?
If I understand you correctly, you want to compose a multipart request manually from an HTTP/REST console. The multipart format is simple; a brief introduction can be found in the HTML 4.01 spec. You need to come up with a boundary, which is a string not found in the content, let’s say HereGoes. You set request header Content-Type: multipart/form-data; boundary=HereGoes. Then this should be a valid request body:
--HereGoes
Content-Disposition: form-data; name="myJsonString"
Content-Type: application/json
{"foo": "bar"}
--HereGoes
Content-Disposition: form-data; name="photo"
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
<...JPEG content in base64...>
--HereGoes--

Categories