Spring Cloud Gateway-ServerWebExchange核心方法與請(qǐng)求或者響應(yīng)內(nèi)容的修改

前提

  • 本文編寫的時(shí)候使用的Spring Cloud Gateway版本為當(dāng)時(shí)最新的版本Greenwich.SR1抠藕。

我們?cè)谑褂?code>Spring Cloud Gateway的時(shí)候,注意到過濾器(包括GatewayFilter尘奏、GlobalFilter和過濾器鏈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);
}    
復(fù)制代碼

這里的設(shè)計(jì)和Servlet中的Filter是相似的,當(dāng)前過濾器可以決定是否執(zhí)行下一個(gè)過濾器的邏輯胡岔,由GatewayFilterChain#filter()是否被調(diào)用來決定。而ServerWebExchange就相當(dāng)于當(dāng)前請(qǐng)求和響應(yīng)的上下文枷餐。ServerWebExchange實(shí)例不單存儲(chǔ)了RequestResponse對(duì)象靶瘸,還提供了一些擴(kuò)展方法,如果想實(shí)現(xiàn)改造請(qǐng)求參數(shù)或者響應(yīng)參數(shù),就必須深入了解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是一個(gè)HTTP請(qǐng)求-響應(yīng)交互的契約屋剑。提供對(duì)HTTP請(qǐng)求和響應(yīng)的訪問,并公開額外的服務(wù)器端處理相關(guān)屬性和特性诗眨,如請(qǐng)求屬性唉匾。

其實(shí),ServerWebExchange命名為服務(wù)網(wǎng)絡(luò)交換器匠楚,存放著重要的請(qǐng)求-響應(yīng)屬性肄鸽、請(qǐng)求實(shí)例和響應(yīng)實(shí)例等等,有點(diǎn)像Context的角色油啤。

ServerWebExchange接口

ServerWebExchange接口的所有方法:

public interface ServerWebExchange {

    // 日志前綴屬性的KEY,值為org.springframework.web.server.ServerWebExchange.LOG_ID
    // 可以理解為 attributes.set("org.springframework.web.server.ServerWebExchange.LOG_ID","日志前綴的具體值");
    // 作用是打印日志的時(shí)候會(huì)拼接這個(gè)KEY對(duì)飲的前綴值蟀苛,默認(rèn)值為""
    String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
    String getLogPrefix();

    // 獲取ServerHttpRequest對(duì)象
    ServerHttpRequest getRequest();

    // 獲取ServerHttpResponse對(duì)象
    ServerHttpResponse getResponse();

    // 返回當(dāng)前exchange的請(qǐng)求屬性益咬,返回結(jié)果是一個(gè)可變的Map
    Map<String, Object> getAttributes();

    // 根據(jù)KEY獲取請(qǐng)求屬性
    @Nullable
    default <T> T getAttribute(String name) {
        return (T) getAttributes().get(name);
    }

    // 根據(jù)KEY獲取請(qǐng)求屬性,做了非空判斷
    @SuppressWarnings("unchecked")
    default <T> T getRequiredAttribute(String name) {
        T value = getAttribute(name);
        Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
        return value;
    }

     // 根據(jù)KEY獲取請(qǐng)求屬性帜平,需要提供默認(rèn)值
    @SuppressWarnings("unchecked")
    default <T> T getAttributeOrDefault(String name, T defaultValue) {
        return (T) getAttributes().getOrDefault(name, defaultValue);
    } 

    // 返回當(dāng)前請(qǐng)求的網(wǎng)絡(luò)會(huì)話
    Mono<WebSession> getSession();

    // 返回當(dāng)前請(qǐng)求的認(rèn)證用戶幽告,如果存在的話
    <T extends Principal> Mono<T> getPrincipal();  

    // 返回請(qǐng)求的表單數(shù)據(jù)或者一個(gè)空的Map,只有Content-Type為application/x-www-form-urlencoded的時(shí)候這個(gè)方法才會(huì)返回一個(gè)非空的Map -- 這個(gè)一般是表單數(shù)據(jù)提交用到
    Mono<MultiValueMap<String, String>> getFormData();   

    // 返回multipart請(qǐng)求的part數(shù)據(jù)或者一個(gè)空的Map裆甩,只有Content-Type為multipart/form-data的時(shí)候這個(gè)方法才會(huì)返回一個(gè)非空的Map  -- 這個(gè)一般是文件上傳用到
    Mono<MultiValueMap<String, Part>> getMultipartData();

    // 返回Spring的上下文
    @Nullable
    ApplicationContext getApplicationContext();   

    // 這幾個(gè)方法和lastModified屬性相關(guān)
    boolean isNotModified();
    boolean checkNotModified(Instant lastModified);
    boolean checkNotModified(String etag);
    boolean checkNotModified(@Nullable String etag, Instant lastModified);

    // URL轉(zhuǎn)換
    String transformUrl(String url);    

    // URL轉(zhuǎn)換映射
    void addUrlTransformer(Function<String, String> transformer); 

    // 注意這個(gè)方法冗锁,方法名是:改變,這個(gè)是修改ServerWebExchange屬性的方法嗤栓,返回的是一個(gè)Builder實(shí)例冻河,Builder是ServerWebExchange的內(nèi)部類
    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);

        // 覆蓋當(dāng)前請(qǐng)求的認(rèn)證用戶
        Builder principal(Mono<Principal> principalMono);

        // 構(gòu)建新的ServerWebExchange實(shí)例
        ServerWebExchange build();
    }
}    
復(fù)制代碼

注意到ServerWebExchange#mutate()方法,ServerWebExchange實(shí)例可以理解為不可變實(shí)例茉帅,如果我們想要修改它叨叙,需要通過mutate()方法生成一個(gè)新的實(shí)例,例如這樣:

public class CustomGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 這里可以修改ServerHttpRequest實(shí)例
        ServerHttpRequest newRequest = ...
        ServerHttpResponse response = exchange.getResponse();
        // 這里可以修改ServerHttpResponse實(shí)例
        ServerHttpResponse newResponse = ...
        // 構(gòu)建新的ServerWebExchange實(shí)例
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).response(newResponse).build();
        return chain.filter(newExchange);
    }
}
復(fù)制代碼

ServerHttpRequest接口

