diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java index cba9d9fd..3b5e442d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java @@ -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 diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java index c1c22731..f7acade4 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java @@ -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); + } } })); }; diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index c067becd..fcd653fc 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -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()); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java index d8bbc51d..17975963 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java @@ -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 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 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(); + } + } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java index 5043e68b..305b5358 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java @@ -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 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()