File upload Restful API: Spring-boot - spring

I'm trying to make image uploading but I get this error, and I don't know why. I've already tried many things but I'm still getting errors.
Firstly, this:
{
"timestamp": 1454645660390
"status": 405
"error": "Method Not Allowed"
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException"
"message": "Request method 'POST' not supported"
"path": "/usuarios/update"
}
This is my controller:
Note: returns null for testing.
#RequestMapping(value = "/update", method = RequestMethod.POST, headers = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Usuarios> updateUsuario(
OAuth2Authentication authentication,
HttpServletRequest req,
#RequestBody Usuarios usuarios,
#RequestParam("file") MultipartFile file) {
req.getHeaderNames();
file.getName();
return null;
}
And this is my MultipartResolver:
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1000000);
return resolver;
}
Any suggestions what I'm doing wrong? Thank you so much!
UPDATE
I've updated my #Bean:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class TierraApplication {
public static void main(String[] args) {
SpringApplication.run(TierraApplication.class, args);
}
#Bean
public MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("");
}
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(1000000);
return resolver;
}
}
and the method on my #RestController:
#RestController
#RequestMapping("/usuarios")
public class UsuariosController implements Serializable {
#RequestMapping(value = "/update", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
public ResponseEntity<Usuarios> updateUsuario(
#RequestBody Usuarios usuarios,
#RequestParam("file") MultipartFile file) {
file.getName();
return null;
}
}
but now i'm getting this error:
{
"timestamp": 1454683574928
"status": 415
"error": "Unsupported Media Type"
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException"
"message": "Content type 'multipart/form-data;boundary=----WebKitFormBoundary6GTTqiBmiacyW0xb;charset=UTF-8' not supported"
"path": "/usuarios/update"
}
EDIT 2
Ok, I've deleted the #Bean of multipartResolver and #RequestBody and all works fine.
#RequestMapping(value = "/update", method = RequestMethod.POST)
public ResponseEntity<?> updateUsuario(#RequestParam("file") MultipartFile file,
OAuth2Authentication authentication,
HttpServletRequest req) {
try {
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(name, HttpStatus.OK);
}
But now I can't reach my body in the request. If I put it got again all the same errors. So how can I pass or reach the body with a JSON like this?
{
"idUsuario": 1,
"roles":{"idRol": 1, "nombreRol": "ADMINISTRADOR", "fechaCreacion": "2016-01-31", "fechaModificacion": null,…},
"nombre": "User",
"apellido": "Test",
"fechaNacimiento": "1992-04-04",
"dni": 38078020,
"email": "test#hotmail.com",
"telefono": 155797919,
"domicilio": "test 972",
"provincia": "San Salvador de Jujuy",
"username": "tester",
"imagen": null,
"estado": true,
"fechaCreacion": "2016-02-03",
"fechaModificacion": null,
"idUsuarioCreacion": 1,
"idUsuarioModificacion": 0,
"passwordUsuario": "$2a$10$SGueYkRnMkL43Ns1nmA9NeONLLrqjChHtYwO8eh/LrMJlTkFHielW"
}

OK. That's the problem.
#RestController("/usarios")
sets the name of the controller not the urlmapping. You should annotate you class with
#RestController
#RequestMapping("/usarios")
to set the correct urlmapping for your service.

Related

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Interceptor and global exception handling

I have a post-interceptor. When the control layer is executed and returns information, the post-interceptor will be executed. At this time, an exception in the post-interceptor will be caught by the global exception handling and a prompt message will be returned. Use "postman" to test and control The information of layer and global exception handling is returned at the same time. Is this really returned? I wrote a test example. In the same situation, only the information of the control layer is returned. I think it should return the information of global exception handling.
Controller
#RestController
#RequestMapping("/v1/book")
#Validated
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
#GetMapping("/search")
public R searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return R.select(bookService.getBookByKeyword(q));
}
}
Interceptor
public class LogInterceptor extends HandlerInterceptorAdapter {
public LogInterceptor(LoggerResolver loggerResolver) {
this.loggerResolver = loggerResolver;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// There will be a runtime exception here
}
}
Global Exception Handing
#Order
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
/**
* Exception
*/
#ExceptionHandler({Exception.class})
public R processException(Exception exception) {
log.error("", exception);
return R.error();
}
}
Result
{
"code": 200,
"data": [
// ...
],
"type": "success",
"message": "OK"
}{
"code": 500,
"type": "error",
"message": "Internal Server Error"
}
"R extends HashMap<String, Object>", used to unify the return structure.
looking at your code snippet, I'm not sure what are those R in the searchBook and processException
try this (edit the processException to meet your specs):
#GetMapping("/search")
public ResponseEntity<?> searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return new ResponseEntity<>(bookService.getBookByKeyword(q), HttpStatus.OK);
}
#ExceptionHandler({Exception.class})
public ResponseEntity<?> processException(Exception exception) {
return new ResponseEntity<>(new ErrorDTO(exception.getMessage()), HttpStatus.UNPROCESSABLE_ENTITY);
}

"error": "Method Not Allowed", "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported

