Spring Cloud Gateway-ServerWebExchange核心方法与请求或者响应内容的修改

本文详细介绍了Spring Cloud Gateway中的ServerWebExchange,包括其接口和相关对象,如ServerHttpRequest和ServerHttpResponse。讲解了如何修改请求体和响应体,以及在处理大请求或响应时可能遇到的问题和解决方案。文章还提到了由于Netty的限制导致的报文截断问题,并给出了解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前提

  • 本文编写的时候使用的Spring Cloud Gateway版本为当时最新的版本Greenwich.SR1

我们在使用Spring Cloud Gateway的时候,注意到过滤器(包括GatewayFilterGlobalFilter和过滤器链GatewayFilterChain),都依赖到ServerWebExchange

public interface GlobalFilter {

    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

public interface GatewayFilter extends ShortcutConfigurable {

	Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

public interface GatewayFilterChain {

    Mono<Void> filter(ServerWebExchange exchange);
}    
复制代码

这里的设计和Servlet中的Filter是相似的,当前过滤器可以决定是否执行下一个过滤器的逻辑,由GatewayFilterChain#filter()是否被调用来决定。而ServerWebExchange就相当于当前请求和响应的上下文。ServerWebExchange实例不单存储了RequestResponse对象,还提供了一些扩展方法,如果想实现改造请求参数或者响应参数,就必须深入了解ServerWebExchange

理解ServerWebExchange

先看ServerWebExchange的注释:

Contract for an HTTP request-response interaction. Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.

翻译一下大概是:

ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。

其实,ServerWebExchange命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等,有点像Context的角色。

ServerWebExchange接口

ServerWebExchange接口的所有方法:

public interface ServerWebExchange {

    // 日志前缀属性的KEY,值为org.springframework.web.server.ServerWebExchange.LOG_ID
    // 可以理解为 attributes.set("org.springframework.web.server.ServerWebExchange.LOG_ID","日志前缀的具体值");
    // 作用是打印日志的时候会拼接这个KEY对饮的前缀值,默认值为""
    String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
    String getLogPrefix();

    // 获取ServerHttpRequest对象
    ServerHttpRequest getRequest();

    // 获取ServerHttpResponse对象
    ServerHttpResponse getResponse();
    
    // 返回当前exchange的请求属性,返回结果是一个可变的Map
    Map<String, Object> getAttributes();
    
    // 根据KEY获取请求属性
    @Nullable
    default <T> T getAttribute(String name) {
        return (T) getAttributes().get(name);
    }
    
    // 根据KEY获取请求属性,做了非空判断
    @SuppressWarnings("unchecked")
    default <T> T getRequiredAttribute(String name) {
        T value = getAttribute(name);
        Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
        return value;
    }

     // 根据KEY获取请求属性,需要提供默认值
    @SuppressWarnings("unchecked")
    default <T> T getAttributeOrDefault(String name, T defaultValue) {
        return (T) getAttributes().getOrDefault(name, defaultValue);
    } 

    // 返回当前请求的网络会话
    Mono<WebSession> getSession();

    // 返回当前请求的认证用户,如果存在的话
    <T extends Principal> Mono<T> getPrincipal();  
    
    // 返回请求的表单数据或者一个空的Map,只有Content-Type为application/x-www-form-urlencoded的时候这个方法才会返回一个非空的Map -- 这个一般是表单数据提交用到
    Mono<MultiValueMap<String, String>> getFormData();   
    
    // 返回multipart请求的part数据或者一个空的Map,只有Content-Type为multipart/form-data的时候这个方法才会返回一个非空的Map  -- 这个一般是文件上传用到
    Mono<MultiValueMap<String, Part>> getMultipartData();
    
    // 返回Spring的上下文
    @Nullable
    ApplicationContext getApplicationContext();   

    // 这几个方法和lastModified属性相关
    boolean isNotModified();
    boolean checkNotModified(Instant lastModified);
    boolean checkNotModified(String etag);
    boolean checkNotModified(@Nullable String etag, Instant lastModified);
    
