Spring Boot - how to allow CORS on REST Controller HTTP PUT - spring

I'm my Spring Boot REST controller, I'm able to allow CORS for HTTP POST, but for some reason HTTP PUT is still being blocked.
I have placed my CORS decorator at the Controller level - HTTP PUT handler still being blocked.
Here is my controller:
package com.khoubyari.example.api.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.khoubyari.example.domain.Hotel;
import com.khoubyari.example.exception.DataFormatException;
import com.khoubyari.example.service.HotelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#RestController
#RequestMapping(value = "/example/v1/hotels")
#Api(tags = {"hotels"})
//#CrossOrigin(origins = "http://localhost:4200")
#CrossOrigin( origins = "*" , allowedHeaders = "*")
public class HotelController extends AbstractRestHandler {
#Autowired
private HotelService hotelService;
#RequestMapping(value = "",
method = RequestMethod.POST,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.CREATED)
#ApiOperation(value = "Create a hotel resource.", notes = "Returns the URL of the new resource in the Location header.")
public void createHotel(#RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
Hotel createdHotel = this.hotelService.createHotel(hotel);
response.setHeader("Location", request.getRequestURL().append("/").append(createdHotel.getId()).toString());
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a paginated list of all hotels.", notes = "The list is paginated. You can provide a page number (default 0) and a page size (default 100)")
public
#ResponseBody
Page<Hotel> getAllHotel(#ApiParam(value = "The page number (zero-based)", required = true)
#RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page,
#ApiParam(value = "Tha page size", required = true)
#RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size,
HttpServletRequest request, HttpServletResponse response) {
return this.hotelService.getAllHotels(page, size);
}
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a single hotel.", notes = "You have to provide a valid hotel ID.")
public
#ResponseBody
Hotel getHotel(#ApiParam(value = "The ID of the hotel.", required = true)
#PathVariable("id") Long id,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Hotel hotel = this.hotelService.getHotel(id);
checkResourceFound(hotel);
return hotel;
}
#RequestMapping(value = "/{id}",
method = RequestMethod.PUT,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Update a hotel resource.", notes = "You have to provide a valid hotel ID in the URL and in the payload. The ID attribute can not be updated.")
public void updateHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, #RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
if (id != hotel.getId()) throw new DataFormatException("ID doesn't match!");
this.hotelService.updateHotel(hotel);
}
//todo: #ApiImplicitParams, #ApiResponses
#RequestMapping(value = "/{id}",
method = RequestMethod.DELETE,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Delete a hotel resource.", notes = "You have to provide a valid hotel ID in the URL. Once deleted the resource can not be recovered.")
public void deleteHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, HttpServletRequest request,
HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
this.hotelService.deleteHotel(id);
}
}
What should I change in order for the HTTP PUT handler to allow updates?

You may need to specify allowed methods explicitely like this in your CORS config (I'm using Kotlin and implementing WebMvcConfigurer):
override fun addCorsMappings(registry: CorsRegistry) {
log.info("CORS: {}", origins)
registry.addMapping("/**").allowedOrigins(*origins)
.allowedMethods("GET", "POST", "PUT", "OPTIONS")
}
PUT is not allowed by default, as can be seen in CorsConfiguration#DEFAULT_PERMIT_METHODS:
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));

Related

Spring boot: Sending a JSON to a post request that uses a model as a param

Lets say I have a predefined post mapping as such:
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> addVal(#RequestBody final valDetail newVal) {
//Do Stuff
}
and the valDetail object as follows:
#Data
#Component
#Entity
#Table(name = "val_portal")
public class valDetail {
#Id
#Column(name = "valcode")
private String valCode;
#Column(name = "valname")
private String valName;
}
How would I go about actually sending JSON values from a separate service to this /add endpoint so that they are properly received as a valDetail object?
Currently I tried this implementation but I keep getting a 415 response code.
JSONObject valDetail = new JSONObject();
valDetail.put("valCode",request.getAppCode().toLowerCase());
valDetail.put("valName", request.getProjectName());
String accessToken = this.jwtUtility.retrieveToken().get("access_token").toString();
HttpHeaders authHeaders = new HttpHeaders();
authHeaders.setBearerAuth(accessToken);
authHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(valDetail.toString(), authHeaders);
ResponseEntity<String> loginResponse = restTemplate.exchange(uri,
HttpMethod.POST,
entity,
String.class);
If you want to pass data as json you don't want to take Model try to use #ResponseBody annotation to transfer data through json.
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<String> addVal(#RequestBody final valDetail newVal) {
//Do Stuff
}

