Handle ClientException.SERVER_THROTTLED as 503
fixes gh-406
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> 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<String> 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<String, String> headers, MultiValueMap<String, String> 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 {
|
||||
|
||||
Reference in New Issue
Block a user