ServerHttpRequest實(shí)例是用于承載請(qǐng)求相關(guān)的屬性和請(qǐng)求體堪澎,Spring Cloud Gateway中底層使用Netty處理網(wǎng)絡(luò)請(qǐng)求擂错,通過追溯源碼,可以從ReactorHttpHandlerAdapter中得知ServerWebExchange實(shí)例中持有的ServerHttpRequest實(shí)例的具體實(shí)現(xiàn)是ReactorServerHttpRequest樱蛤。之所以列出這些實(shí)例之間的關(guān)系钮呀,是因?yàn)檫@樣比較容易理清一些隱含的問題藻三,例如:

  • ReactorServerHttpRequest的父類AbstractServerHttpRequest中初始化內(nèi)部屬性headers的時(shí)候把請(qǐng)求的HTTP頭部封裝為只讀的實(shí)例:
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屏蔽了所有修改請(qǐng)求頭的方法朦乏,直接拋出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);
    }
}
復(fù)制代碼

所以不能直接從ServerHttpRequest實(shí)例中直接獲取請(qǐng)求頭HttpHeaders實(shí)例并且進(jìn)行修改隙疚。

ServerHttpRequest接口如下:

public interface HttpMessage {

    // 獲取請(qǐng)求頭卿城,目前的實(shí)現(xiàn)中返回的是ReadOnlyHttpHeaders實(shí)例菊匿,只讀
    HttpHeaders getHeaders();
}    

public interface ReactiveHttpInputMessage extends HttpMessage {

    // 返回請(qǐng)求體的Flux封裝
    Flux<DataBuffer> getBody();
}

public interface HttpRequest extends HttpMessage {

    // 返回HTTP請(qǐng)求方法狭莱,解析為HttpMethod實(shí)例
    @Nullable
    default HttpMethod getMethod() {
        return HttpMethod.resolve(getMethodValue());
    }

    // 返回HTTP請(qǐng)求方法奥裸,字符串
    String getMethodValue();    

    // 請(qǐng)求的URI
    URI getURI();
}    

public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {

    // 連接的唯一標(biāo)識(shí)或者用于日志處理標(biāo)識(shí)
    String getId();   

    // 獲取請(qǐng)求路徑,封裝為RequestPath對(duì)象
    RequestPath getPath();

    // 返回查詢參數(shù)证杭,是只讀的MultiValueMap實(shí)例
    MultiValueMap<String, String> getQueryParams();

    // 返回Cookie集合田度,是只讀的MultiValueMap實(shí)例
    MultiValueMap<String, HttpCookie> getCookies();  

    // 遠(yuǎn)程服務(wù)器地址信息
    @Nullable
    default InetSocketAddress getRemoteAddress() {
       return null;
    }

    // SSL會(huì)話實(shí)現(xiàn)的相關(guān)信息
    @Nullable
    default SslInfo getSslInfo() {
       return null;
    }  

    // 修改請(qǐng)求的方法,返回一個(gè)建造器實(shí)例Builder解愤,Builder是內(nèi)部類
    default ServerHttpRequest.Builder mutate() {
        return new DefaultServerHttpRequestBuilder(this);
    } 

    interface Builder {

        // 覆蓋請(qǐng)求方法
        Builder method(HttpMethod httpMethod);

        // 覆蓋請(qǐng)求的URI镇饺、請(qǐng)求路徑或者上下文,這三者相互有制約關(guān)系送讲,具體可以參考API注釋
        Builder uri(URI uri);
        Builder path(String path);
        Builder contextPath(String contextPath);

        // 覆蓋請(qǐng)求頭
        Builder header(String key, String value);
        Builder headers(Consumer<HttpHeaders> headersConsumer);

        // 覆蓋SslInfo
        Builder sslInfo(SslInfo sslInfo);

        // 構(gòu)建一個(gè)新的ServerHttpRequest實(shí)例
        ServerHttpRequest build();
    }         
}    
復(fù)制代碼

如果要修改ServerHttpRequest實(shí)例奸笤,那么需要這樣做:

ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest newRequest = request.mutate().headers("key","value").path("/myPath").build();
復(fù)制代碼

這里最值得注意的是:ServerHttpRequest或者說HttpMessage接口提供的獲取請(qǐng)求頭方法HttpHeaders getHeaders();返回結(jié)果是一個(gè)只讀的實(shí)例,具體是ReadOnlyHttpHeaders類型哼鬓,這里提多一次监右,筆者寫這篇博文時(shí)候使用的Spring Cloud Gateway版本為Greenwich.SR1

ServerHttpResponse接口

ServerHttpResponse實(shí)例是用于承載響應(yīng)相關(guān)的屬性和響應(yīng)體异希,Spring Cloud Gateway中底層使用Netty處理網(wǎng)絡(luò)請(qǐng)求健盒,通過追溯源碼,可以從ReactorHttpHandlerAdapter中得知ServerWebExchange實(shí)例中持有的ServerHttpResponse實(shí)例的具體實(shí)現(xiàn)是ReactorServerHttpResponse称簿。之所以列出這些實(shí)例之間的關(guān)系扣癣,是因?yàn)檫@樣比較容易理清一些隱含的問題,例如:

// ReactorServerHttpResponse的父類
public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory, HttpHeaders headers) {
    Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
    Assert.notNull(headers, "HttpHeaders must not be null");
    this.dataBufferFactory = dataBufferFactory;
    this.headers = headers;
    this.cookies = new LinkedMultiValueMap<>();
}

public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
    super(bufferFactory, new HttpHeaders(new NettyHeadersAdapter(response.responseHeaders())));
    Assert.notNull(response, "HttpServerResponse must not be null");
    this.response = response;
}
復(fù)制代碼

可知ReactorServerHttpResponse構(gòu)造函數(shù)初始化實(shí)例的時(shí)候憨降,存放響應(yīng)Header的是HttpHeaders實(shí)例父虑,也就是響應(yīng)Header是可以直接修改的。

ServerHttpResponse接口如下:

public interface HttpMessage {

    // 獲取響應(yīng)Header授药,目前的實(shí)現(xiàn)中返回的是HttpHeaders實(shí)例士嚎,可以直接修改
    HttpHeaders getHeaders();
}  

public interface ReactiveHttpOutputMessage extends HttpMessage {

