Spring redirect from ErrorController - spring-boot

I have a number of generic error pages which are used by multiple applications other than the one I have control of. I would like to configure the Spring Boot error controller to do a redirect to one of these pages. Unfortunately it's not working.
Eg.
#Controller
public class MyCustomErrorController implements ErrorController {
#GetMapping(value = "/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "redirect:https://www.example.com/error-404";
}
}
return "redirect:https://www.example.com/error-500";
}
#Override
public String getErrorPath() {
return "/error";
}
}
If for example I purposefully mistype a URL I can see the response has the Location header with the 404 URL I am expecting but the browser doesn't actually redirect. Any ideas if it's possible to do a redirect from within a custom ErrorController?
Could this be because I'm trying to test this from localhost, and Strict-Transport-Security is ignoring the response Location header value (which is on a FQDN)?

How about adding HttpServletResponse to the method param and use it for redirection?
HttpServletResponse response;
response.sendRedirect("https://www.example.com/error-404");
Reference HERE

Add the following properties in application.proerties file
server.error.whitelabel.enabled=false

try this.
#Controller
public class MyCustomErrorController implements ErrorController {
#GetMapping(value = "/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "redirect:/error-404"; //remove your https://www.example.com
}
}
return "redirect:/error-500";
}
#Override
public String getErrorPath() {
return "/error";
}
}
** EDIT **
change the url mapping and try again:
error-404 -> error/404
error-500 -> error/500
#Controller
public class MyCustomErrorController implements ErrorController {
#GetMapping(value = "/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "redirect:/error/404"; //remove your https://www.example.com
}
}
return "redirect:/error/500";
}
#Override
public String getErrorPath() {
return "/error";
}
}
error/404
#GetMapping("/error/404")
error/500
#GetMapping("/error/500")

Related

