我正在使用
GatewayFilterSpec.modifyResponseBody
(标记为“BETA”功能)重写 JSON 有效负载。只要响应有效负载实际上是内容类型application/json
,这种方法就可以正常工作。就我而言,不幸的是,这并不总是得到保证,我希望它仅在响应具有 modifyResponseBody
标头时应用 Content-Type: application/json
,否则跳过过滤器。 Spring Cloud Gateway 可以做到这一点吗?如何做到这一点?谢谢。
现在我明白了:
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html' not supported
at org.springframework.web.reactive.function.BodyInserters.lambda$null$11(BodyInserters.java:329)
at java.util.Optional.orElseGet(Optional.java:267)
at org.springframework.web.reactive.function.BodyInserters.lambda$bodyInserterFor$12(BodyInserters.java:325)
这是一个“解决方案”,它有各种问题:
package my_package;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@Component
@Primary
public class JsonOnlyModifyResponseBodyGatewayFilterFactory extends ModifyResponseBodyGatewayFilterFactory {
public JsonOnlyModifyResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
super(codecConfigurer);
}
@Override
public GatewayFilter apply(Config config) {
return new MyModifyResponseGatewayFilter(config);
}
public class MyModifyResponseGatewayFilter extends ModifyResponseGatewayFilter {
MyModifyResponseGatewayFilter(Config config) {
super(config);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse serverHttpResponse = getServerHttpResponseFromSuper(exchange);
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (APPLICATION_JSON.isCompatibleWith(getDelegate().getHeaders().getContentType())) {
return serverHttpResponse.writeWith(body);
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
private ServerHttpResponse getServerHttpResponseFromSuper(ServerWebExchange exchange) {
ServerHttpResponse[] serverHttpResponse = new ServerHttpResponse[1];
//noinspection UnassignedFluxMonoInstance
super.filter(exchange, chain -> {
serverHttpResponse[0] = chain.getResponse(); // capture the response when the super sets it
return null;
});
return serverHttpResponse[0];
}
}
}
所选择的方法代替了仅更改现有
ModifyResponseBodyGatewayFilterFactory
的副本。这允许 Spring Boot Gateway 的版本升级带来 ModifyResponseBodyGatewayFilterFactory
的微小变化。但是由于JsonOnlyModifyResponseBodyGatewayFilterFactory
非常依赖于ModifyResponseBodyGatewayFilterFactory
的实现,这很容易被破坏。这个解决方案的另一个缺陷是我必须放置一个@Primary
注释以避免required a single bean, but 2 were found
异常,但它会覆盖默认值,这可能会影响modifyResponseBody
的其他用途。调用 super.filter
而不使用其结果是丑陋的。等等。所以,虽然这个“有效”,但它并没有让我感到高兴。
受这个answer的启发,我们可以通过组合
ModifyResponseBodyGatewayFilterFactory
来创建一个稍微不同的GatewayFilter。组合可能比扩展它更简洁、更短。
package my_package;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
@Component
public class ConditionallyModifyResponseBodyGatewayFilterFactory
extends AbstractGatewayFilterFactory<ModifyResponseBodyGatewayFilterFactory.Config> {
private final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
@Autowired
public ConditionallyModifyResponseBodyGatewayFilterFactory(
final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory) {
super(ModifyResponseBodyGatewayFilterFactory.Config.class);
this.modifyResponseBodyGatewayFilterFactory = modifyResponseBodyGatewayFilterFactory;
}
@Override
public GatewayFilter apply(final ModifyResponseBodyGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
boolean shouldModify = contentType != null && contentType.isCompatibleWith(MediaType.APPLICATION_JSON);
if (shouldModify) {
return this.modifyResponseBodyGatewayFilterFactory.apply(config).filter(exchange, chain);
} else {
// Do nothing
return chain.filter(exchange);
}
};
}
}