Allows use of custom http status codes.

fixes gh-284
This commit is contained in:
Spencer Gibb
2018-04-19 15:37:04 -04:00
parent 64831fd568
commit ce76b5c169
5 changed files with 85 additions and 21 deletions

View File

@@ -22,7 +22,6 @@ import java.util.List;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.http.client.HttpClient;
@@ -30,10 +29,12 @@ import reactor.ipc.netty.http.client.HttpClientRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
@@ -117,7 +118,15 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);
response.getHeaders().putAll(filteredResponseHeaders);
response.setStatusCode(HttpStatus.valueOf(res.status().code()));
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
} else {
throw new IllegalStateException("Unable to set status code on response: " +res.status().code()+", "+response.getClass());
}
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter

View File

@@ -20,14 +20,15 @@ package org.springframework.cloud.gateway.filter.factory;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
@@ -47,6 +48,12 @@ public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<
@Override
public GatewayFilter apply(Config config) {
final HttpStatus status = ServerWebExchangeUtils.parse(config.status);
final Integer intStatus;
if (status == null) {
intStatus = Integer.parseInt(config.status);
} else {
intStatus = null;
}
return (exchange, chain) -> {
// option 1 (runs in filter order)
@@ -61,7 +68,11 @@ public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<
// check not really needed, since it is guarded in setStatusCode,
// but it's a good example
if (!exchange.getResponse().isCommitted()) {
setResponseStatus(exchange, status);
if (status != null) { // standard status
setResponseStatus(exchange, status);
} else if (intStatus != null && exchange.getResponse() instanceof AbstractServerHttpResponse) { //non-standard
((AbstractServerHttpResponse)exchange.getResponse()).setStatusCodeValue(intStatus);
}
}
}));
};

View File

@@ -83,7 +83,7 @@ public class ServerWebExchangeUtils {
try {
int status = Integer.parseInt(statusString);
httpStatus = HttpStatus.valueOf(status);
httpStatus = HttpStatus.resolve(status);
} catch (NumberFormatException e) {
// try the enum string
httpStatus = HttpStatus.valueOf(statusString.toUpperCase());

View File

@@ -19,21 +19,27 @@ package org.springframework.cloud.gateway.filter.factory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.reactive.function.client.ClientResponse;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.test.TestUtils.assertStatus;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@@ -51,23 +57,45 @@ public class SetStatusGatewayFilterFactoryTests extends BaseWebClientTests {
}
private void setStatusStringTest(String host, HttpStatus status) {
Mono<ClientResponse> result = webClient.get()
testClient.get()
.uri("/headers")
.header("Host", host)
.exchange();
.exchange()
.expectStatus().isEqualTo(status);
}
StepVerifier.create(result)
.consumeNextWith(
response -> {
assertStatus(response, status);
})
.expectComplete()
.verify(DURATION);
@Test
public void nonStandardCodeWorks() {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.HOST, "www.setcustomstatus.org");
ResponseEntity<String> response = new TestRestTemplate().exchange(baseUri + "/headers",
HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertThat(response.getStatusCodeValue()).isEqualTo(432);
// https://jira.spring.io/browse/SPR-16748
/*testClient.get()
.uri("/status/432")
.exchange()
.expectStatus().isEqualTo(432)
.expectBody(String.class).isEqualTo("Failed with 432");*/
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig { }
public static class TestConfig {
@Value("${test.uri}")
String uri;
@Bean
public RouteLocator myRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("test_custom_http_status", r -> r.host("*.setcustomstatus.org")
.filters(f -> f.setStatus(432))
.uri(uri))
.build();
}
}
}

View File

@@ -26,8 +26,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -51,6 +53,20 @@ public class HttpStatusTests extends BaseWebClientTests {
.expectBody(String.class).isEqualTo("Failed with 404");
}
@Test
public void nonStandardCodeWorks() {
ResponseEntity<String> response = new TestRestTemplate().getForEntity(baseUri + "/status/432", String.class);
assertThat(response.getStatusCodeValue()).isEqualTo(432);
assertThat(response.getBody()).isEqualTo("Failed with 432");
/*testClient.get()
.uri("/status/432")
.exchange()
.expectStatus().isEqualTo(432)
.expectBody(String.class).isEqualTo("Failed with 432");*/
}
@Test
public void serverErrorResponseWorks() {
testClient.get()