    // 獲取DataBufferFactory實(shí)例,用于包裝或者生成數(shù)據(jù)緩沖區(qū)DataBuffer實(shí)例(創(chuàng)建響應(yīng)體)
    DataBufferFactory bufferFactory();

    // 注冊(cè)一個(gè)動(dòng)作烁焙,在HttpOutputMessage提交之前此動(dòng)作會(huì)進(jìn)行回調(diào)
    void beforeCommit(Supplier<? extends Mono<Void>> action);

    // 判斷HttpOutputMessage是否已經(jīng)提交
    boolean isCommitted();

    // 寫入消息體到HTTP協(xié)議層
    Mono<Void> writeWith(Publisher<? extends DataBuffer> body);

    // 寫入消息體到HTTP協(xié)議層并且刷新緩沖區(qū)
    Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body);

    // 指明消息處理已經(jīng)結(jié)束航邢,一般在消息處理結(jié)束自動(dòng)調(diào)用此方法,多次調(diào)用不會(huì)產(chǎn)生副作用
    Mono<Void> setComplete();
}

public interface ServerHttpResponse extends ReactiveHttpOutputMessage {

    // 設(shè)置響應(yīng)狀態(tài)碼
    boolean setStatusCode(@Nullable HttpStatus status);

    // 獲取響應(yīng)狀態(tài)碼
    @Nullable
    HttpStatus getStatusCode();

    // 獲取響應(yīng)Cookie骄蝇,封裝為MultiValueMap實(shí)例膳殷,可以修改
    MultiValueMap<String, ResponseCookie> getCookies();  

    // 添加響應(yīng)Cookie
    void addCookie(ResponseCookie cookie);  
}    
復(fù)制代碼

這里可以看到除了響應(yīng)體比較難修改之外,其他的屬性都是可變的九火。

ServerWebExchangeUtils和上下文屬性

ServerWebExchangeUtils里面存放了很多靜態(tài)公有的字符串KEY值(這些字符串KEY的實(shí)際值是org.springframework.cloud.gateway.support.ServerWebExchangeUtils. + 下面任意的靜態(tài)公有KEY)赚窃,這些字符串KEY值一般是用于ServerWebExchange的屬性(Attribute,見上文的ServerWebExchange#getAttributes()方法)的KEY岔激,這些屬性值都是有特殊的含義勒极,在使用過濾器的時(shí)候如果時(shí)機(jī)適當(dāng)可以直接取出來使用,下面逐個(gè)分析虑鼎。

  • PRESERVE_HOST_HEADER_ATTRIBUTE:是否保存Host屬性辱匿,值是布爾值類型键痛,寫入位置是PreserveHostHeaderGatewayFilterFactory,使用的位置是NettyRoutingFilter匾七,作用是如果設(shè)置為true絮短,HTTP請(qǐng)求頭中的Host屬性會(huì)寫到底層Reactor-Netty的請(qǐng)求Header屬性中。
  • CLIENT_RESPONSE_ATTR:保存底層Reactor-Netty的響應(yīng)對(duì)象昨忆,類型是reactor.netty.http.client.HttpClientResponse丁频。
  • CLIENT_RESPONSE_CONN_ATTR:保存底層Reactor-Netty的連接對(duì)象,類型是reactor.netty.Connection邑贴。
  • URI_TEMPLATE_VARIABLES_ATTRIBUTEPathRoutePredicateFactory解析路徑參數(shù)完成之后席里,把解析完成后的占位符KEY-路徑Path映射存放在ServerWebExchange的屬性中,KEY就是URI_TEMPLATE_VARIABLES_ATTRIBUTE拢驾。
  • CLIENT_RESPONSE_HEADER_NAMES:保存底層Reactor-Netty的響應(yīng)Header的名稱集合奖磁。
  • GATEWAY_ROUTE_ATTR:用于存放RoutePredicateHandlerMapping中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route)實(shí)例,通過這個(gè)路由實(shí)例可以得知當(dāng)前請(qǐng)求會(huì)路由到下游哪個(gè)服務(wù)繁疤。
  • GATEWAY_REQUEST_URL_ATTRjava.net.URI類型的實(shí)例署穗,這個(gè)實(shí)例代表直接請(qǐng)求或者負(fù)載均衡處理之后需要請(qǐng)求到下游服務(wù)的真實(shí)URI。
  • GATEWAY_ORIGINAL_REQUEST_URL_ATTRjava.net.URI類型的實(shí)例嵌洼,需要重寫請(qǐng)求URI的時(shí)候,保存原始的請(qǐng)求URI封恰。
  • GATEWAY_HANDLER_MAPPER_ATTR:保存當(dāng)前使用的HandlerMapping具體實(shí)例的類型簡(jiǎn)稱(一般是字符串"RoutePredicateHandlerMapping")麻养。
  • GATEWAY_SCHEME_PREFIX_ATTR:確定目標(biāo)路由URI中如果存在schemeSpecificPart屬性,則保存該URI的scheme在此屬性中诺舔,路由URI會(huì)被重新構(gòu)造鳖昌,見RouteToRequestUrlFilter
  • GATEWAY_PREDICATE_ROUTE_ATTR:用于存放RoutePredicateHandlerMapping中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route)實(shí)例的ID低飒。
  • WEIGHT_ATTR:實(shí)驗(yàn)性功能(此版本還不建議在正式版本使用)存放分組權(quán)重相關(guān)屬性许昨,見WeightCalculatorWebFilter
  • ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR:存放響應(yīng)Header中的ContentType的值褥赊。
  • HYSTRIX_EXECUTION_EXCEPTION_ATTRThrowable的實(shí)例糕档,存放的是Hystrix執(zhí)行異常時(shí)候的異常實(shí)例,見HystrixGatewayFilterFactory拌喉。
  • GATEWAY_ALREADY_ROUTED_ATTR:布爾值速那,用于判斷是否已經(jīng)進(jìn)行了路由,見NettyRoutingFilter尿背。
  • GATEWAY_ALREADY_PREFIXED_ATTR:布爾值端仰,用于判斷請(qǐng)求路徑是否被添加了前置部分,見PrefixPathGatewayFilterFactory田藐。

