I need get a JAVA Object (DTO) with a MultipartFile in a Spring Controller
I tried different ways, like use of produces = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE} but nothing works.
The "DTO" to get is:
public class TodoDTO {
private Long id;
private String description;
private Boolean status;
private MultipartFile image;
...
}
And the controller method is:
#GetMapping(produces = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<List<TodoDTO>> getAll() {
ResponseEntity<List<TodoDTO>> response;
try {
response = new ResponseEntity<List<TodoDTO>>(todoService.getAll(), HttpStatus.FOUND);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage(), e);
}
return response;
}
I will expect to get entire object, with Multipart and the other properties. But the response in Postman is:
"status": 406,
"error": "Not Acceptable",
"message": "Could not find acceptable representation",
Related
I'm a newbie with Spring Boot, and I need your help.
I make a GET request with WebClient, and I receive a JSON body as below:
{
"status": "OK",
"error": [],
"payload": {
"name": "John",
"surname": "Doe"
...
}
}
So I have a DTO class in which mapping the response. Something like this:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ResponseAccountDTO {
private String status;
private List<ErrorDTO> errors;
private User payload;
}
I do it whit this method:
public ResponseUserDTO retrieveUserById(String userId) {
return webClient.get()
.uri(GET_USER_BY_ID_V4, accountId)
.header("Auth-Schema", AUTH_SCHEMA)
.header("apikey", API_KEY)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx error");
return Mono.error(new RuntimeException("4xx"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx error");
return Mono.error(new RuntimeException("5xx"));
})
.bodyToMono(ResponseDTO.class)
.block();
}
Finally, I test it with this method:
UserRestClient userRestClient = new UserRestClient(webClient);
#Test
void retrieveUser() {
ResponseDTO response = userRestClient.retrieveUserById("123");
UserDTO user = response.getPayload();
System.out.println("user surname: " + user.surname);
assertEquals("Doe", user.getSurname());
}
All fine until the response has KO Status. If something goes wrong (i.e., BAD REQUEST), I receive the same body JSON structure, as below:
{
"status": "KO",
"errors": [
{
"code": "ER000",
"description": "Wrong ID parameter",
"params": ""
}
],
"payload": {}
}
Is there a way to map also with KO Status the JSON body on my DTO class?
I want to return the error description on my retrieveUser() method.
Update:
I add my ErrorDTO class as suggest by Seelenvirtuose
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ErrorDTO {
private String code;
private String description;
private String params;
}
I myself ran into this issue and had to convert the json error response to an ErrorDTO object.
Hope the below code helps you for what you are looking for.
The below code can be applied to any Status code (e.g. 4xx, 5xx and even for 2xx as well but you won't need it for 2xx)
.onStatus(HttpStatus::is4xxClientError, error -> error
.bodyToMono(Map.class)
.flatMap(body -> {
try {
var message = objectMapper.writeValueAsString(body);
ErrorDTO errorResponse = objectMapper.readValue(message, ErrorDTO.class);
return Mono.error(new ServiceException(error.statusCode().value(), "My custom error message", errorResponse));
} catch (JsonProcessingException jsonProcessingException) {
return Mono.error(new ServiceException("Cannot parse the error response"));
}
})
)
In my Spring Boot REST application I have an endpoint, where I do some stuff. There I also have a #Provider, where I can catch and map all exceptions occurred during the process:
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
ErrorObject error = new ErrorObject("INTERNAL", 500, ex.getMessage());
return Response.status(error.getStatus()).entity(error).type(MediaType.APPLICATION_JSON).build();
}
}
The ErrorObject is just a basic pojo with some information:
public class ErrorObject implements Serializable {
private static final long serialVersionUID = 4181809471936547469L;
public ErrorObject(String name, int status, String message) {
this.name = name;
this.status = status;
this.message = message;
}
private String name;
private int status;
private String message;
setters/getters
}
If I call my endpoint with postman, I got this response if the exception occurs, and this is perfect:
{
"name": "INTERNAL",
"status": 500,
"message": "something happened",
}
But when I call the endpoint within my application, I catch the RestClientResponseException (which is basically HttpClientErrorException), I can see in the exception that it was 500, but there is no body, it is empty.
This is how I call it within my application:
try {
ResponseEntity<WhateverObject> entity = restTemplate.exchange(url, HttpMethod.POST, getBaseHeadersAsHttpEntity(), WhateverObject.class);
//...
} catch (RestClientResponseException e) {
//... ErrorObject of exception is missing here
}
How can I get the same body in case of exception, so my own ErrorObject from the exception?
Thanks for the comment of #Hemant Patel, after trying to set a new ErrorHandler, I figured out that the only thing I need to set is a new request factory:
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
And this factory is able to set the response body in the background successfully.
I am writing a spring boot app which has REST apis (using spring mvc framework) that stream audio/video to HTML5 player on the browser. These apis support range requests for the content.
I have run into an issue where the HTML5 video player complains with error ERR_CONTENT_LENGTH_MISMATCH periodically during streaming.
It seems that bytes received from server do not match bytes advertised by server in Content-Length header.
Please advise what could be the root cause of this.
Things that I have researched so far that could potentially solve the issue but haven't in my case:
No buffering in response.
No apache in front of tomcat.
Here is my code:
#Api("Player API")
#RestController
public class PlayerController {
#Autowired
FetchAssetService fetchAssetService;
#ApiOperation("Get video")
#RequestMapping(value = "player/video/{packageId}/{username}", method = RequestMethod.GET)
public ResponseEntity<StreamingResponseBody> getProxy(#RequestHeader(value="Range", required=false) String range, #PathVariable Long packageId, #PathVariable String username) throws Exception {
Optional<Stream> videoAssetMetaData = fetchAssetService.fetchVideoAssetMetaData(packageId);
if (!videoAssetMetaData.isPresent()) {
throw new AssetNotFoundException("Video asset not found in MPL for package: "+packageId);
}
HttpHeaders httpHeaders = new HttpHeaders();
HttpStatus status = HttpStatus.OK;
Optional<AssetRange> optionalAssetRange = AssetRange.create(range,videoAssetMetaData.get().getLength());
if (optionalAssetRange.isPresent()) {
if (optionalAssetRange.get().isSatisfiable()) {
setSuccessRangeHeaders(httpHeaders,optionalAssetRange.get());
status = HttpStatus.PARTIAL_CONTENT;
} else {
setErrorRangeHeaders(httpHeaders,optionalAssetRange.get());
status = HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
return new ResponseEntity(null,httpHeaders,status);
}
}
setContentHeaders(httpHeaders, “video.mp4");
try {
return new ResponseEntity(fetchAssetService.getStreamingResponseBody(packageId,videoAssetMetaData.get(),optionalAssetRange,username),
httpHeaders,
status);
} catch (Exception ex) {
log.error("Exception while video streaming: package={}, user={}, range={}",packageId,username,range,ex);
throw ex;
}
}
private void setContentHeaders(HttpHeaders httpHeaders, String fileName) {
httpHeaders.add(HttpHeaders.ACCEPT_RANGES,"bytes");
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename="+ fileName);
}
private void setSuccessRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
httpHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(range.getRangeLength()));
httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes %d-%d/%d", range.getStart(), range.getEnd(), range.getTotalLength()));
}
private void setErrorRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes */%d", range.getTotalLength()));
}
#ExceptionHandler(AssetNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public String handleAppException(AssetNotFoundException ex) {
return ex.getMessage();
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAppException(Exception ex) {
return ex.getMessage();
}
}
Best guess,
in setSuccessRangeHeaders, you are setting the content length to a range value rather than the actual content length of your response.
try not setting content_length at all or try setting it more accurately.
this might help:
How to set content length as long value in http header in java?
I'm handling an exception in my project
This is my GET endpoint:
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<V6SubnetRec> get(#RequestBody V6SubnetRequest requestBody) throws QIPException {
Site site = getSite(requestBody.getOrganization());
V6SubnetRec wsSubnet = (V6SubnetRec) requestBody.getV6Subnet();
V6SubnetRec v6SubnetRec = null;
try {
v6SubnetRec = getQipService1().getV6Subnets(wsSubnet, site);
} catch (Exception e) {
log.error(Keys.QIP_CALLOUT_ERROR, e);
throw new RestException(e);
}
return new ResponseEntity<V6SubnetRec>(v6SubnetRec, HttpStatus.OK);
}
#ExceptionHandler(RestException.class)
public ResponseEntity rulesForRestException(RestException restEx){
return new ResponseEntity(restEx.getResponse().getContent(), restEx.getResponse().getStatus());
}
RestException.java
#XmlRootElement(name = "RestException")
#XmlAccessorType(XmlAccessType.FIELD)
public class RestException extends RuntimeException{
#XmlElement
RestResponse response;
public RestException(Exception e){
//...
}
}
When I request with URL http://localhost/api/v1/v6subnet.json (return with JSON format), it will return HTTP status code 404 and include the content. It's OK.
But when I request with URL http://localhost/api/v1/v6subnet.xml (return with XML format) with the same request, it return HTTP status code 500 like a normal exception which is not handled as JSON format
I want to have results like when I request to JSON format.
Thanks.
I've fixed my issue. It's only change from
restEx.getResponse().getContent()
into
restEx.getResponse().getContent().toString()
I hava a problem with my one controller. When i send (POST) data from angular to java controller using ( jason content type ) i see error which is visible in the topic. So I can't catch response from server. Controller catch this request good so in this case book is added to database correctly. Firebug show 404 error but when I checked in Postman I saw 415 Unsupported Media Type. Why there is such an exception if the controller is working properly.
This is example JSON:
{"title":"fgthbfgd","authors":[{"author_id":24,"author":"danielle steel"}],"genres":[{"genre_id":1,"genre":"Dramat"}],"description":"rthg","path_image":"19296.png"}
and this is controller:
#SuppressWarnings("finally")
#RequestMapping(value = "/rest/book", consumes = "application/json", produces = "application/json", method = RequestMethod.POST)
public MessageDTO addNewBook(#RequestBody BookDTO newBook) {
MessageDTO message = new MessageDTO();
try {
bookService.addNewBook(newBook);
message.setCheck(true);
} catch (BookTitleException e) {
message.setCheck(false);
message.setDescription("Ksiązka o tym tytule juz istnieje.");
e.printStackTrace();
} finally {
return message;
}
}
This is BookDTO
public class BookDTO implements Serializable{
private static final long serialVersionUID = -5057364006691079475L;
private Integer id;
private AuthorEntity [] authors;
private String description;
private GenreEntity [] genres;
private String title;
private String path_image;
private double rate;
private Integer progressBar;
private boolean flagRate;
private double userRate;
/* geters and seters */
}
This is Angular code:
var bookResource = $resource( $rootScope.restUrl + 'book');
var book = {
title : $scope.title,
authors : $scope.author,
genres : $scope.genre,
description : $scope.description,
path_image: null
}
service.addNewBook = function(book){
var deferred = $q.defer();
bookResource.save(book)
.$promise.then( function(data){
deferred.resolve( data );
}, function(){
deferred.reject("Error during adding new book.");
});
return deferred.promise;
}
This is only a problem in my aplication. In another case all working correctly.
You have a 415 error because you send your request without the right content-type header
var bookResource = $resource( $rootScope.restUrl + 'book',{}, {
save:{
method:"POST",
headers:{'Content-Type':'application/json; charset=UTF-8'}
}
});
I hope it will solve your problem