FeignException com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.http.ResponseEntity`

Any Help please !!
I receive this error when I'm calling my endpoint which call Feign in the background :
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
`org.springframework.http.ResponseEntity` (no Creators, like default constructor, exist): cannot deserialize
from Object value (no delegate- or property-based Creator)
at [Source: (BufferedReader); line: 1, column: 2]
This is my endpoint inside Controller :
#RestController
#RequestMapping(Routes.URI_PREFIX)
public class CartoController {
#Autowired
private ReadCartographyApiDelegate readCartographyApiDelegate;
#GetMapping(value = "/cartographies/{uid}", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseWrapper<ReadCartographyResponse> readCarto(HttpServletRequest request,
#PathVariable(name = "uid") String uid) {
ResponseEntity<ReadCartographyResponse> result ;
try {
result = readCartographyApiDelegate.readCartography(uid);
}catch (Exception e){
throw new TechnicalException("Error during read Carto");
}
return responseWrapperWithIdBuilder.of(result.getBody());
}
}
Interface ReadCartographyApiDelegate generated automatically by openApi from yaml file :
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "...")
public interface ReadCartographyApiDelegate {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
default ResponseEntity<ReadCartographyResponse> readCartography(String uid) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "null";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
This my ReadCartoApiDelegateImpl which implements ReadCartographyApiDelegate interface :
#Service
public class ReadCartographyApiDelegateImpl implements ReadCartographyApiDelegate {
private EcomGtmClient ecomGtmClient;
public ReadCartographyApiDelegateImpl(EcomGtmClient ecomGtmClient) {
this.ecomGtmClient = ecomGtmClient;
}
#Override
public ResponseEntity<ReadCartographyResponse> readCartography(String uid) {
ResponseEntity<ReadCartographyResponse> response = ecomGtmClient.readCartography(uid);
return response;
}
}
This is the feign client :
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ResponseEntity<ReadCartographyResponse> readCartography(#PathVariable("uid") String uid);
}
The problem is that ResponseEntity (spring class) class doesn't contain default constructor which is needed during creating of instance. is there Any config to resolve this issue ?
If you want access to the body or headers on feign responses, you should use the feign.Response class. ResponseEntity does not work with feign because it is not meant to. I think it is best if you just return Response from your feign client method. You should then be able to pass the body to the ResponseEntity instance in the Controller.
What is your reason to even use the response-wrapper, i can't really figure that out from your code?
Sadly I couldn't find any documentation on the Response class, but here's the link to the source on GitHub.
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Response.java
My Suggestion would be
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ReadCartographyResponse readCartography(#PathVariable("uid") String uid);
}
#RestController
#RequestMapping(Routes.URI_PREFIX)
public class CartoController {
#Autowired
private ReadCartographyApiDelegate readCartographyApiDelegate;
#GetMapping(value = "/cartographies/{uid}", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseWrapper<ReadCartographyResponse> readCarto(HttpServletRequest request,
#PathVariable(name = "uid") String uid) {
ReadCartographyResponse result ;
try {
result = readCartographyApiDelegate.readCartography(uid);
}catch (Exception e){
throw new TechnicalException("Error during read Carto");
}
// I don't know where you get the builder from, so I assume it does something import and is needed
return responseWrapperWithIdBuilder.of(result);
}
}
Of course you'd also have to change all intermediate classes.
The Response Output was the correct Object that I have to put, cause every time I need to check the status from my feign client endpoint to do différent logic
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ReadCartographyResponse readCartography(#PathVariable("uid") String uid);
}

What is the best way to return different types of ResponseEntity in Spring-Boot

I would like to return two different response for a spring boot rest API.
I should not be using <?> wild card as i get the sonar issue "Generic wildcard types should not be used in return types"
My code:
#GetMapping(path = {"/v1/{type}"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> method(#PathVariable(value = "type") boolean type) {
boolean b = type;// some logic
if (b) {
Success result=new Success();
result.setSuccess("Yes");
return new ResponseEntity<>(result,HttpStatus.OK);
}
else {
Error result=new Error();
result.setError("No");
return new ResponseEntity<>(result,HttpStatus.CONFLICT); //appropriate error code
}
}
Any idea how to handle this situation.
Update:
public interface MyResponse{
public Success getSuccessObj();
public Error getErrorObj();
}
#Service
public class Success implements MyResponse {
public Error getErrorObj(){
return null;
}
public Success getSuccessObj(){
Success s=new Success();
return s;
}
#Service
public class Error implements MyResponse {
public Error getErrorObj(){
Error e=new Error();
return e;
}
public Success getSuccessObj(){
return null;
}
Not claiming to be "the best way", but one approach can be:
Introduce:
package com.my.package;
public interface MyResponseI { //if Error, Success (and others) have more "in common", you can also introduce an (abstract) class (with fields, methods, etc.)!
}
"Implement"/Extend:
public class Success implements com.my.package.MyResponseI { //everything else can stay}
as
public class Error implements com.my.package.MyResponseI { //everything else can stay}
Use as Response Type:
#...
public ResponseEntity<com.my.package.MyResponseI> ...
(on client side distinguish).
..and in "your domain" (error, success, ...), you are free to use any "tweaks" of a object oriented design.
Useful links/entries:
https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/
https://swagger.io/resources/articles/best-practices-in-api-design/
https://www.google.com/search?q=rest+api+design
, but also
https://www.google.com/search?q=object+oriented+design
and https://www.google.com/search?q=domain+driven+design
This should work
I tried the snippet below by myself and it worked for me:
#GetMapping("/testresponse/{id}")
public ResponseEntity<?> testMyResponse(#PathVariable("id") int id)
{
if(id==1)
return ResponseEntity.ok(new Success());
else return new ResponseEntity<>(new Error(), HttpStatus.CONFLICT);
}
public class Success {
private String msg = "Success";
public String getMsg() {
return msg;
}
}
public class Error {
private String msg = "Error";
public String getMsg() {
return msg;
}
}
EDIT: The solution as below doesn't work
You should also define an interface for both Success and Error classes. Let say the interface MyResponse
And then change your method declaration, it would look like this
public ResponseEntity<MyResponse> method(#PathVariable(value = "type") boolean type)
If so, the return statement, could be:
return new ResponseEntity<>(result, HttpStatus.OK);
Or
//for status 200 OK
return ResponseEntity.ok(result);

Netflix Feign custom error decoder doesn't work correctly

I have a problem with custom error decoder in netflix Feign.
I have a service A and a service B , service A is sending something to service B :
#FeignClient(value = "A", url = "${B.url}", configuration = MyConfig.class)
public interface NotificationClient {
#DeleteMapping(value = "/somepath", consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> sendSomething(MyObject myObject);
}
And MyConfig looks like:
#Configuration
public class MyConfig {
#Bean
ErrorDecoder errorDecoder() {
return new MyErrorDecoder();
}
}
And MyErrorDecoder :
public class MyErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyClientException(response.status(), response.reason(), response.request().url());
}
if (response.status() >= 500 && response.status() <= 599) {
return new MyServerException(response.status(), response.reason(), response.request().url());
}
return errorStatus(methodKey, response);
}
}
And for example MyClientException :
#Getter
public class MyClientException extends RuntimeException {
private static final long serialVersionUID = 8021218722733395779L;
private final int httpStatusCode;
public MyClientException(int status, String reason, String url){
super(String.format("Error when sending request to: ' %s ' : status: %d %s", url, status, reason != null ? reason : ""));
this.httpStatusCode = status;
}
}
My problem is that I have a validation in service 'B', and it returns http status code 400, but in my service 'A' i got status 500 and message like "Error when sending request to: ' url_from_properties/somepath ' : status: 400 ".. why? I want to have the same status code in service 'A' what i got in service 'B'.
I looking for answer but nothing works exactly like i expect. What is wrong here?

Default return error for white label erorr

I have this controller which is open when user is redirected.
#Controller()
public class RedirectController {
#RequestMapping(method = RequestMethod.GET, value = "/redirect")
#ResponseBody
public String handleRedirectMessage(HttpServletRequest request) throws Exception {
......
}
}
But when the match is not found I get Whitelabel Error Page. Is there some way to set default handler which is by default open when path is not correct?
You need add following property:
server.error.whitelabel.enabled=false
And implement org.springframework.boot.web.servlet.error.ErrorController interface and overrides its getErrorPath() to return a custom path to call when an error occurred:
#Controller
public class MyErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError() {
//do something like logging
return "error";
}
#Override
public String getErrorPath() {
return "/error";
}
}
After that add error page for path "/error" Thymeleaf error.html.
Also you could add pager for different errors:
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error-500";
}
}
return "error";
}
You can find here full code example
and description.

WebClient is not successfully invoking "POST" operation

I am playing with Spring's WebClient. The primary implementation of the REST endpoints (in DemoPOJORouter and DemoPOJOHandler) seem to work. Also, the http.Get endpoint in DemoClientRouter and DemoClientHandler seems to work.
But, the http.Post for the DemoClient implementation "does nothing". It returns success (200), but nothing gets added to the dummy repo. I have a feeling that I need to do something in DemoClient to cause the http.Post endpoint in DemoPOJOHandler to actually execute (i.e., I believe neither the statements in DemoPOJOService.add() nor DemoPOJORepo.add() are being executed).
Based on prior pratfalls in WebFlux/reactive/functional efforts, I have a feeling that I'm not successfully subscribing, and so the statements never are invoked. But, I'm having difficulty identifying the "why".
Test code follows...
DemoClient router...
#Configuration
public class DemoClientRouter {
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoClient/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoClient"), requestHandler::add)));
}
}
DemoClient handler...
#Component
public class DemoClientHandler {
public static final String PATH_VAR_ID = "id";
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO;
int id;
// short-circuit if bad request or invalid value for id
id = getIdFromServerRequest(request);
if (id < 1) {
return ServerResponse.badRequest().build();
}
// non-blocking mechanism for either returning the Mono<DemoPOJO>
// or an empty response if Mono<Void> was returned by repo.getById()
return demoClient.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(demoPOJO), DemoPOJO.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> add(ServerRequest request) {
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.ok().build())
.onErrorResume(e -> simpleErrorReporter(e))
.switchIfEmpty(ServerResponse.badRequest().build());
}
private int getIdFromServerRequest(ServerRequest request) {
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
// short-circuit if bad request
// should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return id;
}
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch (NumberFormatException e) {
// swallow the error, return value <0 to signal error
id = -1;
}
return id;
}
private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
return ServerResponse.badRequest()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(e.getMessage());
}
}
DemoClient impl...
#Component
public class DemoClient {
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Mono<DemoPOJO> getById(int id) {
return client.get().uri("http://localhost:8080/v2/DemoPOJO/" + id)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(DemoPOJO.class));
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return client.post().uri("http://localhost:8080/v2/DemoPOJO")
.syncBody(demoPOJO)
.exchange()
.flatMap(response -> response.bodyToMono(Boolean.class));
}
}
And, the DemoPOJO stuff, starting with DemoPOJORouter...
#Configuration
public class DemoPOJORouter {
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoPOJO/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
DemoPOJOHandler...
#Component
public class DemoPOJOHandler {
public static final String PATH_VAR_ID = "id";
#Autowired
private DemoPOJOService service;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO;
int id;
// short-circuit if bad request or invalid value for id
id = getIdFromServerRequest(request);
if (id < 1) {
return ServerResponse.badRequest().build();
}
// non-blocking mechanism for either returning the Mono<DemoPOJO>
// or an empty response if Mono<Void> was returned by repo.getById()
return service.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(demoPOJO), DemoPOJO.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> add(ServerRequest request) {
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> service.add(demoPOJO))
.then(ServerResponse.ok().build())
.onErrorResume(e -> simpleErrorReporter(e))
.switchIfEmpty(ServerResponse.badRequest().build());
}
private int getIdFromServerRequest(ServerRequest request) {
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
// short-circuit if bad request
// should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return id;
}
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch (NumberFormatException e) {
// swallow the exception, return illegal value to signal error
id = -1;
}
return id;
}
private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
return ServerResponse.badRequest()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(e.getMessage());
}
}
DemoPOJOService...
#Component
public class DemoPOJOService {
#Autowired
private DemoPOJORepo demoPOJORepo;
public Mono<DemoPOJO> getById(int id) {
DemoPOJO demoPOJO = demoPOJORepo.getById(id);
return (demoPOJO == null) ? Mono.empty()
: Mono.just(demoPOJO);
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return Mono.just(demoPOJORepo.add(demoPOJO));
}
}
DemoPOJORepo...
#Component
public class DemoPOJORepo {
private static final int NUM_OBJS = 5;
private static DemoPOJORepo demoRepo = null;
private Map<Integer, DemoPOJO> demoPOJOMap;
private DemoPOJORepo() {
initMap();
}
public static DemoPOJORepo getInstance() {
if (demoRepo == null) {
demoRepo = new DemoPOJORepo();
}
return demoRepo;
}
public DemoPOJO getById(int id) {
return demoPOJOMap.get(id);
}
public boolean add(DemoPOJO demoPOJO) throws InvalidParameterException {
// short-circuit on null pointer or duplicate id
if (demoPOJO == null) {
throw new InvalidParameterException("Add failed, null object detected...");
} else if (demoPOJOMap.containsKey(demoPOJO.getId())) {
throw new InvalidParameterException("Add failed, duplicate id detected...");
}
demoPOJOMap.put(demoPOJO.getId(), demoPOJO);
// if the return statement is reached, then the new demoPOJO was added
return true;
}
}
Finally, DemoPOJO...
public class DemoPOJO {
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(int id) {
this(id, DEF_NAME, DEF_VALUE);
}
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
this.id = id;
this.name = name;
this.value = value;
}
/*
* setters and getters go here
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append(" :: ");
builder.append(name);
builder.append(" :: ");
builder.append(value);
return builder.toString();
}
}
Here is probably your problem.
DemoPOJOHandler.class
request.bodyToMono(DemoPOJO.class).doOnSuccess(demoPOJO -> service.add(demoPOJO))
DemoPOJOService.class
public Mono<Boolean> add(DemoPOJO demoPOJO) {
return Mono.just(demoPOJORepo.add(demoPOJO));
}
doOnSuccess returns Void, but you are calling a method that wraps the "action" in a returning Mono. So the demoPOJORepo#add function will never be triggered because you have broken the event chain here. The easiest fix is to just remove the wrapping Mono and return void.
public void add(DemoPOJO demoPOJO) {
demoPOJORepo.add(demoPOJO);
}
This took me way to long to find so here are some pointers when asking a question.
The names of your classes are too like each other, it was hard to follow the codeflow.
DemoPOJOService service your names are so alike so when i saw service was it the DemoPOJOService or the DemoClientService? clear names please.
There is nothing called http.POST when you wrote that i had no idea what you where talking about.
you had problems with the POST part but you posted everything, even the working GET parts, please only post code you suspect is relevant and are part of the problem.
Explain the question more clearly, what you have done, how you do it, what your application structure is and so fourth
Your endpoint urls say nothing "/DemoClient"?
How this question could have been asked to be more clear:
I have two endpoints in two routers in the same spring reactive
application.
When I do a POST request to the "/add" endpoint, this endpoint in turn
makes an a POST call using a WebClient to the same application just on
another endpoint called "/addToMap".
When this first call returns, it returns me a 200 OK status but when i
check the map (that the second endpoint is supposed to add the posted
data to) nothing gets added.
So please, next time asking a question, be clear, very clear, a lot clearer than you think. make sure your code is clear too with good variable and class names and clear url names. If you have messy names on your own computer its fine but when posting here be polite and clean up the code .It takes 5 minutes to add good names to classes and parameters so that we understand your code quicker.
take the time to read the "how to ask a good question" please.
How to ask a good question

Resources