ServerWebExchangeUtils提供的上下文屬性用于Spring Cloud GatewayServerWebExchange組件處理請(qǐng)求和響應(yīng)的時(shí)候荔烧,內(nèi)部一些重要實(shí)例或者標(biāo)識(shí)屬性的安全傳輸和使用吱七,使用它們可能存在一定的風(fēng)險(xiǎn),因?yàn)闆]有人可以確定在版本升級(jí)之后鹤竭,原有的屬性KEY或者VALUE是否會(huì)發(fā)生改變踊餐,如果評(píng)估過風(fēng)險(xiǎn)或者規(guī)避了風(fēng)險(xiǎn)之后,可以安心使用诺擅。例如我們?cè)谧稣?qǐng)求和響應(yīng)日志(類似Nginx的Access Log)的時(shí)候市袖,可以依賴到GATEWAY_ROUTE_ATTR,因?yàn)槲覀円蛴÷酚傻哪繕?biāo)信息烁涌。舉個(gè)簡(jiǎn)單例子:

@Slf4j
@Component
public class AccessLogFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        HttpMethod method = request.getMethod();
        // 獲取路由的目標(biāo)URI
        URI targetUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain.filter(exchange.mutate().build()).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("請(qǐng)求路徑:{},客戶端遠(yuǎn)程IP地址:{},請(qǐng)求方法:{},目標(biāo)URI:{},響應(yīng)碼:{}",
                    path, remoteAddress, method, targetUri, statusCode);
        }));
    }
}
復(fù)制代碼

修改請(qǐng)求體

修改請(qǐng)求體是一個(gè)比較常見的需求苍碟。例如我們使用Spring Cloud Gateway實(shí)現(xiàn)網(wǎng)關(guān)的時(shí)候,要實(shí)現(xiàn)一個(gè)功能:把存放在請(qǐng)求頭中的JWT解析后撮执,提取里面的用戶ID微峰,然后寫入到請(qǐng)求體中。我們簡(jiǎn)化這個(gè)場(chǎng)景抒钱,假設(shè)我們把userId明文存放在請(qǐng)求頭中的accessToken中蜓肆,請(qǐng)求體是一個(gè)JSON結(jié)構(gòu):

{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload" : {
        // ... 這里是有效載荷,存放具體的數(shù)據(jù)
    }
}
復(fù)制代碼

我們需要提取accessToken谋币,也就是userId插入到請(qǐng)求體JSON中如下:

{
    "userId": "用戶ID",
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload" : {
        // ... 這里是有效載荷仗扬,存放具體的數(shù)據(jù)
    }
}
復(fù)制代碼

這里為了簡(jiǎn)化設(shè)計(jì),用全局過濾器GlobalFilter實(shí)現(xiàn)蕾额,實(shí)際需要結(jié)合具體場(chǎng)景考慮:

@Slf4j
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter {

    private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String accessToken = request.getHeaders().getFirst("accessToken");
        if (!StringUtils.hasLength(accessToken)) {
            throw new IllegalArgumentException("accessToken");
        }
        // 新建一個(gè)ServerHttpRequest裝飾器,覆蓋需要裝飾的方法
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {

            @Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> body = super.getBody();
                InputStreamHolder holder = new InputStreamHolder();
                body.subscribe(buffer -> holder.inputStream = buffer.asInputStream());
                if (null != holder.inputStream) {
                    try {
                        // 解析JSON的節(jié)點(diǎn)
                        JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
                        Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式異常");
                        ObjectNode objectNode = (ObjectNode) jsonNode;
                        // JSON節(jié)點(diǎn)最外層寫入新的屬性
                        objectNode.put("userId", accessToken);
                        DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
                        String json = objectNode.toString();
                        log.info("最終的JSON數(shù)據(jù)為:{}", json);
                        dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
                        return Flux.just(dataBuffer);
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                } else {
                    return super.getBody();
                }
            }
        };
        // 使用修改后的ServerHttpRequestDecorator重新生成一個(gè)新的ServerWebExchange
        return chain.filter(exchange.mutate().request(decorator).build());
    }

    private class InputStreamHolder {

        InputStream inputStream;
    }
}
復(fù)制代碼

測(cè)試一下:

