Removes use of stream() in hot path.

This includes `filter()` and `test()`.

Fixes gh-2197
This commit is contained in:
spencergibb
2021-04-05 16:20:28 -04:00
parent 4c0f2b450f
commit 7cb009d297
15 changed files with 159 additions and 67 deletions

View File

@@ -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;
}
}

View File

@@ -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;
});
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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));

View File

@@ -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;
}
/**

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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)