Spring boot 406 error using APPLICATION_OCTET_STREAM - spring

my code
#RestController
#RequestMapping("/rest/v1/files")
public class FileController {
#Autowired
FileService fileService;
#GetMapping(value="/{id}")
ResponseEntity<InputStreamResource> downloadFile(#PathVariable Integer id, HttpServletRequest request) throws IOException {
FileInfo file = fileService.getFile(id);
String dispositionPrefix = "attachment; filename=";
String encodedFilename = file.getOrgName();
Path filePath = Paths.get(file.getPath(), file.getName());
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath, StandardOpenOption.READ));
return ResponseEntity.ok()
.contentLength(Files.size(filePath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, dispositionPrefix + encodedFilename)
.body(resource);
}
}
And I get org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
I set content type with MediaType.APPLICATION_OCTET_STREAM. But I don't know what goes wrong...
I tried to download JPG files name like *************.jpg such as 1542293055613.jpg

There was no proper HttpMessageConverter for my spring project. I just added ResourceHttpMessageConverter, then it works.
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ResourceHttpMessageConverter(true));
}
}

This posted code is correctly. I run it without error.
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
This Exception thrown when the request handler cannot generate a response that is acceptable by the client.
Please check your request from client.

Related

How to get the updated/modified HttpServletRequest object from AOP #Before advice to Spring controller method

I used Spring AOP #Before advice in Spring boot application, and it should execute before hitting any api's.
My task/requirement :- If in the request header application-name is not passed then we should modify the header and add to 'unknown' value to the application-name for every API.
I am modifying the header in the AOP #before advice using HttpServletWrapper class as shown below.
Problem is - the AOP should return updated HttpServletrequest to a controller method but it's not working, not returning the updated one in controller.
Controller:-
#GetMapping
#RequestMapping("/demo")
public ResponseEntity<String> getEmployee(HttpServletRequest request) {
System.out.println("Header, application-name"+request.getHeader("application-name"));
return new ResponseEntity<>("Success", HttpStatus.OK);
}
Spring AOP code,
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Before("controllerRequestMapping()")
public HttpServletRequest advice(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
}
System.out.println("After setting---"+request.getHeader("application-name"));
return request;
}
}
Finally I resolved the issue,
Instead of using #Before advice used #Around advice, Around advice should return the object using proceed method.
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Around("controllerRequestMapping()")
public Object advice(ProceedingJoinPoint jp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
System.out.println("Header in AOP"+header);
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
//throw new BadRequestException("Invalid application name");
}
System.out.println("After setting---"+request.getHeader("application-name"));
return jp.proceed(new Object[] {request});
}
}
Updated httpservlet request is getting in controller method. Thanks

How to unit test a multipart POST request with Spring MVC Test?

I am trying to create unit test for REST APi but having big trouble with the uploading excel method.
Here is the method on the controller side
#RestController()
#RequestMapping(path = "/upload")
#CrossOrigin(origins = "http://localhost:4200")
public class FileController {
#Autowired
FileService fileService;
#PostMapping(value = "/{managerId}/project/{projectId}")
public List<Task> importExcelFile(#RequestParam("file") MultipartFile files, #PathVariable int managerId,
#PathVariable int projectId) throws IOException, ParseException {
return fileService.getTasksFromExcel(files, managerId, projectId);
}
Whatever I try I get a lot of errors and evidently I don't really understand what I am supposed to do.
The main error I get is
current request is not a multipart request
You can do the following.
I just simplified your example a tiny bit.
So, here's the controller that returns the file size of the file it receives.
#RestController
#RequestMapping(path = "/upload")
public class FileController {
#PostMapping(value = "/file")
public ResponseEntity<Object> importExcelFile(#RequestParam("file") MultipartFile files) {
return ResponseEntity.ok(files.getSize());
}
}
and this one is the test of it. There is a class called MockMvc that Spring provides to easily unit test your controllers and controller advices. There is a method called multipart that you can use to simulate file upload cases.
class FileControllerTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new FileController())
.build();
#Test
#SneakyThrows
void importExcelFile() {
final byte[] bytes = Files.readAllBytes(Paths.get("TEST_FILE_URL_HERE"));
mockMvc.perform(multipart("/upload/file")
.file("file", bytes))
.andExpect(status().isOk())
.andExpect(content().string("2037")); // size of the test input file
}
}
Generally Multipart uploads can be tested via MockMultipartFile:
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/file-upload-test.html