How can I hide #ApiResponse form #ControllerAdvice for an endpoint?

I'm trying to migrate our manually writen OpenAPI (swagger) to a generated OpenAPI using springdoc-openapi for our Spring-Boot application. We got some issues, because the controller responses (mostly ErrorCodes) didn't match to the documentatation.
We already used a #ControllerAdvice annotated handler configuration. Here a snippet:
#ControllerAdvice
public class ExceptionHandler {
#ResponseStatus(code = HttpStatus.NOT_FOUND)
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!", content = #Content)
#ExceptionHandler(NotFoundException.class)
public void handleException(NotFoundException e) {
log.warn("Returning {} due to a NotFoundException: {}", HttpStatus.NOT_FOUND, e.toString());
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ApiResponse(responseCode = "400", description = "(BAD REQUEST) Given resource is invalid!", content = #Content)
#ExceptionHandler(InvalidResourceException.class)
public void handleException(InvalidResourceExceptione) {
log.error("Invalid resource: {}", e.toString());
}
The generated API now showed all defined ApiResponses as responses for all controllers and endpoints. So I splittet the handler config using #ControllerAdvice(basePackageClasses = MyController.class) to group the possible exceptions. But there are still responses that are not fitting to all endpoints of a controller. Like:
#RestController
public class MyController {
#ResponseStatus(HttpStatus.CREATED)
#Operation(summary = "Create", description = "Create myResource!")
#PostMapping(value = "/myResources/", produces = {"application/json"})
#ResponseBody
public Integer create(#RequestBody MyResource newResource) throws InvalidResourceException {
return creationService.createResource(newResource).getId();
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Update", description = "Update myResource!")
#PutMapping(value = "/myResources/{id}", produces = {"application/json"})
public void update(#PathVariable("id") Integer id, #RequestBody MyResource newResource)
throws ResourceNotFoundException, InvalidResourceException {
return updateService.updateResource(id, newResource);
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
}
POST will never respond with my 'business' 404 and GET will never respond with my 'business' 400. Is it possible to annotate an endpoint, so that not possible response codes are hidden in the API?
I tried to override the responses, but didn't work as intended:
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
400 still shows up...
You need to remove the #ApiResponse from your #ControllerAdvice class and need to add the respective response in your controller class, as mentioned by you.
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}

Trying to send Http response to the frontend before method logic happens

What I am trying to accomplish is I have a controller that gets accessed from a frontend (Angular). The users uploads an array of images from the frontend and those images are sent and processed through the backend (Spring Boot). Before the images are processed, I would like to send a response (200) to the frontend so the user does not have to wait for the images to be processed. The code looks like so:
#CrossOrigin
#RestController
public class SolarController {
#Autowired
SolarImageServiceImpl solarImageService;
#Autowired
SolarVideoServiceImpl solarVideoService;
#ApiOperation(value = "Submit images")
#PostMapping(value="/solarImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void getUploadImages(#ApiParam(value = "Upload images", required = true) #RequestPart(value = "files") MultipartFile[] files,
#ApiParam(value = "User's LanId", required = true) #RequestParam(value = "lanID") String lanId,
#ApiParam(value = "Site name", required = true) #RequestParam(value = "siteName") String siteName,
#ApiParam(value = "User email", required = true) #RequestParam(value = "userEmail") String userEmail,
#ApiParam(value = "Inspection ID", required = true) #RequestParam(value = "inspectionID") String inspectionID) throws IOException{
if (!ArrayUtils.isEmpty(files)) {
this.solarImageService.uploadImages(files, lanId, siteName, userEmail, inspectionID);
}
I have looked at multiple other examples, as in using #Async over the method, using HttpServletResponse, and setting my own responses. But nothing is working.
Resolved.
#ApiOperation(value = "Submit images")
#PostMapping(value="/solarImage", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void getUploadImages(#ApiParam(value = "Upload images", required = true) #RequestPart(value = "files") MultipartFile[] files,
#ApiParam(value = "User's LanId", required = true) #RequestParam(value = "lanID") String lanId,
#ApiParam(value = "Site name", required = true) #RequestParam(value = "siteName") String siteName,
#ApiParam(value = "User email", required = true) #RequestParam(value = "userEmail") String userEmail,
#ApiParam(value = "Inspection ID", required = true) #RequestParam(value = "inspectionID") String inspectionID, HttpServletResponse response) throws IOException{
int code = (!ArrayUtils.isEmpty(files)) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code);
return;
}
PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.flush();
wr.close();
if (!ArrayUtils.isEmpty(files)) {
this.solarImageService.uploadImages(files, lanId, siteName, userEmail, inspectionID);
}
Sending the HttpServletResponse first did the trick. Annotating the method with #Async did not work.

Spring boot throws HttpMediaTypeNotAcceptableException: Could not find acceptable representation

Here are the 2 mappings I have in my class:
#Override
#RequestMapping(value="/getUserDetails", consumes={"application/xml", "text/xml"}, produces={"application/xml", "text/xml"}, method = RequestMethod.POST)
#ResponseBody
#ApiOperation(httpMethod = "GET", value = "Response for user details", notes = "Gets response for user details", response = GetUserDetailsResponse.class )
#ApiResponse(code = 200, message = "returns response for user details")
#Timed(name = "http.userservice.getUserDetails", absolute = true)
public GetUserDetailsResponse getUserDetails(
#RequestBody GetUserDetailsRequest request) throws ServiceException {
GetUserDetailsResponse response = new GetUserDetailsResponse();
String username = request.getUsername();
User user = createUser(username);
response.setUser(user);
response.setUsername(username);
return response;
}
#Override
#RequestMapping(value = "/", method = RequestMethod.GET)
#ResponseBody
#ApiOperation(value = "hello world" , response = String.class )
#ApiResponse(code = 200, message = "hello world")
public String helloWorld() {
return "hello world";
}
When I request localhost:8080/,
I am getting proper response i.e. "hello world". "/" is mapped to second method below.
But when I request for localhost:8080/getUserDetails with POST request, spring throws HttpMediaTypeNotAcceptableException.
Any idea?
Below is the xml data i am sending as part of POST request
<userDetailsRequest>
    <username>abc#cdk.com</username>
</userDetailsRequest>

400 (Bad Request) while sending json in Spring

I'm trying to send json string to Spring controller, i'm getting 400 - bad request as response
i'm using Spring 4.0.3
This is my controller
#Controller
public class Customer{
#RequestMapping(value = "/apis/test", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody String test(HttpServletRequest params) throws JsonIOException {
String json = params.getParameter("json");
JsonParser jObj = new JsonParser();
JsonArray jsonObj = (JsonArray ) jObj.parse(json);
for(int i = 0; i < jsonObj.size(); i++) {
JsonObject jsonObject = jsonObj.get(i).getAsJsonObject();
System.out.println(jsonObject.get("name").getAsString());
}
return json;
}
}
Please help me to solve this
#RequestMapping(value = "/apis/test", method = RequestMethod.GET, produces = "application/json")
The above means this is a HTTP GET method which does not normally accept data. You should be using a HTTP POST method eg:
#RequestMapping(value = "/apis/test", method = RequestMethod.POST, consumes = "application/json")
public #ResponseBody String test(#RequestParam final String param1, #RequestParam final String param2, #RequestBody final String body) throws JsonIOException {
then you can execute POST /apis/test?param1=one&param2=two and adding strings in the RequestBody of the request
I hope this helps!

Resources