Multi-Part Request - java

I have an application which handles Multipart Request in below format.
POST .... HTTP/1.1
. . .
Accept:multipart/form-data
...
---boundary123 Content-type:application/octet-stream content-Disposition:
form-data filenale="payload.txt" name="someuniquename"
...
[paylaod content](this is in xml format)
---boundary123 content-type:application/json content-Disposition:form-data
name="someuniquname1"
{
...
ID:"999"
}
---boundary123
and here is my my controller part.
#Restcontroller
Class A{
#RequestMapping(value = "/a/b/c", method = RequestMethod.POST, consumes=
MediaType.MULTIPART_FORM_DATA_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody static void MyController(#RequestParam("file")
List<MultipartFile> files) {
}
Is this controller can parse both parts by identifying content-type(xml and json, no order) if I am receiving single multi-part file.If not Can you suggest format of controller for the same.

Use following to get the FormData in your Controller.
RequestMapping(value = "/yourPath", method = RequestMethod.POST)
public #ResponseBody Object upload(MultipartHttpServletRequest request, HttpServletResponse response) throws IOException {
//Get your form fields...
final String ID= request.getParameter('ID');
//and so on......
//Get your files.
Iterator<String> iterator = request.getFileNames();
MultipartFile multipartFile = null;
while (iterator.hasNext()) {
multipartFile = request.getFile(iterator.next());
//do something with the file.....
}
}

The way to achieve this would be to use the part boundry name with the RequestPart annotation:
#Restcontroller
Class A {
#RequestMapping(
value = "/a/b/c",
method = RequestMethod.POST,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public #ResponseBody void myController(#RequestPart("someuniquname") SomePojo xmlPart, #RequestPart("someuniquname1") SomeOtherPojo jsonPart) {
// ...
}
// ...
}

Related

Java Spring error 404 while trying to get JSON from API. Works in postman

I am trying to get a JSON Object from an API while using an API key in the header.
This works perfectly when I test it in Postman, but when I try it in my Spring application.
I got an error:
There was an unexpected error (type=Not Found, status=404). No message available.
API-Key and the URL are changed out with dummy data
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public #ResponseBody void testingAPI() throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If your are testing your API in Postman and it works perfectly, and in your application it's not working, this means that your method mapping isn't correct or it's not correctly called.
But from the comments where you said that the same configuration works if you don't have an API key, this means that your header isn't correctly mapped, in this case I'd recommend using #RequestHeader annotation to handle your API key.
Your method mapping will be like this:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader("APIKey") String apiKey) throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If you want to use 12345 as a default value for your API key param you can write:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader(name = "APIKey", defaultValue = "12345") String apiKey) throws ParseException {
You can check How to Read HTTP Headers in Spring REST Controllers tutorial for further reading about the #RequestHeader annotation.
A quick fix could be to change the void to a Class. like
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
#ResponseBody
public XXXResponse testingAPI() throws ParseException {
...
return new XXXRepsonse();
}
or:
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public void testingAPI() throws ParseException {
...
}
Where are you add header in your request? You controller should look like this:
#RestController
public class DemoController {
#GetMapping("/apitest" )
public void doRequest(#RequestHeader(name = "Ocp-Apim-Subscription-Key", defaultValue = "12345") String apiKey) {
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Ocp-Apim-Subscription-Key", apiKey);
ResponseEntity<String> responseEntity = restTemplate.exchange("https://api.kognif.ai/AIS/v1/aispositioncurrent?vesselimo=8505941&output=json",
HttpMethod.GET, new HttpEntity<String>(headers), String.class);
System.out.println(responseEntity.toString());
}
}
Postman request to your Spring app must be :
And of course, specify valid Ocp-Apim-Subscription-Key

Empty List<MultipartFile> when trying to upload many files in Spring with ng-file-upload

I have the following controller method for uploading multiple files at once, inspired by this blog post and answers to this question as well:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,
#RequestParam("file") List<MultipartFile> files) {
// handle files
}
However, the list of the files is always empty although request contains them.
If I add the third MultipartRequest parameter to the method:
public void upload(#PathVariable User user,
#RequestParam("file") List<MultipartFile> files,
MultipartRequest request)
I can see it contains my uploaded files correctly:
What might be the reason of empty List<MultipartFile>?
I'm using ng-file-upload to submit the files, but I don't think it is connected with the issue. Spring 4.2.4.
The problem was that ng-file-upload by default submits array of files using names file[0], file[1] etc. It is configurable with the arrayKey value when using Upload Service. Setting it to empty string forces the files to be sent under the same file key, which is correctly resolved with Spring and the #RequestParam("file") List<MultipartFile> contains all files that has been submitted.
Upload.upload({url: url, data: {file: arrayOfFiles}, arrayKey: ''})
Try to use #ModelAttribute like this:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,#ModelAttribute("uploadFile") FileUpload uploadFile) throws IllegalStateException, IOException {
List<MultipartFile> files = uploadFile.getFiles();
...
And create a class like:
public class FileUpload {
private List<MultipartFile> files;
public List<MultipartFile> getFiles() {
return files;
}
public void setFiles(List<MultipartFile> files) {
this.files= files;
}
}
That works for me, sending big 'email' object with multiple file attachments from UI to back-end:
Angular
sendEmailWithAttachments(taskId: string, template: string, email: any, modelConfig: any, files: any[]) {
let formData = new FormData();
formData.append('form', new Blob([JSON.stringify(email)], {type: 'application/json'}));
files.forEach(file => {
formData.append('files', file);
});
return this.$http({
method: 'POST',
data: formData,
url: this.baseUrl + '/' + taskId + '/email-with-attachment?template=' + template,
headers: {
'Content-Type': undefined
},
responseType: 'arraybuffer'
});
}
Java Spring
#RequestMapping(value = "{taskId}/email-with-attachment", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
public void sendEmailWithAttachment(
#PathVariable String taskId,
#RequestParam String template,
#RequestParam("form") MultipartFile form,
#RequestParam("files") List<MultipartFile> files) throws IOException {
Map<String, String> parameters = new ObjectMapper().readValue(form.getInputStream(), HashMap.class);
System.out.println("taskId"+ taskId);
System.out.println("template"+ template);
System.out.println("files"+ files);
System.out.println("parameters"+ parameters);
}
for multiple files. do this in your javascript
//first add files to form data
var formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append("images", files[i]);
}
//post files to backend e.g using angular
$http.post('upload', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.then(function(response){
console.log("UPLOAD COMPLETE::=> ", response);
}, function (error) {
console.log(error);
});
Do this in your java
//your java method signature
#PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public Response uploadImage(#RequestParam(value = "images") MultipartFile[] images){
}
I think that in the way you sent data from front, it can not bound with java.util.List. If you create a JSON data as request and you annotated your List with #RequestBody like:
#RequestMapping(value = "/{user}/attachment", method = RequestMethod.POST)
#PreAuthorize(...)
public void upload(#PathVariable User user,
#RequestBody List<MultipartFile> files) {
// handle files
}
this should work. Some info here.