RestTemplate Spring Boot receive the file

In my Spring Boot application I have implemented the RestController method that returns the file in response.getOutputStream():
#RequestMapping(value = "/{fileId}", method = RequestMethod.GET)
#ResponseStatus(value = HttpStatus.OK)
public void getFile(#PathVariable #NotBlank String fileId, HttpServletResponse response) throws IOException, TelegramApiException {
File file = fileService.getFile(fileId);
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
response.setContentType(FileUtils.detectMimeType(inputStream));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("inline; filename=%s", file.getName()));
response.setContentLength((int) file.length());
FileCopyUtils.copy(inputStream, response.getOutputStream());
}
I would like to implement the integration test and use RestTemplate in order to invoke this endpoint and receive the file.
Something like:
restTemplate.getForObject(String.format("%s/v1.0/files/%s", getBaseApiUrl(), fileId), SOMECLASS.class);
Could you please show an example how it can be properly archived with Spring RestTemplate ?
I am assuming you want to start the server and call that API to see if it really works. Using spring-test, you can do this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/" + 1234, // File ID
String.class)).contains("File Content Here");
}
}
But you can actually test your logic only without start the server itself. Please refer to https://spring.io/guides/gs/testing-web/ for more info.
I have found the solution:
ResponseEntity<Resource> responseEntity = restTemplate.exchange(String.format("%s/v1.0/files/%s", getBaseApiUrl(), fileId), HttpMethod.GET, null, Resource.class);
return responseEntity.getBody().getInputStream();

How to add CORS headers to the Spring error page rendered by BasicErrorController?

I have a single page client being served by a Spring Boot REST MVC API application (spring boot version 1.5.2).
My app is secured via Auth0 JWT tokens. When things are working, the CORS headers for responses are provided by a ServletFilter that gets configured as part of setting up the security:
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterBefore(simpleCORSFilter(), Auth0AuthenticationFilter.class);
...
}
This seems to work everywhere I've tested it so far - but one place where it's not working is with the default Spring error page (path "/error", rendered by default by the BasicErrorController class).
When there's an exception thrown in my service methods, the error page works and renders the content I want as JSON in the response body, but the client app can't access the http response body because the response lacks CORS headers.
So the question: "how do I add CORS headers to the error page"?
Should I be removing the CORS filter from my security setup and applying the CORS filter more globally? Where would that be done - I can't find anything relevant in the Spring doccumentation.
Or should I be writing a custom Error controller? The only example of a custom error controller in the documentation just seems to allow you to return a string.
You can define a separate Controller for Error and allow cross origin to it using
#CrossOrigin("*")
Combining Poorvi's answer with Joni Karppinen's custom error controller code gives:
#RestController
public class ErrorController
implements org.springframework.boot.autoconfigure.web.ErrorController
{
private static final String PATH = "/error";
#Autowired private ErrorAttributes errorAttributes;
#Override
public String getErrorPath(){
return PATH;
}
// I guess when time comes to lock down cors header, we could use a spring
// value configuration here to share with corsfilter.
#CrossOrigin("*")
#RequestMapping(value = PATH, produces = "application/json")
public #ResponseBody
ErrorJson error(HttpServletRequest request, HttpServletResponse response){
return new ErrorJson(
response.getStatus(),
getErrorAttributes(request, false) );
}
private Map<String, Object> getErrorAttributes(
HttpServletRequest request,
boolean includeStackTrace
){
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(
requestAttributes,
includeStackTrace);
}
}
class ErrorJson {
public Integer status;
public String error;
public String message;
public String timeStamp;
public String trace;
public ErrorJson(int status, Map<String, Object> errorAttributes){
this.status = status;
this.error = (String) errorAttributes.get("error");
this.message = (String) errorAttributes.get("message");
this.timeStamp = errorAttributes.get("timestamp").toString();
this.trace = (String) errorAttributes.get("trace");
}
}
Which seems to do the job for me.

