Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch application errors to display standardized error page #122

Merged
merged 1 commit into from
May 27, 2024

Conversation

emmdurin
Copy link
Contributor

@emmdurin emmdurin commented May 16, 2024

We wanted to show a standardized error page when an application behind the gateway returns an HTTP 4xx or 5xx error, instead of an application specific error page.

It uses error pages templates embedded in gateway or overriden in gateway/template/error directory of datadir.

To enable it globally, add this to gateway/application.yaml of datadir :

spring:
  cloud:
    gateway:
      default-filters:
        - ApplicationError

To enable it only on some routes, add this to concerned routes in gateway/routes.yaml of datadir :

        filters:
        - name: ApplicationError

A side effect that can be discussed is that it prints a stack trace in gateway log as implementation throws an exception. Some things can be done differently, like using a more specific exception class, or catch it in a AbstractErrorWebExceptionHandler subclass to maybe avoid to print it in log, or do not throw an exception but replace content of response by content of 500.html template. Suggestions are welcome.

Example of log with printed stacktrace :

gateway-1  | 2024-05-16 07:56:41.462 DEBUG 1 --- [or-http-epoll-7] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/auth/login', method=null}
gateway-1  | 2024-05-16 07:56:41.462 DEBUG 1 --- [or-http-epoll-7] athPatternParserServerWebExchangeMatcher : Request 'GET /console/a' doesn't match 'null /auth/login'
gateway-1  | 2024-05-16 07:56:41.462 DEBUG 1 --- [or-http-epoll-7] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
gateway-1  | 2024-05-16 07:56:41.463 DEBUG 1 --- [or-http-epoll-7] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@e26b7e1'
gateway-1  | 2024-05-16 07:56:41.479 ERROR 1 --- [or-http-epoll-7] a.w.r.e.AbstractErrorWebExceptionHandler : [a3de8979-61]  500 Server Error for HTTP GET "/console/a"
gateway-1  | 
gateway-1  | java.lang.RuntimeException: SERVICE ERROR
gateway-1  |    at org.georchestra.gateway.filter.headers.ServiceErrorGatewayFilterFactory$ServiceErrorGatewayFilter.lambda$filter$0(ServiceErrorGatewayFilterFactory.java:43) ~[classes/:23.1-SNAPSHOT]
gateway-1  |    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
gateway-1  | Error has been observed at the following site(s):
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
gateway-1  |    *__checkpoint ? HTTP GET "/console/a" [ExceptionHandlingWebHandler]
gateway-1  | Original Stack Trace:
gateway-1  |            at org.georchestra.gateway.filter.headers.ServiceErrorGatewayFilterFactory$ServiceErrorGatewayFilter.lambda$filter$0(ServiceErrorGatewayFilterFactory.java:43) ~[classes/:23.1-SNAPSHOT]
gateway-1  |            at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73) ~[reactor-core-3.4.29.jar:3.4.29]
                        < stack trace continues about 50 lines >

@emmdurin emmdurin requested review from pmauduit and f-necas May 16, 2024 07:46
@pmauduit
Copy link
Member

poke @groldan I think DT's request is similar to what is intended here.

return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (exchange.getResponse().getStatusCode().is4xxClientError() || exchange.getResponse().getStatusCode().is5xxServerError()) {
// log error ?
throw new RuntimeException("SERVICE ERROR");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be ideal if we throw an exception that preserves the status code. I can't remember how they're called right now though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org.springframework.web.server.ResponseStatusException that's it

Copy link
Member

@groldan groldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, as discussed in our meeting, it'd be great if this filter could be enabled through a config property, being disabled by default

@groldan
Copy link
Member

groldan commented May 16, 2024

Also, as discussed in our meeting, it'd be great if this filter could be enabled through a config property, being disabled by default

either way, whether it's enabled or disabled by default, just make sure to add an entry in the security docs

@emmdurin emmdurin changed the title Catch service errors to display standardized error page Catch application errors to display standardized error page May 23, 2024
@emmdurin emmdurin marked this pull request as ready for review May 23, 2024 20:42
…teway returns an error.

`GatewayFilterFactory` providing a `GatewayFilter` that throws a
`ResponseStatusException` with the proxied response status code if the
target responded with a `400` or `500` status code.

Usage: to enable it globally, add this to application.yaml :

```
 spring:
  cloud:
    gateway:
      default-filters:
        - ApplicationError
```

To enable it only on some routes, add this to concerned routes
in `routes.yaml`:

```
        filters:
       - name: ApplicationError
```
@groldan groldan merged commit 1b8f1b1 into main May 27, 2024
3 checks passed
@groldan groldan deleted the catch_service_errors branch May 27, 2024 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants