Allows use of custom http status codes.
fixes gh-284
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user