// HTTP
POST /order/json HTTP/1.1
Host: localhost:9090
Content-Type: application/json
accessToken: 10086
Accept: */*
Cache-Control: no-cache
Host: localhost:9090
accept-encoding: gzip, deflate
content-length: 94
Connection: keep-alive
cache-control: no-cache

{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload": {
        "name": "doge"
    }
}

// 日志輸出
最終的JSON數(shù)據(jù)為:{"serialNumber":"請(qǐng)求流水號(hào)","payload":{"name":"doge"},"userId":"10086"}
復(fù)制代碼

最重要的是用到了ServerHttpRequest裝飾器ServerHttpRequestDecorator早芭,主要覆蓋對(duì)應(yīng)獲取請(qǐng)求體數(shù)據(jù)緩沖區(qū)的方法即可,至于怎么處理其他邏輯需要自行考慮诅蝶,這里只是做一個(gè)簡(jiǎn)單的示范退个。一般的代碼邏輯如下:

ServerHttpRequest request = exchange.getRequest();
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(request) {

     @Override
     public Flux<DataBuffer> getBody() {
         // 拿到承載原始請(qǐng)求體的Flux
         Flux<DataBuffer> body = super.getBody();
         // 這里通過自定義方式生成新的承載請(qǐng)求體的Flux
         Flux<DataBuffer> newBody = ...
         return newBody;
     }            
}
return chain.filter(exchange.mutate().request(requestDecorator).build());    
復(fù)制代碼

修改響應(yīng)體

修改響應(yīng)體的需求也是比較常見的,具體的做法和修改請(qǐng)求體差不多调炬。例如我們想要實(shí)現(xiàn)下面的功能:第三方服務(wù)請(qǐng)求經(jīng)過網(wǎng)關(guān)语盈,原始報(bào)文是密文,我們需要在網(wǎng)關(guān)實(shí)現(xiàn)密文解密缰泡,然后把解密后的明文路由到下游服務(wù)刀荒,下游服務(wù)處理成功響應(yīng)明文,需要在網(wǎng)關(guān)把明文加密成密文再返回到第三方服務(wù)〖現(xiàn)在簡(jiǎn)化整個(gè)流程照棋,用AES加密算法,統(tǒng)一密碼為字符串"throwable"武翎,假設(shè)請(qǐng)求報(bào)文和響應(yīng)報(bào)文明文如下:

// 請(qǐng)求密文
{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload" : "加密后的請(qǐng)求消息載荷"
}

// 請(qǐng)求明文(僅僅作為提示)
{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload" : "{\"name:\":\"doge\"}"
}

// 響應(yīng)密文
{
    "code": 200,
    "message":"ok",
    "payload" : "加密后的響應(yīng)消息載荷"
}

// 響應(yīng)明文(僅僅作為提示)
{
    "code": 200,
    "message":"ok",
    "payload" : "{\"name:\":\"doge\",\"age\":26}"
}
復(fù)制代碼

為了方便一些加解密或者編碼解碼的實(shí)現(xiàn)烈炭,需要引入Apachecommons-codec類庫(kù):

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.12</version>
</dependency>
復(fù)制代碼

這里定義一個(gè)全局過濾器專門處理加解密,實(shí)際上最好結(jié)合真實(shí)的場(chǎng)景決定是否適合全局過濾器宝恶,這里只是一個(gè)示例:

// AES加解密工具類
public enum AesUtils {

    // 單例
    X;

    private static final String PASSWORD = "throwable";
    private static final String KEY_ALGORITHM = "AES";
    private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    public String encrypt(String content) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, provideSecretKey());
            return Hex.encodeHexString(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public byte[] decrypt(String content) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, provideSecretKey());
            return cipher.doFinal(Hex.decodeHex(content));
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private SecretKey provideSecretKey() {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM);
            SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
            secureRandom.setSeed(PASSWORD.getBytes(StandardCharsets.UTF_8));
            keyGen.init(128, secureRandom);
            return new SecretKeySpec(keyGen.generateKey().getEncoded(), KEY_ALGORITHM);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}

// EncryptionGlobalFilter
@Slf4j
@Component
public class EncryptionGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public int getOrder() {
        return -2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
        ServerHttpRequestDecorator requestDecorator = processRequest(request, bufferFactory);
        ServerHttpResponseDecorator responseDecorator = processResponse(response, bufferFactory);
        return chain.filter(exchange.mutate().request(requestDecorator).response(responseDecorator).build());
    }

    private ServerHttpRequestDecorator processRequest(ServerHttpRequest request, DataBufferFactory bufferFactory) {
        Flux<DataBuffer> body = request.getBody();
        DataBufferHolder holder = new DataBufferHolder();
        body.subscribe(dataBuffer -> {
            int len = dataBuffer.readableByteCount();
            holder.length = len;
            byte[] bytes = new byte[len];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            String text = new String(bytes, StandardCharsets.UTF_8);
            JsonNode jsonNode = readNode(text);
            JsonNode payload = jsonNode.get("payload");
            String payloadText = payload.asText();
            byte[] content = AesUtils.X.decrypt(payloadText);
            String requestBody = new String(content, StandardCharsets.UTF_8);
            log.info("修改請(qǐng)求體payload,修改前:{},修改后:{}", payloadText, requestBody);
            rewritePayloadNode(requestBody, jsonNode);
            DataBuffer data = bufferFactory.allocateBuffer();
            data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
            holder.dataBuffer = data;
        });
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(request.getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        return new ServerHttpRequestDecorator(request) {

            @Override
            public HttpHeaders getHeaders() {
                int contentLength = holder.length;
                if (contentLength > 0) {
                    headers.setContentLength(contentLength);
                } else {
                    headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return headers;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return Flux.just(holder.dataBuffer);
            }
        };
    }

    private ServerHttpResponseDecorator processResponse(ServerHttpResponse response, DataBufferFactory bufferFactory) {
        return new ServerHttpResponseDecorator(response) {

            @SuppressWarnings("unchecked")
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(flux.map(buffer -> {
                        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
                        DataBufferUtils.release(buffer);
                        JsonNode jsonNode = readNode(charBuffer.toString());
                        JsonNode payload = jsonNode.get("payload");
                        String text = payload.toString();
                        String content = AesUtils.X.encrypt(text);
                        log.info("修改響應(yīng)體payload,修改前:{},修改后:{}", text, content);
                        setPayloadTextNode(content, jsonNode);
                        return bufferFactory.wrap(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
                    }));
                }
                return super.writeWith(body);
            }
        };
    }

    private void rewritePayloadNode(String text, JsonNode root) {
        try {
            JsonNode node = objectMapper.readTree(text);
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload", node);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private void setPayloadTextNode(String text, JsonNode root) {
        try {
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload", new TextNode(text));
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private class DataBufferHolder {

        DataBuffer dataBuffer;
        int length;
    }
}  
復(fù)制代碼

先準(zhǔn)備一份密文:

Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "請(qǐng)求流水號(hào)");
String content = "{\"name\": \"doge\"}";
json.put("payload", AesUtils.X.encrypt(content));
System.out.println(new ObjectMapper().writeValueAsString(json));

// 輸出
{"serialNumber":"請(qǐng)求流水號(hào)","payload":"144e3dc734743f5709f1adf857bca473da683246fd612f86ac70edeb5f2d2729"}
復(fù)制代碼

模擬請(qǐng)求:

POST /order/json HTTP/1.1
Host: localhost:9090
accessToken: 10086
Content-Type: application/json
User-Agent: PostmanRuntime/7.13.0
Accept: */*
Cache-Control: no-cache
Postman-Token: bda07fc3-ea1a-478c-b4d7-754fe6f37200,634734d9-feed-4fc9-ba20-7618bd986e1c
Host: localhost:9090
cookie: customCookieName=customCookieValue
accept-encoding: gzip, deflate
content-length: 104
Connection: keep-alive
cache-control: no-cache

{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload": "FE49xzR0P1cJ8a34V7ykc9poMkb9YS+GrHDt618tJyk="
}

// 響應(yīng)結(jié)果
{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload": "oo/K1igg2t/S8EExkBVGWOfI1gAh5pBpZ0wyjNPW6e8="   # <--- 解密后:{"name":"doge","age":26}
}
復(fù)制代碼