    // URL转换
    String transformUrl(String url);    
   
    // URL转换映射
    void addUrlTransformer(Function<String, String> transformer); 

    // 注意这个方法,方法名是:改变,这个是修改ServerWebExchange属性的方法,返回的是一个Builder实例,Builder是ServerWebExchange的内部类
    default Builder mutate() {
	     return new DefaultServerWebExchangeBuilder(this);
    }

    interface Builder {      
         
        // 覆盖ServerHttpRequest
        Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer);
        Builder request(ServerHttpRequest request);
        
        // 覆盖ServerHttpResponse
        Builder response(ServerHttpResponse response);
        
        // 覆盖当前请求的认证用户
        Builder principal(Mono<Principal> principalMono);
    
        // 构建新的ServerWebExchange实例
        ServerWebExchange build();
    }
}    
复制代码

注意到ServerWebExchange#mutate()方法,ServerWebExchange实例可以理解为不可变实例,如果我们想要修改它,需要通过mutate()方法生成一个新的实例,例如这样:

public class CustomGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 这里可以修改ServerHttpRequest实例
        ServerHttpRequest newRequest = ...
        ServerHttpResponse response = exchange.getResponse();
        // 这里可以修改ServerHttpResponse实例
        ServerHttpResponse newResponse = ...
        // 构建新的ServerWebExchange实例
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).response(newResponse).build();
        return chain.filter(newExchange);
    }
}
复制代码

ServerHttpRequest接口

ServerHttpRequest实例是用于承载请求相关的属性和请求体,Spring Cloud Gateway中底层使用Netty处理网络请求,通过追溯源码,可以从ReactorHttpHandlerAdapter中得知ServerWebExchange实例中持有的ServerHttpRequest实例的具体实现是ReactorServerHttpRequest。之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:

  • ReactorServerHttpRequest的父类AbstractServerHttpRequest中初始化内部属性headers的时候把请求的HTTP头部封装为只读的实例:
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) {
	this.uri = uri;
	this.path = RequestPath.parse(uri, contextPath);
	this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}

// HttpHeaders类中的readOnlyHttpHeaders方法,其中ReadOnlyHttpHeaders屏蔽了所有修改请求头的方法,直接抛出UnsupportedOperationException
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
	Assert.notNull(headers, "HttpHeaders must not be null");
	if (headers instanceof ReadOnlyHttpHeaders) {
		return headers;
	}
	else {
		return new ReadOnlyHttpHeaders(headers);
	}
}
复制代码

所以不能直接从ServerHttpRequest实例中直接获取请求头HttpHeaders实例并且进行修改。

ServerHttpRequest接口如下:

public interface HttpMessage {
    
    // 获取请求头,目前的实现中返回的是ReadOnlyHttpHeaders实例,只读
    HttpHeaders getHeaders();
}    

public interface ReactiveHttpInputMessage extends HttpMessage {
    
    // 返回请求体的Flux封装
    Flux<DataBuffer> getBody();
}

public interface HttpRequest extends HttpMessage {

    // 返回HTTP请求方法,解析为HttpMethod实例
    @Nullable
    default HttpMethod getMethod() {
        return HttpMethod.resolve(getMethodValue());
    }
    
    // 返回HTTP请求方法,字符串
    String getMethodValue();    
    
    // 请求的URI
    URI getURI();
}    