415 error while ajax post in spring rest

after solving this Do we have to have to post json object with exactly same fields as in pojo object in controller? im getting 415 error while posting from AJAX ,I m using spring rest . And yes i have seen other similar questions but non of them solved my problem
controller:
#RequestMapping(value = "/createTest", method = RequestMethod.POST )
public #ResponseBody String createTest(#RequestBody TestJsonDTO testJson)
throws JsonProcessingException, IOException {
TestSet test = new TestSet();
//................
AJAX:
function createTest() {
$.ajax({
type : 'POST',
url : "http://localhost:8085/annotationBased/admin/createTest",
dataType : "json",
accept:"application/json",
contentType : "application/json",
data : testToJSON(),
success : function() {
alert("success")
},
complete : function(){
findAllTEst()
alert("OK")
},
});
function testToJSON() {
listOfQuestionForTest = questionToAdd;
return JSON.stringify({
"testSet" : {name : $('#testname').val(),
fullmark : parseInt($('#fullmark').val()),
passmark : parseInt($('#passmark').val())},
"questionsInTest" : listOfQuestionForTest
// "testDate":$('#testDate').value()
})
}
and i have added those class u suggested.
You're getting a 415 status code because the server is sending html in the response, while your client expects json.
This might indicate that a server-side exception occured. In such a case, application servers send back a html response.
You have to either make the server respond with json, even if an exception has occured, or let the client handle not only json responses, but also html ones.
I recommend you take the first approach:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
ErrorResponse err = new ErrorResponse();
err.setStatusCode(/* 4XX or 500, depending on exception type */);
err.setERrorMessage(ex.getMessage());
return err;
}
}
public class ErrorResponse {
private int statusCode;
private String errorMessage;
// getters and setters or make the fields public
}
A #ControllerAdvice is like a Spring controller, except that it works for every request. #ExceptionHandler tells Spring to intercept exceptions of the specified type and run the code within the annotated method.
Depending on the type of the exception, you should set the right status code in the ErrorResponse object you'll be returning. This is a very basic example, you can also extend from default Spring exception resolvers and overwrite the default behavior. Please refer to this article for further details.
EDIT:
Another thing you could try is to force response's Content-Type to be always application/json, no matter the http stastus returned. You can do this by adding an interceptor in the class where you configure message converters and JSON serialization/deserialization properties:
#Configuration
public class ServiceContext
extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = this.getMappingJackson2HttpMessageConverter();
converters.add(converter);
}
#Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = this.getObjectMapper();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
return mappingJackson2HttpMessageConverter;
}
#Bean
public ObjectMapper getObjectMapper() {
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper(jsonFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // this is what you need
objectMapper.setSerializationInclusion(Include.NON_NULL); // this is to not serialize unset properties
return objectMapper;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseHeadersInterceptor headersInterceptor = this.getResponseHeadersInterceptor();
registry.addInterceptor(headersInterceptor).addPathPatterns("/**");
}
#Bean
public ResponseHeadersInterceptor getResponseHeadersInterceptor() {
return new ResponseHeadersInterceptor();
}
}
With ResponseHeadersInterceptor being as follows:
public class ResponseHeadersInterceptor
extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
response.setContentType(MediaType.APPLICATION_JSON_VALUE + "; charset=UTF-8");
}
}
This way, the server always responds JSON. If you still get 404 or 415, no doubt it's due to some error in the client side.

Resources