Parsing Multipart/mixed in Spring - java

I want to parse requests like the following in my RestController
POST http://#.#.#.#:#/report HTTP/1.1
User-Agent: Android
Accept: text/html,application/xml,application/json,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Content-Type: multipart/mixed; boundary=%&REPORT_DIVIDER&%
Authorization: Basic ***
Content-Length: 23236
Host: #.#.#.#:#
Connection: Keep-Alive
Accept-Encoding: gzip
--%&REPORT_DIVIDER&%
Content-Type: application/json
{"content-excluded":true}
--%&REPORT_DIVIDER&%
Content-Disposition: attachment; filename="INSTALLATION"
Content-Type: application/octet-stream
609903cf-fcc0-460c-87db-958e031ac156
--%&REPORT_DIVIDER&%--
This message is conform to rfc1341, yet I cannot find a way to parse this in Spring.
Note that the number of files may vary.
I've already tried using CommonsMultipartResolver or StandardServletMultipartResolver, but both only support multipart/form-data (rfc1867).
Is there any way to parse these request in Spring besides writing my own parser?

While it is not a very nice solution, it was the only one I found:
I essentially reimplemented ServletFileUpload with this class and then loaded it into the apache library with this simple subclass of CommonsMultipartResolver

#Path("api")
#PUT
#Consumes("multipart/mixed")
#Produces("multipart/mixed")
public MultiPart twelve( MultiPart multiPart) throws IOException {
List<BodyPart> bodyParts = multiPart.getBodyParts();
BodyPartEntity bpe = (BodyPartEntity) bodyParts.get(1).getEntity();
}
This is for jersey based spring boot project.

Related

apache cxf MultiPart request has no Content-Length header