遇到的問題:

  • 必須實(shí)現(xiàn)Ordered接口符隙,返回一個(gè)小于-1的order值趴捅,這是因?yàn)?code>NettyWriteResponseFilter的order值為-1,我們需要覆蓋返回響應(yīng)體的邏輯霹疫,自定義的GlobalFilter必須比NettyWriteResponseFilter優(yōu)先執(zhí)行拱绑。
  • 網(wǎng)關(guān)每次重啟之后,第一個(gè)請(qǐng)求總是無法從原始的ServerHttpRequest讀取到有效的Body丽蝎,準(zhǔn)確來說出現(xiàn)的現(xiàn)象是NettyRoutingFilter調(diào)用ServerHttpRequest#getBody()的時(shí)候獲取到一個(gè)空的對(duì)象猎拨,導(dǎo)致空指針;奇怪的是從第二個(gè)請(qǐng)求開始就能正常調(diào)用屠阻。筆者把Spring Cloud Gateway的版本降低到Finchley.SR3红省,Spring Boot的版本降低到2.0.8.RELEASE,問題不再出現(xiàn)国觉,初步確定是Spring Cloud Gateway版本升級(jí)導(dǎo)致的兼容性問題或者是BUG吧恃。

最重要的是用到了ServerHttpResponse裝飾器ServerHttpResponseDecorator,主要覆蓋寫入響應(yīng)體數(shù)據(jù)緩沖區(qū)的部分麻诀,至于怎么處理其他邏輯需要自行考慮痕寓,這里只是做一個(gè)簡(jiǎn)單的示范。一般的代碼邏輯如下:

ServerHttpResponse response = exchange.getResponse();
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(flux.map(buffer -> {
                        // buffer就是原始的響應(yīng)數(shù)據(jù)的緩沖區(qū)
                        // 下面處理完畢之后返回新的響應(yīng)數(shù)據(jù)的緩沖區(qū)即可
                        return bufferFactory.wrap(...);
                    }));
                }
                return super.writeWith(body);
            }
        };
return chain.filter(exchange.mutate().response(responseDecorator).build());    
復(fù)制代碼

請(qǐng)求體或者響應(yīng)體報(bào)文過大的問題

有熱心的同學(xué)告訴筆者蝇闭,如果請(qǐng)求報(bào)文過大或者響應(yīng)報(bào)文過大的時(shí)候呻率,前面兩節(jié)的修改請(qǐng)求和響應(yīng)報(bào)文的方法會(huì)出現(xiàn)問題,這里嘗試重現(xiàn)一下遇到的具體問題呻引。先把請(qǐng)求報(bào)文嘗試加長(zhǎng):

Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "請(qǐng)求流水號(hào)");
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    builder.append("doge");
}
String content = String.format("{\"name\": \"%s\"}", builder.toString());
json.put("payload", AesUtils.X.encrypt(content));
System.out.println(new ObjectMapper().writeValueAsString(json));

// 請(qǐng)求的JSON報(bào)文如下:
{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload": "0Dcf2plFpESprKjkdqNHM8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/zyJ4ipyLGvo5LX87d9oDAs="
}
復(fù)制代碼

用上面的請(qǐng)求報(bào)文發(fā)起請(qǐng)求筷凤,確實(shí)存在問題:

[圖片上傳中...(image-a54118-1564041069471-0)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

主要問題是:

  • 請(qǐng)求體包數(shù)據(jù)裝成的Flux<DataBuffer>實(shí)例被訂閱之后,讀取到的字節(jié)數(shù)組的長(zhǎng)度被截?cái)嗔税撸峁┑脑颊?qǐng)求報(bào)文里面字符串長(zhǎng)度要大于1000,轉(zhuǎn)換成byte數(shù)組絕對(duì)要大于1000挪丢,但是上面的示例中只讀取到長(zhǎng)度為673的byte數(shù)組蹂风。
  • 讀取到的字節(jié)數(shù)組被截?cái)嗪螅瑒t使用Jackson進(jìn)行反序列化的時(shí)候提示沒有讀取到字符串的EOF標(biāo)識(shí)乾蓬,導(dǎo)致反序列化失敗惠啄。

既然遇到了問題,就想辦法解決任内。首先第一步定位一下是什么原因撵渡,直覺告訴筆者:要開啟一下DEBUG日志進(jìn)行觀察,如果還沒有頭緒可能要跟蹤一下源碼死嗦。

開啟DEBUG日志級(jí)別之后做一次請(qǐng)求趋距,發(fā)現(xiàn)了一些可疑的日志信息:

