diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/post/SendErrorFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/post/SendErrorFilter.java index 2cd857ef..547bdd3d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/post/SendErrorFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/post/SendErrorFilter.java @@ -17,6 +17,7 @@ package org.springframework.cloud.netflix.zuul.filters.post; import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; import lombok.extern.apachecommons.CommonsLog; @@ -59,19 +60,28 @@ public class SendErrorFilter extends ZuulFilter { public Object run() { try { RequestContext ctx = RequestContext.getCurrentContext(); + HttpServletRequest request = ctx.getRequest(); + int statusCode = (Integer) ctx.get("error.status_code"); + request.setAttribute("javax.servlet.error.status_code", statusCode); + if (ctx.containsKey("error.exception")) { Object e = ctx.get("error.exception"); log.warn("Error during filtering", Throwable.class.cast(e)); - ctx.getRequest().setAttribute("javax.servlet.error.exception", e); + request.setAttribute("javax.servlet.error.exception", e); } - ctx.getRequest().setAttribute("javax.servlet.error.status_code", statusCode); - RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher( + + if (ctx.containsKey("error.message")) { + String message = (String) ctx.get("error.message"); + request.setAttribute("javax.servlet.error.message", message); + } + + RequestDispatcher dispatcher = request.getRequestDispatcher( this.errorPath); if (dispatcher != null) { ctx.set(SEND_ERROR_FILTER_RAN, true); if (!ctx.getResponse().isCommitted()) { - dispatcher.forward(ctx.getRequest(), ctx.getResponse()); + dispatcher.forward(request, ctx.getResponse()); } } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java index bbef65c7..d951cc59 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RibbonRoutingFilter.java @@ -79,6 +79,7 @@ public class RibbonRoutingFilter extends ZuulFilter { } catch (ZuulException ex) { context.set("error.status_code", ex.nStatusCode); + context.set("error.message", ex.errorCause); context.set("error.exception", ex); } catch (Exception ex) { @@ -127,13 +128,14 @@ public class RibbonRoutingFilter extends ZuulFilter { } catch (HystrixRuntimeException ex) { info.put("status", "500"); - if (ex.getFallbackException() != null - && ex.getFallbackException().getCause() != null - && ex.getFallbackException().getCause() instanceof ClientException) { - ClientException cause = (ClientException) ex.getFallbackException() - .getCause(); - throw new ZuulException(cause, "Forwarding error", 500, - cause.getErrorType().toString()); + ClientException clientException = findClientException(ex); + if (clientException != null) { + int statusCode = 500; + if (clientException.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) { + statusCode = 503; + } + throw new ZuulException(clientException, "Forwarding error", statusCode, + clientException.getErrorType().toString()); } throw new ZuulException(ex, "Forwarding error", 500, ex.getFailureType().toString()); @@ -141,6 +143,19 @@ public class RibbonRoutingFilter extends ZuulFilter { } + protected ClientException findClientException(HystrixRuntimeException ex) { + if (ex.getCause() != null + && ex.getCause() instanceof ClientException) { + return (ClientException) ex.getCause(); + } + if (ex.getFallbackException() != null + && ex.getFallbackException().getCause() != null + && ex.getFallbackException().getCause() instanceof ClientException) { + return (ClientException) ex.getFallbackException().getCause(); + } + return null; + } + private InputStream getRequestBody(HttpServletRequest request) { InputStream requestEntity = null; // ApacheHttpClient4Handler does not support body in delete requests diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java index 3ddcbcd9..32f418e6 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/SampleZuulProxyApplicationTests.java @@ -16,12 +16,16 @@ package org.springframework.cloud.netflix.zuul; +import java.io.InputStream; +import java.net.URISyntaxException; import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import com.netflix.client.ClientException; +import com.netflix.client.http.HttpRequest; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; -import com.netflix.zuul.exception.ZuulException; +import com.netflix.niws.client.http.RestClient; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,9 +48,12 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -84,6 +91,14 @@ public class SampleZuulProxyApplicationTests extends ZuulProxyTestBase { assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode()); } + @Test + public void ribbonCommandServiceUnavailable() { + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/simple/throwexception/503", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.SERVICE_UNAVAILABLE, result.getStatusCode()); + } + @Test public void ribbonCommandBadHost() { ResponseEntity result = new TestRestTemplate().exchange( @@ -133,18 +148,41 @@ class SampleZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyAppl } @Override + @SuppressWarnings("deprecation") @SneakyThrows public RestClientRibbonCommand create(RibbonCommandContext context) { String uri = context.getUri(); if (uri.startsWith("/throwexception/")) { String code = uri.replace("/throwexception/", ""); - throw new ZuulException(new RuntimeException(), Integer.parseInt(code), - "test error"); + RestClient restClient = getClientFactory().getClient(context.getServiceId(), + RestClient.class); + return new MyCommand(Integer.parseInt(code), + context.getServiceId(), restClient, getVerb(context.getVerb()), + context.getUri(), context.getRetryable(), context.getHeaders(), + context.getParams(), context.getRequestEntity()); } return super.create(context); } } + static class MyCommand extends RestClientRibbonCommand { + + private int errorCode; + + public MyCommand(int errorCode, String commandKey, RestClient restClient, HttpRequest.Verb verb, String uri, Boolean retryable, MultiValueMap headers, MultiValueMap params, InputStream requestEntity) throws URISyntaxException { + super(commandKey, restClient, verb, uri, retryable, headers, params, requestEntity); + this.errorCode = errorCode; + } + + @Override + protected ClientHttpResponse forward() throws Exception { + if (errorCode == 503) { + throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED); + } + return new MockClientHttpResponse((byte[])null, HttpStatus.valueOf(this.errorCode)); + } + } + // Load balancer with fixed server list for "simple" pointing to localhost @Configuration static class BadHostRibbonClientConfiguration {