I'm having below code to send a multipart/form-data request.
List<Attachment> multipartData = new ArrayList<>();
ContentDisposition cd1 = new ContentDisposition("form-data; name=\"file\";
filename="+fileObj.getName());
FileInputStream inputStream = new FileInputStream(fileObj);
multipartData.add(new Attachment("file",inputStream, cd1));
MultipartBody multipart = new MultipartBody(multipartData);
In my RestClient class, I'm using the below lines of code to send a POST request using JAX-RS Client object
if ("POST".equals(method)) {
response = this.client.getBuilder().post(Entity.entity(entity,MediaType.MULTIPART_FORM_DATA));
I checked the HTTP request body using Wiremock and is as below:
Transfer-Encoding: [chunked]
Accept: [*/*]
Cache-Control: [no-cache]
User-Agent: [Apache-CXF/3.2.5]
Connection: [keep-alive]
Host: [127.0.0.1:9990]
Pragma: [no-cache]
Content-Type: [multipart/form-data; boundary="uuid:04b491f5-50de-4f4f-b7c0-cd745136d3d1"]
--uuid:04b491f5-50de-4f4f-b7c0-cd745136d3d1
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <file>
Content-Disposition: form-data; name="file"; filename=sample.txt
<File content goes here>
I want to know how the content-length header is missing in the request payload. Is there any way to set the content-length header to the request?
Please help me.
I used the apache cxf WebClient to unset the transfer encoding as chunked.
if ("POST".equals(method)) {
Invocation.Builder builder = this.client.getBuilder();
WebClient.getConfig(builder).getHttpConduit().getClient().setAllowChunking(false);
response = builder.post(Entity.entity(entity,MediaType.MULTIPART_FORM_DATA));
}
With this, the client is able to send the request with content-length header.

How to Read Json data in Restful web services using jax-rs

How to pass json as input to restful web services and how to insert the json data in to database.
Usually you pass JSON payload sending request with POST or PUT method and setting Content-Type header to application/json. Then on the server it converts to an object representation with help of library, for example Jackson.
POST /rest/sms/templates HTTP/1.1
Host: your-host.com
Accept: application/json
Content-Type: application/json
Cache-Control: no-cache
{
"template": "Hello, ${userName}"
}

Parsing FORM-ENCODED parameters with GRAILS (chargify webhooks)

I have a GRAILS 3 controller that receive an HTTP post from a webservice (Chargify) with this format (the payload section has about 100 entries with a lot of sub-fields):
POST / HTTP/1.1
Accept: */*; q=0.5, application/xml
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
X-Chargify-Webhook-Id: 81309408
X-Chargify-Webhook-Signature: xxxxxxxxxxxxx
X-Chargify-Webhook-Signature-Hmac-Sha-256: yyyyyyyyyyyyyy
Content-Length: 48
User-Agent: Ruby
X-Newrelic-Id: xxxxxx
X-Newrelic-Transaction: aaaaaaaaaaaaaa=
Host: myhost.test.it
id=81197881&event=statement_settled&payload[site][id]=12345&payload[site][subdomain]=test-sandbox
Is there any way with GRAILS to parse the "payload" part and convert it dynamically to a POJO (or also a simple hashmap)?. Chargify use this strange format not recognized by GRAILS framework and I'm unable to parse it automatically.
Is there anyone to help me for parsing? Advance thanks for helping.
Can you try this ?
def readChargify() {
String requestData = request.reader.text
def reqMap = org.grails.web.util.WebUtils.fromQueryString(requestData)
}
This Java library to parse the body of webhooks was contributed by another Chargify customer and may be helpful:
https://github.com/prowave/chargify-webhook-java

Jersey throws ParseException when uploading file in multipart/form-data request

I am trying to send an mp3 file along with some metadata to my Jersey REST service. For this I am trying to use multipart/form-data content type, however I always get java.text.ParseException: Next event is not a Separator when I send the request. The whole response:
{"restResponse":{
"responseCode":"INVALID_PARAMETER",
"i18nMessage":"Invalid Parameter: Next event is not a Separator",
"responseDescription":"The request contains an invalid parameter"}}
If I remove the mp3 file from the request, it is working properly so I assume there is a problem with how my request is constructed. I am using Paw, which generates request like this:
POST /rest/myapi/recording/multipart HTTP/1.1
Accept: application/json
Accept-Language: en-US
Content-Type: multipart/form-data; boundary=EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Cookie: JSESSIONID=a5f5cfa7329142158766a6182645; JSESSIONIDSSO=BAFA3371F6D14A179B0BA6216DD6C119
Host: localhost:8181
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 44504
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="queue"
qMultipart
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="datetime"
20151029-021807
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="recording"; filename="test4_multiform.mp3"
Content-Type: audio/mpeg
ID3ETT2test4_multiformCOMengiTunPGAP0TENiTunes 12.3.0.44COMhengiTunNORM 00000152 00000152 000013BE 000013BE 0000023E 0000023E 00003F10 00003F10 000002A7 000002A7COMengiTunSMPB 00000000 00000210 00000A30 0000000000015BC0 00000000 0
--- the rest of the recording ---
The serverside handler:
#Path(value = "multipart")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public Response receiveFileAndMetadataAsMultipart(
#FormDataParam(RECORDING) InputStream recordingIinputStream,
#FormDataParam(RECORDING) FormDataContentDisposition callRecordingDispositionHeader,
#FormDataParam(QUEUE) String queue,
#FormDataParam(DATETIME) String datetime) throws JSONException, ParseException, IOException {
//do stuff with my recording and metadata
//...
JSONObject response = new JSONObject();
return Response.status(Response.Status.OK).entity(response).build();
}
When I try to send just the file and the content type is not multipart/form-data but application/octet-stream, then it works fine. (I mean not with this handler but one which accepts octet-stream) although the request is very similar. I'll post the request and code for octet stream as well, maybe it helps:
PUT /rest/myapi/recording HTTP/1.1
Accept: application/json
Accept-Language: en-US
Content-Type: application/octet-stream
Cookie: JSESSIONID=a5f5cfa7329142158766a6182645; JSESSIONIDSSO=BAFA3371F6D14A179B0BA6216DD6C119
Host: localhost:8181
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 55414
ID3BTT2test3_2callsCOMengiTunPGAP0TENiTunes 12.3.0.44COMhengiTunNORM 0000038A 0000038A 00001B3D 00001B3D
--- the rest of the recording ---
Method:
#Path(value = "recording")
#PUT
#Consumes(MediaType.APPLICATION_OCTET_STREAM)
#Produces(MediaType.APPLICATION_JSON)
public Response receiveRecording(InputStream callRecordingInputStream) throws IOException, JSONException {
//do stuff with my recording
//...
JSONObject response = new JSONObject();
return Response.status(Response.Status.OK).entity(response).build();
}
Does anyone have any idea what is wrong? I found this approach with multipart on several places online so I guess it should work doing it like that.
Btw, I also tried removing all of the metadata and just send the recording but the problem was the same.
I have found the issue thanks to #peeskillet's comment, so just in case this exact same thing happens to someone else:
I had a bad import for FormDataContentDisposition.
Wrong:
import com.sun.jersey.core.header.FormDataContentDisposition;
Correct:
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;

Resteasy always get application/json content type on request

I have a web application that use Angularjs on frontend och Resteasy + Jackson on the back end. I'm sending a file from Angular component to a REST method, receiving method looks like this :
#POST
#Path("/upload/attachment/for/{eventId}")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultipartFormDataInput input,
final #PathParam("eventId") Long eventId,
#Context HttpServletRequest request) {
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
...my awesome stuff...
return Response.ok().build();
}
And request has following headers when sent :
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Content-Length 347085
Content-Type multipart/form-data; boundary=---------------------------12164806981346771846716776342
Cookie JSESSIONID=aoBd1hgzR3GM8bSG5P-9g-vQ; csrftoken=ziQ7kN7TlMehR2aURDrmaMLYAroMsSpu
Host localhost:9000
Referer http://localhost:9000/local/myapp/index.html
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:32.0) Gecko/20100101 Firefox/32.0
The problem is that THIS request ALWAYS has application/json as a Content-Type instead for multipart/form-data as it says in the headers. And I get :
20:12:00,490 WARN [org.jboss.resteasy.core.SynchronousDispatcher] (http-/127.0.0.1:8080-2) Failed executing POST /events/upload/attachment/for/null: org.jboss.resteasy.spi.UnsupportedMediaTypeException: Cannot consume content type
Already in HttpServletDispatcher the content is wrong.
I can't get if this is JBOSS that set Content-Type to wrong value or some thing else.
Ok, it was that I have an extra layer between angular and server which runs on Node.js and it just tunel requests to the server. I had to add following :
var r = null;
r = request.put({uri: url, json: inReq.body, headers: inReq.headers, form: inReq.form});
inReq.pipe(r).pipe(inRes);
This set all the headers from outgoing request and tunnel the request
Now I have right content-type

Categories