I am trying to check my #RestController with Postman but when I enter localhost:8081/rest/teachers/remove/2 I gain this error
My RestController :
#RestController
#RequestMapping("/rest/teachers")
public class TeacherRestController {
private static final String TEACHER_MODEL = "teacher";
#Autowired
TeacherService teacherService;
#DeleteMapping("/remove/{id}")
public ResponseEntity<HttpStatus> deleteTeacher(#PathVariable("id") long id) {
try {
teacherService.removeTeacher(teacherService.getTeacherById((int) id));
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
It is my foolish mistake.
I sent "GET" request on the Postman.
When I switched it to "Delete", it worked.

Content type blank is not supported

I want to handle the POST request when there is empty content-type.
When I add consumes = MediaType.APPLICATION_JSON_VALUE
and make a request in postman with Content-type blank I get the following error
{
"timestamp": 1581594986909,
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type '' not supported",
"path": "/test"
}
Here is the code
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity create(#RequestBody TestRequest testRequest) throws TestException {
LOG.debug("Starting...");
//code
return createtest(testRequest);
}
when i remove consumes = MediaType.APPLICATION_JSON_VALUE
and make a request with content-type = blank
i get the following error
{
"timestamp": 1581595348209,
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'application/octet-stream' not supported",
"path": "/test"
}
Here is the code
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity create(#RequestBody TestRequest testRequest) throws TestException {
LOG.debug("Starting...");
//code
return createtest(testRequest);
}
Here is the POstMan request
I want to handle this scenario and assume as if content-Type= application/json is sent
To handle empty Content-Type as if application/json, you need to configure MappingJackson2HttpMessageConverter to support application/octet-stream and controller's method (i.e. your create method) consumes both application/octet-stream and application/json.
For example:
[CONFIGURATION]
#Configuration(proxyBeanMethods = false)
public class MyConfigurer {
#Bean
public HttpMessageConverters customConverters() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.addAll(converter.getSupportedMediaTypes());
supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
converter.setSupportedMediaTypes(supportedMediaTypes);
return new HttpMessageConverters(converter);
}
}
[CONTROLLER'S METHOD]
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_OCTET_STREAM_VALUE })
public ResponseEntity create(#RequestBody TestRequest testRequest) throws TestException {
LOG.debug("Starting...");
//code
return createtest(testRequest);
}
Spring seems to assume that Content-Type is application/octet-stream when Content-Type is empty and by default configuration MappingJackson2HttpMessageConverter supports application/json and application/*+json only. Therefore you need to modify your configuration and controller's method like above.
Following references are helpful for you:
Javadoc of MappingJackson2HttpMessageConverter
Spring boot documents
I finally configured it and it is working. Here is the correct configuration for MappingJackson2HttpMessageConverter
#Configuration(proxyBeanMethods = false)
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonMessageConverter());
WebMvcConfigurer.super.configureMessageConverters(converters);
}
#Bean
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
List<MediaType> supportedMediaTypes=new ArrayList<>();
supportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
messageConverter.setSupportedMediaTypes(supportedMediaTypes);
supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
messageConverter.setSupportedMediaTypes(supportedMediaTypes);
messageConverter.setPrettyPrint(true);
return messageConverter;
}
Aso add the APPLICATION_OCTET_STREAM_VALUE } in the controller method you want to support the octet-stream.
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_OCTET_STREAM_VALUE
}

Spring RequestMapping DELETE and unauthorized access JWT

I have a problem with DELETE method in spring. I'm using JWT and sending it in request header but GET/POST/PATCH works, DELETE don't..I don't really know why. Even via postman I'm not authorized 401 to delete item but I can get/patch/post a new one... Here is my code of controllers:
#CrossOrigin(origins = "http://localhost:8081", maxAge = 3600)
#RestController
public class JwtAuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailsService userDetailsService;
#Autowired
private CarDetailsService carDetailsService;
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
#RequestMapping(value = "/car", method = RequestMethod.POST)
public ResponseEntity<?> getRents(#RequestBody CarDTO car) throws Exception {
return ResponseEntity.ok(carDetailsService.saveCar(car));
}
#RequestMapping(value ="/cars", method = RequestMethod.GET)
public ResponseEntity<?> getCars() throws Exception{
return ResponseEntity.ok(carDetailsService.getAllCars());
}
#PatchMapping("/cars/{id}")
public ResponseEntity<?> partialUpdate(#RequestBody PartialCarDTO partialCar, #PathVariable("id") Integer id){
return ResponseEntity.ok(carDetailsService.updateCar(partialCar,id));
}
#RequestMapping(value = "/cars/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteCar(#RequestBody PartialCarDTO partialCar, #PathVariable("id") Integer id){
return ResponseEntity.ok(carDetailsService.deleteCar(partialCar,id));
}
A good answer here: https://stackoverflow.com/a/299696/4573580
If a DELETE request includes an entity body, the body is ignored [...]
I deleted PartialCarDTO from requestmapping and via postman it is possible to delete entity, but in my rest api it's not .. :/ I tried a lot of variations but without success. Even if i pass NULL instead of payload in axios while keeping headers like authorization with my token, content type and access control allow origin. No I really don't know where is the problem. Always 401. Do You have any ideas?
return new Promise((resolve, reject) => {
let id=payload.id;
let url="http://localhost:8080/cars/"+id
let config = {
headers: {
"Authorization": "Bearer "+localStorage.getItem('token'),
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
}
axios.delete(url, payload, config)
.then(({data,status}) => {
if(status === 200){
resolve(true);
}
})
.catch(error=> {
reject(error);
})
}

Resources