Http Post request with content type application/x-www-form-urlencoded not working in Spring

Am new to spring currently am trying to do HTTP POST request application/x-www-form-url encoded but when i keep this in my headers then spring not recognizing it and saying 415 Unsupported Media Type
for x-www-form-urlencoded
org.springframework.web.HttpMediaTypeNotSupportedException: Content
type 'application/x-www-form-urlencoded' not supported
Can any one know how to solve it? please comment me.
An example of my controller is:
#RequestMapping(
value = "/patientdetails",
method = RequestMethod.POST,
headers="Accept=application/x-www-form-urlencoded")
public #ResponseBody List<PatientProfileDto> getPatientDetails(
#RequestBody PatientProfileDto name
) {
List<PatientProfileDto> list = new ArrayList<PatientProfileDto>();
list = service.getPatient(name);
return list;
}
The problem is that when we use application/x-www-form-urlencoded, Spring doesn't understand it as a RequestBody. So, if we want to use this
we must remove the #RequestBody annotation.
Then try the following:
#RequestMapping(value = "/patientdetails", method = RequestMethod.POST,consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public #ResponseBody List<PatientProfileDto> getPatientDetails(
PatientProfileDto name) {
List<PatientProfileDto> list = new ArrayList<PatientProfileDto>();
list = service.getPatient(name);
return list;
}
Note that removed the annotation #RequestBody
you should replace #RequestBody with #RequestParam, and do not accept parameters with a java entity.
Then you controller is probably like this:
#RequestMapping(value = "/patientdetails", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public #ResponseBody List<PatientProfileDto> getPatientDetails(
#RequestParam Map<String, String> name) {
List<PatientProfileDto> list = new ArrayList<PatientProfileDto>();
...
PatientProfileDto patientProfileDto = mapToPatientProfileDto(mame);
...
list = service.getPatient(patientProfileDto);
return list;
}
The solution can be found here https://github.com/spring-projects/spring-framework/issues/22734
you can create two separate post request mappings. For example.
#PostMapping(path = "/test", consumes = "application/json")
public String test(#RequestBody User user) {
return user.toString();
}
#PostMapping(path = "/test", consumes = "application/x-www-form-urlencoded")
public String test(User user) {
return user.toString();
}
Remove #ResponseBody annotation from your use parameters in method. Like this;
#Autowired
ProjectService projectService;
#RequestMapping(path = "/add", method = RequestMethod.POST)
public ResponseEntity<Project> createNewProject(Project newProject){
Project project = projectService.save(newProject);
return new ResponseEntity<Project>(project,HttpStatus.CREATED);
}
The easiest thing to do is to set the content type of your ajax request to "application/json; charset=utf-8" and then let your API method consume JSON. Like this:
var basicInfo = JSON.stringify({
firstName: playerProfile.firstName(),
lastName: playerProfile.lastName(),
gender: playerProfile.gender(),
address: playerProfile.address(),
country: playerProfile.country(),
bio: playerProfile.bio()
});
$.ajax({
url: "http://localhost:8080/social/profile/update",
type: 'POST',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: basicInfo,
success: function(data) {
// ...
}
});
#RequestMapping(
value = "/profile/update",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResponseModel> UpdateUserProfile(
#RequestBody User usersNewDetails,
HttpServletRequest request,
HttpServletResponse response
) {
// ...
}
I guess the problem is that Spring Boot has issues submitting form data which is not JSON via ajax request.
Note: the default content type for ajax is "application/x-www-form-urlencoded".
replace contentType : "application/x-www-form-urlencoded", by dataType : "text" as wildfly 11 doesn't support mentioned contenttype..
You have to tell Spring what input content-type is supported by your service. You can do this with the "consumes" Annotation Element that corresponds to your request's "Content-Type" header.
#RequestMapping(value = "/", method = RequestMethod.POST, consumes = {"application/x-www-form-urlencoded"})
It would be helpful if you posted your code.

