下面列出了org.springframework.http.server.reactive.ServerHttpRequestDecorator#org.springframework.cloud.gateway.filter.GatewayFilterChain 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 清洗请求头中from 参数
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove(SecurityConstants.FROM))
.build();
// 2. 重写StripPrefix
addOriginalRequestUrl(exchange, request.getURI());
String rawPath = request.getURI().getRawPath();
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
.skip(1L).collect(Collectors.joining("/"));
ServerHttpRequest newRequest = request.mutate()
.path(newPath)
.build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate()
.request(newRequest.mutate()
.build()).build());
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (routeEhance) {
Mono<Void> blackListResult = routeEnhanceService.filterBlackList(exchange);
if (blackListResult != null) {
routeEnhanceService.saveBlockLogs(exchange);
return blackListResult;
}
Mono<Void> rateLimitResult = routeEnhanceService.filterRateLimit(exchange);
if (rateLimitResult != null) {
routeEnhanceService.saveRateLimitLogs(exchange);
return rateLimitResult;
}
routeEnhanceService.saveRequestLogs(exchange);
}
byte[] token = Base64Utils.encode((FebsConstant.GATEWAY_TOKEN_VALUE).getBytes());
String[] headerValues = {new String(token)};
ServerHttpRequest build = exchange.getRequest().mutate().header(FebsConstant.GATEWAY_TOKEN_HEADER, headerValues).build();
ServerWebExchange newExchange = exchange.mutate().request(build).build();
return chain.filter(newExchange);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
HttpHeaders headers = exchange.getRequest().getHeaders();
Iterator<Map.Entry<String,List<String>>> iterator = headers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,List<String>> entry = iterator.next();
log.info(entry.getKey());
for(String s : entry.getValue()){
log.info(s);
}
}
})
);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
TokenBucket tokenBucket = TokenBuckets.builder().withCapacity(capacity)
.withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit)
.build();
// TODO: get a token bucket for a key
log.debug("TokenBucket capacity: " + tokenBucket.getCapacity());
boolean consumed = tokenBucket.tryConsume();
if (consumed) {
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI originalUri = exchange.getRequest().getURI();
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String forwardedUri = request.getURI().toString();
if (forwardedUri != null && forwardedUri.startsWith("https")) {
try {
URI mutatedUri = new URI("http",
originalUri.getUserInfo(),
originalUri.getHost(),
originalUri.getPort(),
originalUri.getPath(),
originalUri.getQuery(),
originalUri.getFragment());
mutate.uri(mutatedUri);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
ServerHttpRequest build = mutate.build();
return chain.filter(exchange.mutate().request(build).build());
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
if (isSkip(path)) {
return chain.filter(exchange);
}
ServerHttpResponse resp = exchange.getResponse();
String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
if (StringUtils.isAllBlank(headerToken, paramToken)) {
return unAuth(resp, "缺失令牌,鉴权失败");
}
String auth = StringUtils.isBlank(headerToken) ? paramToken : headerToken;
String token = JwtUtil.getToken(auth);
Claims claims = JwtUtil.parseJWT(token);
if (claims == null) {
return unAuth(resp, "请求未授权");
}
return chain.filter(exchange);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info("{} R: {} {} {}",
request.getRemoteAddress(),
request.getId(),
request.getMethodValue(),
request.getURI());
long startTimestamp = Calendar.getInstance().getTimeInMillis();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
log.info("R: {}, {} {}(ms)",
request.getId(),
response.getStatusCode(),
Calendar.getInstance().getTimeInMillis() - startTimestamp);
}));
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
log.info(" access url : "+ url);
// 1. 重写StripPrefix(获取真实的URL)
addOriginalRequestUrl(exchange, exchange.getRequest().getURI());
String rawPath = exchange.getRequest().getURI().getRawPath();
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")).skip(1L).collect(Collectors.joining("/"));
ServerHttpRequest newRequest = exchange.getRequest().mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
//将现在的request,添加当前身份
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header("Authorization-UserName", "").build();
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
return chain.filter(mutableExchange);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("code");
URI uri = exchange.getRequest().getURI();
String backUrl = "";
String redirectUrl;
if (uri != null) {
backUrl = uri.toString();
}
String urlPath = exchange.getRequest().getURI().getPath();
if (StringUtils.isEmpty(token) && !urlPath.contains(STATIC_RESOURCE_PATH)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SEE_OTHER);
try {
redirectUrl = authServerUrl + "?backUrl=" + URLEncoder.encode(backUrl, "UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error to encode url");
redirectUrl = authServerUrl + "?backUrl=" + backUrl;
}
response.getHeaders().set("Location", redirectUrl);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain chain) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(serverWebExchange);
}
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain chain) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(serverWebExchange);
}
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain chain) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(serverWebExchange);
}
/**
* 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
* 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String method = request.getMethodValue();
String url = request.getPath().value();
log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
//不需要网关签权的url
if (authService.ignoreAuthentication(url)) {
return chain.filter(exchange);
}
//调用签权服务看用户是否有权限,若有权限进入下一个filter
if (authService.hasPermission(authentication, url, method)) {
ServerHttpRequest.Builder builder = request.mutate();
//TODO 转发的请求都加上服务间认证token
builder.header(X_CLIENT_TOKEN, "TODO zhoutaoo添加服务间简单认证");
//将jwt token中的用户信息传给服务
builder.header(X_CLIENT_TOKEN_USER, authService.getJwt(authentication).getClaims());
return chain.filter(exchange.mutate().request(builder.build()).build());
}
return unauthorized(exchange);
}
/**
* 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
* 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String method = request.getMethodValue();
String url = request.getPath().value();
log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
//不需要网关签权的url
if (authService.ignoreAuthentication(url)) {
return chain.filter(exchange);
}
//调用签权服务看用户是否有权限,若有权限进入下一个filter
if (permissionService.permission(authentication, url, method)) {
ServerHttpRequest.Builder builder = request.mutate();
//TODO 转发的请求都加上服务间认证token
builder.header(X_CLIENT_TOKEN, "TODO zhoutaoo添加服务间简单认证");
//将jwt token中的用户信息传给服务
builder.header(X_CLIENT_TOKEN_USER, getUserToken(authentication));
return chain.filter(exchange.mutate().request(builder.build()).build());
}
return unauthorized(exchange);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> rewriteLocation(exchange, config)));
}
@Override
public String toString() {
// @formatter:off
return filterToStringCreator(
RewriteLocationResponseHeaderGatewayFilterFactory.this)
.append("stripVersion", config.stripVersion)
.append("locationHeaderName", config.locationHeaderName)
.append("hostValue", config.hostValue)
.append("protocols", config.protocols)
.toString();
// @formatter:on
}
};
}
@Override
public GatewayFilter apply(Config config) {
String replacement = config.replacement.replace("$\\", "$");
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
addOriginalRequestUrl(exchange, req.getURI());
String path = req.getURI().getRawPath();
String newPath = path.replaceAll(config.regexp, replacement);
ServerHttpRequest request = req.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(RewritePathGatewayFilterFactory.this)
.append(config.getRegexp(), replacement).toString();
}
};
}
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
exchange.getResponse().getHeaders().add(config.getName(), value);
return chain.filter(exchange);
}
@Override
public String toString() {
return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
@Override
public GatewayFilter apply(NameConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> exchange
.getResponse().getHeaders().remove(config.getName())));
}
@Override
public String toString() {
return filterToStringCreator(
RemoveResponseHeaderGatewayFilterFactory.this)
.append("name", config.getName()).toString();
}
};
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return chain.filter(exchange)
.then(Mono.fromRunnable(() -> rewriteHeader(exchange, config)));
}
@Override
public String toString() {
return filterToStringCreator(
RewriteResponseHeaderGatewayFilterFactory.this)
.append("name", config.getName())
.append("regexp", config.getRegexp())
.append("replacement", config.getReplacement())
.toString();
}
};
}
@Override
public GatewayFilter apply(NameConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove(config.getName()))
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(RemoveRequestHeaderGatewayFilterFactory.this)
.append("name", config.getName()).toString();
}
};
}
@Override
public GatewayFilter apply(Object config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return exchange.getSession().map(WebSession::save)
.then(chain.filter(exchange));
}
@Override
public String toString() {
return filterToStringCreator(SaveSessionGatewayFilterFactory.this)
.toString();
}
};
}
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.set(config.name, value))
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(SetRequestHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
ServerHttpRequest request = exchange.getRequest().mutate()
.header(config.getName(), value).build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
return chain.filter(exchange).then(Mono.fromRunnable(() -> exchange
.getResponse().getHeaders().set(config.name, value)));
}
@Override
public String toString() {
return filterToStringCreator(SetResponseHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
public GatewayFilter apply(Object config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, true);
return chain.filter(exchange);
}
@Override
public String toString() {
return filterToStringCreator(PreserveHostHeaderGatewayFilterFactory.this)
.toString();
}
};
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(
() -> dedupe(exchange.getResponse().getHeaders(), config)));
}
@Override
public String toString() {
return filterToStringCreator(
DedupeResponseHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getStrategy())
.toString();
}
};
}
@Test
public void filterChangeRequestUri() {
RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory();
GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url"));
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.header("X-CF-Forwarded-Url", "https://example.com").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
URI.create("http://localhost"));
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor
.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
filter.filter(exchange, filterChain);
ServerWebExchange webExchange = captor.getValue();
URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).isNotNull();
assertThat(uri.toString()).isEqualTo("https://example.com");
}
@Test
public void filterDoesNotChangeRequestUriIfHeaderIsAbsent() {
RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory();
GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url"));
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
URI.create("http://localhost"));
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor
.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
filter.filter(exchange, filterChain);
ServerWebExchange webExchange = captor.getValue();
URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).isNotNull();
assertThat(uri.toString()).isEqualTo("http://localhost");
}
@Test
public void filterDoesNotChangeRequestUriIfHeaderIsInvalid() throws Exception {
RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory();
GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url"));
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.header("X-CF-Forwarded-Url", "example").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
URI.create("http://localhost"));
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor
.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
filter.filter(exchange, filterChain);
ServerWebExchange webExchange = captor.getValue();
URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).isNotNull();
assertThat(uri.toURL().toString()).isEqualTo("http://localhost");
}
private void testFilter(String template, String expectedPath,
HashMap<String, String> variables) {
GatewayFilter filter = new SetPathGatewayFilterFactory()
.apply(c -> c.setTemplate(template));
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor
.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
filter.filter(exchange, filterChain);
ServerWebExchange webExchange = captor.getValue();
assertThat(webExchange.getRequest().getURI()).hasPath(expectedPath);
LinkedHashSet<URI> uris = webExchange
.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
assertThat(uris).contains(request.getURI());
}