public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
    
    // 连接的唯一标识或者用于日志处理标识
    String getId();   
    
    // 获取请求路径,封装为RequestPath对象
    RequestPath getPath();
    
    // 返回查询参数,是只读的MultiValueMap实例
    MultiValueMap<String, String> getQueryParams();

    // 返回Cookie集合,是只读的MultiValueMap实例
    MultiValueMap<String, HttpCookie> getCookies();  
    
    // 远程服务器地址信息
    @Nullable
    default InetSocketAddress getRemoteAddress() {
       return null;
    }

    // SSL会话实现的相关信息
    @Nullable
    default SslInfo getSslInfo() {
       return null;
    }  
    
    // 修改请求的方法,返回一个建造器实例Builder,Builder是内部类
    default ServerHttpRequest.Builder mutate() {
        return new DefaultServerHttpRequestBuilder(this);
    } 

    interface Builder {

        // 覆盖请求方法
        Builder method(HttpMethod httpMethod);
		 
        // 覆盖请求的URI、请求路径或者上下文,这三者相互有制约关系,具体可以参考API注释
        Builder uri(URI uri);
        Builder path(String path);
        Builder contextPath(String contextPath);

        // 覆盖请求头
        Builder header(String key, String value);
        Builder headers(Consumer<HttpHeaders> headersConsumer);
        
        // 覆盖SslInfo
        Builder sslInfo(SslInfo sslInfo);
        
        // 构建一个新的ServerHttpRequest实例
        ServerHttpRequest build();
    }         
}    
复制代码

如果要修改ServerHttpRequest实例,那么需要这样做:

ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest newRequest = request.mutate().headers("key","value").path("/myPath").build();
复制代码

这里最值得注意的是:ServerHttpRequest或者说HttpMessage接口提供的获取请求头方法HttpHeaders getHeaders();返回结果是一个只读的实例,具体是ReadOnlyHttpHeaders类型,这里提多一次,笔者写这篇博文时候使用的Spring Cloud Gateway版本为Greenwich.SR1

ServerHttpResponse接口

ServerHttpResponse实例是用于承载响应相关的属性和响应体,Spring Cloud Gateway中底层使用Netty处理网络请求,通过追溯源码,可以从ReactorHttpHandlerAdapter中得知ServerWebExchange实例中持有的ServerHttpResponse实例的具体实现是ReactorServerHttpResponse。之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:

// ReactorServerHttpResponse的父类
如果你想在Spring Cloud Gateway中根据响应体的内容来判断是否进行转发请求或返回结果,可以使用Spring Cloud Gateway提供的GlobalFilter来实现。 具体步骤如下: 1.Spring Cloud Gateway中定义一个GlobalFilter,例如: ``` @Component public class MyFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).then(Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); HttpStatus statusCode = response.getStatusCode(); if (statusCode == HttpStatus.OK) { // 获取响应内容 byte[] bytes = ((DataBuffer) response.getBody()).asByteBuffer().array(); String responseBody = new String(bytes, StandardCharsets.UTF_8); // 判断响应内容是否需要转发请求 if (responseBody.contains("需要转发请求的关键字")) { // 构造新的请求URI URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()) .host("newhost") .path("newpath") .build() .toUri(); // 构造新的请求对象 ServerHttpRequest newRequest = exchange.getRequest().mutate().uri(newUri).build(); // 转发新的请求 chain.filter(exchange.mutate().request(newRequest).build()); } else { // 返回响应内容 response.getHeaders().setContentLength(responseBody.length()); DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Flux.just(buffer)); } } })); } } ``` 这个GlobalFilter会在每个请求响应之后执行,获取响应内容并根据内容判断是否需要转发请求或返回响应内容。 2.Spring Cloud Gateway的配置文件中配置这个GlobalFilter,例如: ``` spring: cloud: gateway: default-filters: - MyFilter ``` 这个配置将这个GlobalFilter添加到默认的过滤器列表中,使其在每个请求中都被执行。 注意:这个GlobalFilter的核心逻辑是根据响应内容来判断是否需要转发请求或返回响应内容。如果需要转发请求,可以构造新的请求URI和请求对象,然后使用GatewayFilterChain的filter方法来执行新的请求。如果需要返回响应内容,可以构造一个新的DataBuffer对象,并使用ServerHttpResponse的writeWith方法来返回响应内容
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值