Removes use of stream() in hot path.
This includes `filter()` and `test()`. Fixes gh-2197
This commit is contained in:
@@ -130,10 +130,15 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
|
||||
}
|
||||
|
||||
// TODO: use framework if possible
|
||||
// TODO: port to WebClientWriteResponseFilter
|
||||
private boolean isStreamingMediaType(@Nullable MediaType contentType) {
|
||||
return (contentType != null && this.streamingMediaTypes.stream()
|
||||
.anyMatch(contentType::isCompatibleWith));
|
||||
if (contentType != null) {
|
||||
for (int i = 0; i < streamingMediaTypes.size(); i++) {
|
||||
if (streamingMediaTypes.get(i).isCompatibleWith(contentType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -31,6 +31,7 @@ import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||
import org.springframework.web.reactive.socket.WebSocketMessage;
|
||||
import org.springframework.web.reactive.socket.WebSocketSession;
|
||||
@@ -45,7 +46,6 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.P
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted;
|
||||
import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -105,17 +105,26 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
|
||||
|
||||
List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
|
||||
if (protocols != null) {
|
||||
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream().flatMap(
|
||||
header -> Arrays.stream(commaDelimitedListToStringArray(header)))
|
||||
.map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
List<String> protocols = getProtocols(headers);
|
||||
|
||||
return this.webSocketService.handleRequest(exchange, new ProxyWebSocketHandler(
|
||||
requestUrl, this.webSocketClient, filtered, protocols));
|
||||
}
|
||||
|
||||
/* for testing */ List<String> getProtocols(HttpHeaders headers) {
|
||||
List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
|
||||
if (protocols != null) {
|
||||
ArrayList<String> updatedProtocols = new ArrayList<>();
|
||||
for (int i = 0; i < protocols.size(); i++) {
|
||||
String protocol = protocols.get(i);
|
||||
updatedProtocols.addAll(
|
||||
Arrays.asList(StringUtils.tokenizeToStringArray(protocol, ",")));
|
||||
}
|
||||
protocols = updatedProtocols;
|
||||
}
|
||||
return protocols;
|
||||
}
|
||||
|
||||
/* for testing */ List<HttpHeadersFilter> getHeadersFilters() {
|
||||
if (this.headersFilters == null) {
|
||||
this.headersFilters = this.headersFiltersProvider
|
||||
@@ -138,11 +147,11 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
|
||||
|
||||
headersFilters.add((headers, exchange) -> {
|
||||
HttpHeaders filtered = new HttpHeaders();
|
||||
headers.entrySet().stream()
|
||||
.filter(entry -> !entry.getKey().toLowerCase()
|
||||
.startsWith("sec-websocket"))
|
||||
.forEach(header -> filtered.addAll(header.getKey(),
|
||||
header.getValue()));
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
if (!entry.getKey().toLowerCase().startsWith("sec-websocket")) {
|
||||
filtered.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -146,7 +147,7 @@ public class DedupeResponseHeaderGatewayFilterFactory extends
|
||||
headers.set(name, values.get(values.size() - 1));
|
||||
break;
|
||||
case RETAIN_UNIQUE:
|
||||
headers.put(name, values.stream().distinct().collect(Collectors.toList()));
|
||||
headers.put(name, new ArrayList<>(new LinkedHashSet<>(values)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -87,8 +87,13 @@ public class RetryGatewayFilterFactory
|
||||
// null status code might mean a network exception?
|
||||
if (!retryableStatusCode && statusCode != null) {
|
||||
// try the series
|
||||
retryableStatusCode = retryConfig.getSeries().stream()
|
||||
.anyMatch(series -> statusCode.series().equals(series));
|
||||
retryableStatusCode = false;
|
||||
for (int i = 0; i < retryConfig.getSeries().size(); i++) {
|
||||
if (statusCode.series().equals(retryConfig.getSeries().get(i))) {
|
||||
retryableStatusCode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean finalRetryableStatusCode = retryableStatusCode;
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -88,9 +88,13 @@ public class RewriteResponseHeaderGatewayFilterFactory extends
|
||||
}
|
||||
|
||||
protected List<String> rewriteHeaders(Config config, List<String> headers) {
|
||||
return headers.stream()
|
||||
.map(val -> rewrite(val, config.getRegexp(), config.getReplacement()))
|
||||
.collect(Collectors.toList());
|
||||
ArrayList<String> rewrittenHeaders = new ArrayList<>();
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
String rewriten = rewrite(headers.get(i), config.getRegexp(),
|
||||
config.getReplacement());
|
||||
rewrittenHeaders.add(rewriten);
|
||||
}
|
||||
return rewrittenHeaders;
|
||||
}
|
||||
|
||||
String rewrite(String value, String regexp, String replacement) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -64,11 +63,25 @@ public class StripPrefixGatewayFilterFactory
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
addOriginalRequestUrl(exchange, request.getURI());
|
||||
String path = request.getURI().getRawPath();
|
||||
String newPath = "/"
|
||||
+ Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
|
||||
.skip(config.parts).collect(Collectors.joining("/"));
|
||||
newPath += (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
|
||||
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
|
||||
String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
|
||||
|
||||
// all new paths start with /
|
||||
StringBuilder newPath = new StringBuilder("/");
|
||||
for (int i = 0; i < originalParts.length; i++) {
|
||||
if (i >= config.getParts()) {
|
||||
// only append slash if this is the second part or greater
|
||||
if (newPath.length() > 1) {
|
||||
newPath.append('/');
|
||||
}
|
||||
newPath.append(originalParts[i]);
|
||||
}
|
||||
}
|
||||
if (newPath.length() > 1 && path.endsWith("/")) {
|
||||
newPath.append('/');
|
||||
}
|
||||
|
||||
ServerHttpRequest newRequest = request.mutate().path(newPath.toString())
|
||||
.build();
|
||||
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
|
||||
newRequest.getURI());
|
||||
|
||||
@@ -98,9 +98,11 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
// copy all headers except Forwarded
|
||||
original.entrySet().stream().filter(
|
||||
entry -> !entry.getKey().toLowerCase().equalsIgnoreCase(FORWARDED_HEADER))
|
||||
.forEach(entry -> updated.addAll(entry.getKey(), entry.getValue()));
|
||||
for (Map.Entry<String, List<String>> entry : original.entrySet()) {
|
||||
if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) {
|
||||
updated.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));
|
||||
|
||||
|
||||
@@ -31,19 +31,18 @@ public interface HttpHeadersFilter {
|
||||
|
||||
static HttpHeaders filter(List<HttpHeadersFilter> filters, HttpHeaders input,
|
||||
ServerWebExchange exchange, Type type) {
|
||||
HttpHeaders response = input;
|
||||
if (filters != null) {
|
||||
HttpHeaders reduce = filters.stream()
|
||||
.filter(headersFilter -> headersFilter.supports(type)).reduce(input,
|
||||
(headers, filter) -> filter.filter(headers, exchange),
|
||||
(httpHeaders, httpHeaders2) -> {
|
||||
httpHeaders.addAll(httpHeaders2);
|
||||
return httpHeaders;
|
||||
});
|
||||
return reduce;
|
||||
HttpHeaders filtered = input;
|
||||
for (int i = 0; i < filters.size(); i++) {
|
||||
HttpHeadersFilter filter = filters.get(i);
|
||||
if (filter.supports(type)) {
|
||||
filtered = filter.filter(filtered, exchange);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
return response;
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.cloud.gateway.filter.headers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -66,9 +68,11 @@ public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
|
||||
HttpHeaders filtered = new HttpHeaders();
|
||||
|
||||
input.entrySet().stream()
|
||||
.filter(entry -> !this.headers.contains(entry.getKey().toLowerCase()))
|
||||
.forEach(entry -> filtered.addAll(entry.getKey(), entry.getValue()));
|
||||
for (Map.Entry<String, List<String>> entry : input.entrySet()) {
|
||||
if (!this.headers.contains(entry.getKey().toLowerCase())) {
|
||||
filtered.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.filter.headers;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -201,8 +202,9 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
HttpHeaders original = input;
|
||||
HttpHeaders updated = new HttpHeaders();
|
||||
|
||||
original.entrySet().stream()
|
||||
.forEach(entry -> updated.addAll(entry.getKey(), entry.getValue()));
|
||||
for (Map.Entry<String, List<String>> entry : original.entrySet()) {
|
||||
updated.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (isForEnabled() && request.getRemoteAddress() != null
|
||||
&& request.getRemoteAddress().getAddress() != null) {
|
||||
@@ -229,7 +231,7 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
|
||||
|
||||
if (originalUris != null && requestUri != null) {
|
||||
|
||||
originalUris.stream().forEach(originalUri -> {
|
||||
originalUris.forEach(originalUri -> {
|
||||
|
||||
if (originalUri != null && originalUri.getPath() != null) {
|
||||
String prefix = originalUri.getPath();
|
||||
|
||||
@@ -67,8 +67,13 @@ public class HeaderRoutePredicateFactory
|
||||
// values is now guaranteed to not be empty
|
||||
if (hasRegex) {
|
||||
// check if a header value matches
|
||||
return values.stream()
|
||||
.anyMatch(value -> value.matches(config.regexp));
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
String value = values.get(i);
|
||||
if (value.matches(config.regexp)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// there is a value and since regexp is empty, we only check existence.
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
@@ -63,12 +62,18 @@ public class HostRoutePredicateFactory
|
||||
@Override
|
||||
public boolean test(ServerWebExchange exchange) {
|
||||
String host = exchange.getRequest().getHeaders().getFirst("Host");
|
||||
Optional<String> optionalPattern = config.getPatterns().stream()
|
||||
.filter(pattern -> pathMatcher.match(pattern, host)).findFirst();
|
||||
String match = null;
|
||||
for (int i = 0; i < config.getPatterns().size(); i++) {
|
||||
String pattern = config.getPatterns().get(i);
|
||||
if (pathMatcher.match(pattern, host)) {
|
||||
match = pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optionalPattern.isPresent()) {
|
||||
if (match != null) {
|
||||
Map<String, String> variables = pathMatcher
|
||||
.extractUriTemplateVariables(optionalPattern.get(), host);
|
||||
.extractUriTemplateVariables(match, host);
|
||||
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.cloud.gateway.handler.predicate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -93,13 +92,18 @@ public class PathRoutePredicateFactory
|
||||
PathContainer path = parsePath(
|
||||
exchange.getRequest().getURI().getRawPath());
|
||||
|
||||
Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
|
||||
.filter(pattern -> pattern.matches(path)).findFirst();
|
||||
PathPattern match = null;
|
||||
for (int i = 0; i < pathPatterns.size(); i++) {
|
||||
PathPattern pathPattern = pathPatterns.get(i);
|
||||
if (pathPattern.matches(path)) {
|
||||
match = pathPattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optionalPathPattern.isPresent()) {
|
||||
PathPattern pathPattern = optionalPathPattern.get();
|
||||
traceMatch("Pattern", pathPattern.getPatternString(), path, true);
|
||||
PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
|
||||
if (match != null) {
|
||||
traceMatch("Pattern", match.getPatternString(), path, true);
|
||||
PathMatchInfo pathMatchInfo = match.matchAndExtract(path);
|
||||
putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.filter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -35,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.SEC_WEBSOCKET_PROTOCOL;
|
||||
import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.changeSchemeIfIsWebSocketUpgrade;
|
||||
import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.convertHttpToWs;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
@@ -44,6 +46,21 @@ import static org.springframework.http.HttpHeaders.UPGRADE;
|
||||
|
||||
public class WebsocketRoutingFilterTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testProtocolParsing() {
|
||||
ObjectProvider<List<HttpHeadersFilter>> headersFilters = mock(
|
||||
ObjectProvider.class);
|
||||
WebsocketRoutingFilter filter = new WebsocketRoutingFilter(
|
||||
mock(WebSocketClient.class), mock(WebSocketService.class),
|
||||
headersFilters);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.put(SEC_WEBSOCKET_PROTOCOL, Arrays.asList(" p1,p2", "p3 , p4 "));
|
||||
List<String> protocols = filter.getProtocols(headers);
|
||||
assertThat(protocols).containsExactly("p1", "p2", "p3", "p4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertHttpToWs() {
|
||||
assertThat(convertHttpToWs("http")).isEqualTo("ws");
|
||||
|
||||
@@ -197,6 +197,13 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryFilterSeries() {
|
||||
testClient.get().uri("/retry?key=series&failStatus=404")
|
||||
.header(HttpHeaders.HOST, "www.retryseries.org").exchange().expectStatus()
|
||||
.isOk().expectBody(String.class).isEqualTo("3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringFormat() {
|
||||
RetryConfig config = new RetryConfig();
|
||||
@@ -251,7 +258,7 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
@RequestParam(name = "count", defaultValue = "3") int count,
|
||||
@RequestParam("expectedbody") String expectedbody,
|
||||
@RequestBody String body) {
|
||||
ResponseEntity<String> response = retry(key, count);
|
||||
ResponseEntity<String> response = retry(key, count, null);
|
||||
if (!expectedbody.equals(body)) {
|
||||
AtomicInteger num = getCount(key);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@@ -263,14 +270,19 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
|
||||
@RequestMapping("/httpbin/retry")
|
||||
public ResponseEntity<String> retry(@RequestParam("key") String key,
|
||||
@RequestParam(name = "count", defaultValue = "3") int count) {
|
||||
@RequestParam(name = "count", defaultValue = "3") int count,
|
||||
@RequestParam(name = "failStatus", required = false) Integer failStatus) {
|
||||
AtomicInteger num = getCount(key);
|
||||
int i = num.incrementAndGet();
|
||||
log.warn("Retry count: " + i);
|
||||
String body = String.valueOf(i);
|
||||
if (i < count) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.header("X-Retry-Count", body).body("temporarily broken");
|
||||
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
if (failStatus != null) {
|
||||
httpStatus = HttpStatus.resolve(failStatus);
|
||||
}
|
||||
return ResponseEntity.status(httpStatus).header("X-Retry-Count", body)
|
||||
.body("temporarily broken");
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.OK).header("X-Retry-Count", body)
|
||||
.body(body);
|
||||
@@ -288,6 +300,11 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
|
||||
.retry(config -> config.setRetries(2)
|
||||
.setMethods(HttpMethod.POST, HttpMethod.GET)))
|
||||
.uri(uri))
|
||||
.route("retry_series", r -> r.host("**.retryseries.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.retry(config -> config.setRetries(2)
|
||||
.setSeries(HttpStatus.Series.CLIENT_ERROR)))
|
||||
.uri(uri))
|
||||
.route("retry_only_get", r -> r.host("**.retry-only-get.org")
|
||||
.filters(f -> f.prefixPath("/httpbin")
|
||||
.retry(config -> config.setRetries(2)
|
||||
|
||||
Reference in New Issue
Block a user