2019-05-19 11:16:15.660 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58012] READ COMPLETE
2019-05-19 11:16:15.660 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 ! R:/0:0:0:0:0:0:0:1:58012] INACTIVE
2019-05-19 11:16:15.660 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] READ: 1024B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 50 4f 53 54 20 2f 6f 72 64 65 72 2f 6a 73 6f 6e |POST /order/json|
|00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 61 63 63 65 73 | HTTP/1.1..acces|
|00000020| 73 54 6f 6b 65 6e 3a 20 31 30 30 38 36 0d 0a 43 |sToken: 10086..C|
|00000030| 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 61 70 70 |ontent-Type: app|
|00000040| 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 0d 0a 55 |lication/json..U|
|00000050| 73 65 72 2d 41 67 65 6e 74 3a 20 50 6f 73 74 6d |ser-Agent: Postm|
|00000060| 61 6e 52 75 6e 74 69 6d 65 2f 37 2e 31 33 2e 30 |anRuntime/7.13.0|
|00000070| 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 43 |..Accept: */*..C|
|00000080| 61 63 68 65 2d 43 6f 6e 74 72 6f 6c 3a 20 6e 6f |ache-Control: no|
|00000090| 2d 63 61 63 68 65 0d 0a 50 6f 73 74 6d 61 6e 2d |-cache..Postman-|
|000000a0| 54 6f 6b 65 6e 3a 20 31 31 32 30 38 64 35 39 2d |Token: 11208d59-|
|000000b0| 65 61 34 61 2d 34 62 39 63 2d 61 30 33 39 2d 30 |ea4a-4b9c-a039-0|
|000000c0| 30 65 36 64 38 61 30 65 33 65 66 0d 0a 48 6f 73 |0e6d8a0e3ef..Hos|
|000000d0| 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 39 30 39 |t: localhost:909|
|000000e0| 30 0d 0a 63 6f 6f 6b 69 65 3a 20 63 75 73 74 6f |0..cookie: custo|
|000000f0| 6d 43 6f 6f 6b 69 65 4e 61 6d 65 3d 63 75 73 74 |mCookieName=cust|
|00000100| 6f 6d 43 6f 6f 6b 69 65 56 61 6c 75 65 0d 0a 61 |omCookieValue..a|
|00000110| 63 63 65 70 74 2d 65 6e 63 6f 64 69 6e 67 3a 20 |ccept-encoding: |
|00000120| 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 0d 0a 63 |gzip, deflate..c|
|00000130| 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a 20 35 |ontent-length: 5|
|00000140| 34 31 36 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a |416..Connection:|
|00000150| 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a 0d 0a 7b | keep-alive....{|
|00000160| 0a 20 20 20 20 22 73 65 72 69 61 6c 4e 75 6d 62 |.    "serialNumb|
|00000170| 65 72 22 3a 20 22 e8 af b7 e6 b1 82 e6 b5 81 e6 |er": "..........|
|00000180| b0 b4 e5 8f b7 22 2c 0a 20 20 20 20 22 70 61 79 |.....",.    "pay|
|00000190| 6c 6f 61 64 22 3a 20 22 30 44 63 66 32 70 6c 46 |load": "0Dcf2plF|
|000001a0| 70 45 53 70 72 4b 6a 6b 64 71 4e 48 4d 38 6a 6a |pESprKjkdqNHM8jj|
|000001b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000001c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000001d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000001e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000001f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000200| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000210| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000220| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000230| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000240| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000250| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000260| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000270| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000280| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000290| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000002a0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000002b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000002c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000002d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000002e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000002f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000300| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000310| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000320| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000330| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000340| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000350| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000360| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000370| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000380| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000390| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000003a0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000003b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000003c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000003d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000003e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000003f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
+--------+-------------------------------------------------+----------------+
2019-05-19 11:16:15.662 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 ! R:/0:0:0:0:0:0:0:1:58012] UNREGISTERED
2019-05-19 11:16:15.665 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServerOperations - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] Increasing pending responses, now 1
2019-05-19 11:16:15.671 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] READ COMPLETE
復(fù)制代碼

注意一下關(guān)鍵字READ: 1024B,這里應(yīng)該是底層的Reactor-Netty讀取的最大數(shù)據(jù)報(bào)的長(zhǎng)度限制越除,打印出來的數(shù)據(jù)報(bào)剛好也是1024B的大小节腐,這個(gè)應(yīng)該就是導(dǎo)致請(qǐng)求體被截?cái)嗟母驹蛲舛ⅲ贿@個(gè)問題不單單會(huì)出現(xiàn)在請(qǐng)求體的獲取,也會(huì)出現(xiàn)在響應(yīng)體的寫入翼雀。既然這個(gè)是共性的問題饱苟,那么項(xiàng)目Github上肯定有對(duì)應(yīng)的Issue,找到一個(gè)互動(dòng)比較長(zhǎng)的gateway request size limit 1024B because netty default limit 1024,how to solve it? #581狼渊,從回答來看箱熬,官方建議使用ModifyRequestBodyGatewayFilterFactoryModifyResponseBodyGatewayFilterFactory完成對(duì)應(yīng)的功能。這里可以嘗試借鑒一下ModifyRequestBodyGatewayFilterFactory的實(shí)現(xiàn)方式修改之前的代碼狈邑,因?yàn)榇a的邏輯比較長(zhǎng)和復(fù)雜城须,解密請(qǐng)求體的過濾器拆分到新的類RequestEncryptionGlobalFilter,加密響應(yīng)體的過濾器拆分到ResponseDecryptionGlobalFilter

RequestEncryptionGlobalFilter的代碼如下:

@Slf4j
@Component
public class RequestEncryptionGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private ObjectMapper objectMapper;

    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    @Override
    public int getOrder() {
        return -2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return processRequest(exchange, chain);
    }

    private Mono<Void> processRequest(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerRequest serverRequest = new DefaultServerRequest(exchange, messageReaders);
        DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
        Mono<String> rawBody = serverRequest.bodyToMono(String.class).map(s -> s);
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(rawBody, String.class);
        HttpHeaders tempHeaders = new HttpHeaders();
        tempHeaders.putAll(exchange.getRequest().getHeaders());
        tempHeaders.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, tempHeaders);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            Flux<DataBuffer> body = outputMessage.getBody();
            DataBufferHolder holder = new DataBufferHolder();
            body.subscribe(dataBuffer -> {
                int len = dataBuffer.readableByteCount();
                holder.length = len;
                byte[] bytes = new byte[len];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                String text = new String(bytes, StandardCharsets.UTF_8);
                JsonNode jsonNode = readNode(text);
                JsonNode payload = jsonNode.get("payload");
                String payloadText = payload.asText();
                byte[] content = AesUtils.X.decrypt(payloadText);
                String requestBody = new String(content, StandardCharsets.UTF_8);
                log.info("修改請(qǐng)求體payload,修改前:{},修改后:{}", payloadText, requestBody);
                rewritePayloadNode(requestBody, jsonNode);
                DataBuffer data = bufferFactory.allocateBuffer();
                data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
                holder.dataBuffer = data;
            });
            ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {

                @Override
                public HttpHeaders getHeaders() {
                    long contentLength = tempHeaders.getContentLength();
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                @Override
                public Flux<DataBuffer> getBody() {
                    return Flux.just(holder.dataBuffer);
                }
            };
            return chain.filter(exchange.mutate().request(requestDecorator).build());
        }));
    }

    private void rewritePayloadNode(String text, JsonNode root) {
        try {
            JsonNode node = objectMapper.readTree(text);
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload", node);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private void setPayloadTextNode(String text, JsonNode root) {
        try {
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload", new TextNode(text));
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private class DataBufferHolder {

        DataBuffer dataBuffer;
        int length;
    }
}
復(fù)制代碼

ResponseDecryptionGlobalFilter的代碼如下:

@Slf4j
@Component
public class ResponseDecryptionGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return processResponse(exchange, chain);
    }

    private Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
                ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
                DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
                Mono<String> rawBody = clientResponse.bodyToMono(String.class).map(s -> s);
                BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(rawBody, String.class);
                CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
                return bodyInserter.insert(outputMessage, new BodyInserterContext())
                        .then(Mono.defer(() -> {
                            Flux<DataBuffer> messageBody = outputMessage.getBody();
                            Flux<DataBuffer> flux = messageBody.map(buffer -> {
                                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
                                DataBufferUtils.release(buffer);
                                JsonNode jsonNode = readNode(charBuffer.toString());
                                JsonNode payload = jsonNode.get("payload");
                                String text = payload.toString();
                                String content = AesUtils.X.encrypt(text);
                                log.info("修改響應(yīng)體payload,修改前:{},修改后:{}", text, content);
                                setPayloadTextNode(content, jsonNode);
                                return getDelegate().bufferFactory().wrap(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
                            });
                            HttpHeaders headers = getDelegate().getHeaders();
                            if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                flux = flux.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
                            }
                            return getDelegate().writeWith(flux);
                        }));
            }
        };
        return chain.filter(exchange.mutate().response(responseDecorator).build());
    }

    private void setPayloadTextNode(String text, JsonNode root) {
        try {
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload", new TextNode(text));
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private class ResponseAdapter implements ClientHttpResponse {

        private final Flux<DataBuffer> flux;
        private final HttpHeaders headers;

        @SuppressWarnings("unchecked")
        private ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
            this.headers = headers;
            if (body instanceof Flux) {
                flux = (Flux) body;
            } else {
                flux = ((Mono) body).flux();
            }
        }

        @Override
        public Flux<DataBuffer> getBody() {
            return flux;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        @Override
        public HttpStatus getStatusCode() {
            return null;
        }

        @Override
        public int getRawStatusCode() {
            return 0;
        }

        @Override
        public MultiValueMap<String, ResponseCookie> getCookies() {
            return null;
        }
    }
}
復(fù)制代碼

模擬請(qǐng)求:

POST /order/json HTTP/1.1
Host: localhost:9090
accessToken: 10086
Content-Type: application/json
User-Agent: PostmanRuntime/7.13.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 3a830202-f3d1-450e-839f-ae8f3b88bced,b229feb1-7c8b-4d25-a039-09345f3fe8f0
Host: localhost:9090
cookie: customCookieName=customCookieValue
accept-encoding: gzip, deflate
content-length: 5416
Connection: keep-alive
cache-control: no-cache

{
    "serialNumber": "請(qǐng)求流水號(hào)",
    "payload": "0Dcf2plFpESprKjkdqNHM8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/zyJ4ipyLGvo5LX87d9oDAs="
}

// 響應(yīng)
{"serialNumber":"請(qǐng)求流水號(hào)","userId":null,"payload":"7S2VqLu4J6LdW0As50JgZ0eFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+Dm8rTVHylECORYnLNgnfWx0ENJ9a6E+abYhyFJ9zSIda"}
復(fù)制代碼

徹底解決了之前的請(qǐng)求或者響應(yīng)報(bào)文截?cái)嗟膯栴}官地,筆者發(fā)現(xiàn)了很多博文都在(照搬)更改讀取DataBuffer實(shí)例時(shí)候的代碼邏輯酿傍,其實(shí)那段邏輯是不相關(guān)的,可以嘗試用BufferedReader基于行讀取然后用StringBuilder承載驱入,或者像本文那樣直接讀取為byte數(shù)組等等赤炒,因?yàn)楦镜脑蚴堑讓拥?code>Reactor-Netty的數(shù)據(jù)塊讀取大小限制導(dǎo)致獲取到的DataBuffer實(shí)例里面的數(shù)據(jù)是不完整的,解決方案就是參照Spring Cloud Gateway本身提供的基礎(chǔ)類庫(kù)進(jìn)行改造(暫時(shí)沒發(fā)現(xiàn)有入口可以調(diào)整Reactor-Netty的配置)亏较,難度也不大莺褒。

小結(jié)

剛好遇到一個(gè)需求需要做網(wǎng)關(guān)的加解密包括請(qǐng)求體和響應(yīng)體的修改,這里順便把Spring Cloud Gateway一些涉及到這方面的一些內(nèi)容梳理了一遍雪情,順便把坑踩了并且填完遵岩。下一步嘗試按照目前官方提供的可用組件修改一下實(shí)現(xiàn)自定義的邏輯,包括Hystrix巡通、基于EurekaRibbon的負(fù)載均衡尘执、限流等等。

對(duì)文章感興趣的朋友宴凉,可以加我的群來聊聊喲

歡迎工作一到五年的Java工程師朋友們加入JavaQQ群:219571750誊锭,群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)弥锄、高性能及分布式丧靡、Jvm性能調(diào)優(yōu)、Spring源碼籽暇,MyBatis温治,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰戒悠!趁年輕熬荆,使勁拼,給未來的自己一個(gè)交代绸狐!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惶看,一起剝皮案震驚了整個(gè)濱河市捏顺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纬黎,老刑警劉巖幅骄,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異本今,居然都是意外死亡拆座,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門冠息,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挪凑,“玉大人,你說我怎么就攤上這事逛艰□锾迹” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵散怖,是天一觀的道長(zhǎng)菇绵。 經(jīng)常有香客問我,道長(zhǎng)镇眷,這世上最難降的妖魔是什么咬最? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮欠动,結(jié)果婚禮上永乌,老公的妹妹穿的比我還像新娘。我一直安慰自己具伍,他們只是感情好翅雏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著人芽,像睡著了一般望几。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啼肩,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音衙伶,去河邊找鬼祈坠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛矢劲,可吹牛的內(nèi)容都是我干的赦拘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼芬沉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瘦材!你這毒婦竟也來了末誓?” 一聲冷哼從身側(cè)響起遮婶,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剃袍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捎谨,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡民效,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涛救。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏邢。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖检吆,靈堂內(nèi)的尸體忽然破棺而出舒萎,到底是詐尸還是另有隱情,我是刑警寧澤蹭沛,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布臂寝,位于F島的核電站,受9級(jí)特大地震影響致板,放射性物質(zhì)發(fā)生泄漏交煞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一斟或、第九天 我趴在偏房一處隱蔽的房頂上張望素征。 院中可真熱鬧,春花似錦萝挤、人聲如沸御毅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)端蛆。三九已至,卻和暖如春酥泛,著一層夾襖步出監(jiān)牢的瞬間今豆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工柔袁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呆躲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓捶索,卻偏偏與公主長(zhǎng)得像插掂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355