Skip to content

Commit 5b73fdb

Browse files
committed
Refactored HttpUtils to AbstractProxyExchange
This allows the streaming media types and the buffer size to be configured with new properties GatewayMvcProperties.streamingBufferSize and GatewayMvcProperties.streamingMediaTypes. See gh-3486
1 parent 6a1b21f commit 5b73fdb

File tree

6 files changed

+90
-29
lines changed

6 files changed

+90
-29
lines changed

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ public RestClientCustomizer gatewayRestClientCustomizer(ClientHttpRequestFactory
8888

8989
@Bean
9090
@ConditionalOnMissingBean(ProxyExchange.class)
91-
public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder) {
92-
return new RestClientProxyExchange(restClientBuilder.build());
91+
public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder,
92+
GatewayMvcProperties properties) {
93+
return new RestClientProxyExchange(restClientBuilder.build(), properties);
9394
}
9495

9596
@Bean
Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,29 @@
2020
import java.io.InputStream;
2121
import java.io.OutputStream;
2222

23+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
24+
import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange;
2325
import org.springframework.http.client.ClientHttpResponse;
2426
import org.springframework.util.Assert;
2527
import org.springframework.util.StreamUtils;
2628

27-
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
29+
public abstract class AbstractProxyExchange implements ProxyExchange {
2830

29-
public abstract class HttpUtils {
31+
private final GatewayMvcProperties properties;
3032

31-
private static final int BUFFER_SIZE = 16384;
32-
33-
private HttpUtils() {
34-
throw new AssertionError("Must not instantiate utility class.");
33+
protected AbstractProxyExchange(GatewayMvcProperties properties) {
34+
this.properties = properties;
3535
}
3636

37-
public static int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream,
37+
protected int copyResponseBody(ClientHttpResponse clientResponse, InputStream inputStream,
3838
OutputStream outputStream) throws IOException {
3939
Assert.notNull(clientResponse, "No ClientResponse specified");
4040
Assert.notNull(inputStream, "No InputStream specified");
4141
Assert.notNull(outputStream, "No OutputStream specified");
4242

4343
int transferredBytes;
4444

45-
if (TEXT_EVENT_STREAM.equals(clientResponse.getHeaders().getContentType())) {
45+
if (properties.getStreamingMediaTypes().contains(clientResponse.getHeaders().getContentType())) {
4646
transferredBytes = copyResponseBodyWithFlushing(inputStream, outputStream);
4747
}
4848
else {
@@ -52,11 +52,10 @@ public static int copyResponseBody(ClientHttpResponse clientResponse, InputStrea
5252
return transferredBytes;
5353
}
5454

55-
private static int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream outputStream)
56-
throws IOException {
55+
private int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream outputStream) throws IOException {
5756
int readBytes;
5857
var totalReadBytes = 0;
59-
var buffer = new byte[BUFFER_SIZE];
58+
var buffer = new byte[properties.getStreamingBufferSize()];
6059

6160
while ((readBytes = inputStream.read(buffer)) != -1) {
6261
outputStream.write(buffer, 0, readBytes);

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.time.Duration;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.LinkedHashMap;
2223
import java.util.List;
2324

@@ -26,6 +27,7 @@
2627

2728
import org.springframework.boot.context.properties.ConfigurationProperties;
2829
import org.springframework.core.style.ToStringCreator;
30+
import org.springframework.http.MediaType;
2931

3032
@ConfigurationProperties(GatewayMvcProperties.PREFIX)
3133
public class GatewayMvcProperties {
@@ -51,6 +53,18 @@ public class GatewayMvcProperties {
5153

5254
private HttpClient httpClient = new HttpClient();
5355

56+
/**
57+
* Mime-types that are streaming.
58+
*/
59+
private List<MediaType> streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM,
60+
new MediaType("application", "stream+json"), new MediaType("application", "grpc"),
61+
new MediaType("application", "grpc+protobuf"), new MediaType("application", "grpc+json"));
62+
63+
/**
64+
* Buffer size for streaming media mime-types.
65+
*/
66+
private int streamingBufferSize = 16384;
67+
5468
public List<RouteProperties> getRoutes() {
5569
return routes;
5670
}
@@ -71,11 +85,29 @@ public HttpClient getHttpClient() {
7185
return httpClient;
7286
}
7387

88+
public List<MediaType> getStreamingMediaTypes() {
89+
return streamingMediaTypes;
90+
}
91+
92+
public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
93+
this.streamingMediaTypes = streamingMediaTypes;
94+
}
95+
96+
public int getStreamingBufferSize() {
97+
return streamingBufferSize;
98+
}
99+
100+
public void setStreamingBufferSize(int streamingBufferSize) {
101+
this.streamingBufferSize = streamingBufferSize;
102+
}
103+
74104
@Override
75105
public String toString() {
76106
return new ToStringCreator(this).append("httpClient", httpClient)
77107
.append("routes", routes)
78108
.append("routesMap", routesMap)
109+
.append("streamingMediaTypes", streamingMediaTypes)
110+
.append("streamingBufferSize", streamingBufferSize)
79111
.toString();
80112
}
81113

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,28 @@
2020
import java.io.InputStream;
2121
import java.io.UncheckedIOException;
2222

23-
import org.springframework.cloud.gateway.server.mvc.common.HttpUtils;
23+
import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange;
2424
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
25+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
2526
import org.springframework.http.client.ClientHttpRequest;
2627
import org.springframework.http.client.ClientHttpRequestFactory;
2728
import org.springframework.http.client.ClientHttpResponse;
2829
import org.springframework.util.StreamUtils;
2930
import org.springframework.web.servlet.function.ServerResponse;
3031

31-
public class ClientHttpRequestFactoryProxyExchange implements ProxyExchange {
32+
public class ClientHttpRequestFactoryProxyExchange extends AbstractProxyExchange {
3233

3334
private final ClientHttpRequestFactory requestFactory;
3435

36+
@Deprecated
3537
public ClientHttpRequestFactoryProxyExchange(ClientHttpRequestFactory requestFactory) {
38+
super(new GatewayMvcProperties());
39+
this.requestFactory = requestFactory;
40+
}
41+
42+
public ClientHttpRequestFactoryProxyExchange(ClientHttpRequestFactory requestFactory,
43+
GatewayMvcProperties properties) {
44+
super(properties);
3645
this.requestFactory = requestFactory;
3746
}
3847

@@ -55,7 +64,7 @@ public ServerResponse exchange(Request request) {
5564
InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(),
5665
MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR);
5766
// copy body from request to clientHttpRequest
58-
HttpUtils.copyResponseBody(clientHttpResponse, inputStream,
67+
ClientHttpRequestFactoryProxyExchange.this.copyResponseBody(clientHttpResponse, inputStream,
5968
httpServletResponse.getOutputStream());
6069
}
6170
return null;

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,26 @@
2121
import java.io.OutputStream;
2222
import java.io.UncheckedIOException;
2323

24-
import org.springframework.cloud.gateway.server.mvc.common.HttpUtils;
24+
import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange;
2525
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
26+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
2627
import org.springframework.http.client.ClientHttpResponse;
2728
import org.springframework.util.StreamUtils;
2829
import org.springframework.web.client.RestClient;
2930
import org.springframework.web.servlet.function.ServerResponse;
3031

31-
public class RestClientProxyExchange implements ProxyExchange {
32+
public class RestClientProxyExchange extends AbstractProxyExchange {
3233

3334
private final RestClient restClient;
3435

36+
@Deprecated
3537
public RestClientProxyExchange(RestClient restClient) {
38+
super(new GatewayMvcProperties());
39+
this.restClient = restClient;
40+
}
41+
42+
public RestClientProxyExchange(RestClient restClient, GatewayMvcProperties properties) {
43+
super(properties);
3644
this.restClient = restClient;
3745
}
3846

@@ -60,7 +68,7 @@ private static int copyBody(Request request, OutputStream outputStream) throws I
6068
return StreamUtils.copy(request.getServerRequest().servletRequest().getInputStream(), outputStream);
6169
}
6270

63-
private static ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException {
71+
private ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException {
6472
InputStream body = clientResponse.getBody();
6573
// put the body input stream in a request attribute so filters can read it.
6674
MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body);
@@ -72,7 +80,8 @@ private static ServerResponse doExchange(Request request, ClientHttpResponse cli
7280
InputStream inputStream = MvcUtils.getAttribute(request.getServerRequest(),
7381
MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR);
7482
// copy body from request to clientHttpRequest
75-
HttpUtils.copyResponseBody(clientResponse, inputStream, httpServletResponse.getOutputStream());
83+
RestClientProxyExchange.this.copyResponseBody(clientResponse, inputStream,
84+
httpServletResponse.getOutputStream());
7685
}
7786
return null;
7887
});
Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
import org.junit.jupiter.api.Test;
2424

25+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
2526
import org.springframework.http.MediaType;
2627
import org.springframework.mock.http.client.MockClientHttpResponse;
28+
import org.springframework.web.servlet.function.ServerResponse;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
2931
import static org.mockito.ArgumentMatchers.any;
@@ -35,7 +37,7 @@
3537
/**
3638
* @author Jens Mallien
3739
*/
38-
public class HttpUtilsTests {
40+
public class AbstractProxyExchangeTests {
3941

4042
@Test
4143
public void copyResponseBodyForJson() throws IOException {
@@ -46,7 +48,7 @@ public void copyResponseBodyForJson() throws IOException {
4648
when(inputStream.transferTo(any())).thenReturn(3L);
4749
OutputStream outputStream = mock(OutputStream.class);
4850

49-
int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream);
51+
int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream);
5052

5153
assertThat(result).isEqualTo(3);
5254
verify(outputStream, times(1)).flush();
@@ -58,14 +60,10 @@ public void copyResponseBodyForTextEventStream() throws IOException {
5860
mockResponse.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
5961

6062
InputStream inputStream = mock(InputStream.class);
61-
when(inputStream.read(any()))
62-
.thenReturn(1)
63-
.thenReturn(1)
64-
.thenReturn(1)
65-
.thenReturn(-1);
63+
when(inputStream.read(any())).thenReturn(1).thenReturn(1).thenReturn(1).thenReturn(-1);
6664
OutputStream outputStream = mock(OutputStream.class);
6765

68-
int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream);
66+
int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream);
6967

7068
assertThat(result).isEqualTo(3);
7169
verify(outputStream, times(4)).flush();
@@ -79,10 +77,23 @@ public void copyResponseBodyWithoutContentType() throws IOException {
7977
when(inputStream.transferTo(any())).thenReturn(3L);
8078
OutputStream outputStream = mock(OutputStream.class);
8179

82-
int result = HttpUtils.copyResponseBody(mockResponse, inputStream, outputStream);
80+
int result = new TestProxyExchange().copyResponseBody(mockResponse, inputStream, outputStream);
8381

8482
assertThat(result).isEqualTo(3);
8583
verify(outputStream, times(1)).flush();
8684
}
8785

86+
class TestProxyExchange extends AbstractProxyExchange {
87+
88+
protected TestProxyExchange() {
89+
super(new GatewayMvcProperties());
90+
}
91+
92+
@Override
93+
public ServerResponse exchange(Request request) {
94+
return ServerResponse.ok().build();
95+
}
96+
97+
}
98+
8899
}

0 commit comments

Comments
 (0)