Posting a file and JSON data to Spring rest service

I am building a Spring rest service for uploading a file. There is a form that consists of various field and one field for uploading a file. On submitting that form, I am sending a multipart form request i.e. Content-Type as multipart/form-data.
So I tried with below
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestBody CompanyDTO companyDTO, #RequestParam(value = "image", required = false) MultipartFile image){
.................
But, the above didn't work. So for time being,i sent JSON data as String and forming Company Object from that String in rest service like
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestParam("companyJson") String companyJson, #RequestParam(value = "image",required = false) MultipartFile image) throws JsonParseException, JsonMappingException, IOException{
CompanyDTO companyDTO = new ObjectMapper().readValue(companyJson, CompanyDTO.class);
.............................
Can't I send JSON data with #RequestBody without passing JSON as String?
Appending the values to the URL what u have been doing now using #RequestParam.
#RequestParam annotation will not work for complex JSON Objects , it is specifi for Integer or String .
If it is a Http POST method , use of #RequestBody will make the Spring to map the incoming request to the POJO what u have created (condition: if the POJO maps the incoming JSON)
create FormData() and append your json and file
if (form.validate()) {
var file = $scope.file;
var fd = new FormData();
fd.append('jsondata', $scope.jsonData);
fd.append('file', file);
MyService.submitFormWithFile('doc/store.html', fd, '', (response){
console.log(response)
});
}
//Service called in above
MyService.submitFormWithFile = function(url, data, config, callback) {
$http({
method : 'POST',
url : url,
headers : {
'Content-Type' : undefined
},
data : data,
transformRequest : function(data, headersGetterFunction) {
return data;
}
}).success(function(response, status, header, config) {
if (status === 200) {
callback(response);
} else {
console.log("error")
}
}).error(function(response, status, header, config) {
console.log(response);
});
};
// in your java part using ObjectMapper
//it is like string
fd.append('jsondata', JSON.stringify($scope.jsonData));
#Autowired
private ObjectMapper mapper;
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestParam String jsondata,
#RequestParam(required = true) MultipartFile file){
CompanyDto companyDto=mapper.readValue(jsondata, CompanyDTO.class);
......
}
Use below code snippet:
#RequestMapping(value= "/path", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseObject methodName(MyData input, #RequestParam(required=false) MultipartFile file) {
// To Do
}

How to make Content-Type header optional?

I've an heartbeat API implemeted using Spring REST service:
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}
And MyService.java has below method:
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyJson json = new MyJson();
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
try {
Date currDate = new Date(System.currentTimeMillis());
currentDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(currDate);
json.setTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatApi.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion", HttpStatus.BAD_REQUEST);
}
}
It works as expected but have 2 issues:
When I invoke this API, Content-Type header is mandatory & I want to know how to make this header optional.
How to add "Accept" header so that it can support other format such as Google Protobuf?
Thanks!
If you don't want to require Content-Type exist and be "application/json", you can just omit the consumes section entirely.
"Accept" is available via the "produces" value, as opposed to "consumes." So if you wanted to support Google Protobuf OR application/json, you could do this:
#Controller
#RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
#ResponseBody
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}

Categories

Resources