Spring Cloud Gateway-ServerWebExchange核心方法與請求或者響應內容的修改

前提

  • 本文編寫的時候使用的Spring Cloud Gateway版本爲當時最新的版本Greenwich.SR1

咱們在使用Spring Cloud Gateway的時候,注意到過濾器(包括GatewayFilterGlobalFilter和過濾器鏈GatewayFilterChain),都依賴到ServerWebExchangejava

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對象,還提供了一些擴展方法,若是想實現改造請求參數或者響應參數,就必須深刻了解ServerWebExchangenode

理解ServerWebExchange

先看ServerWebExchange的註釋:react

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

翻譯一下大概是:github

ServerWebExchange是一個HTTP請求-響應交互的契約。提供對HTTP請求和響應的訪問,並公開額外的服務器端處理相關屬性和特性,如請求屬性。web

其實,ServerWebExchange命名爲服務網絡交換器,存放着重要的請求-響應屬性、請求實例和響應實例等等,有點像Context的角色。算法

ServerWebExchange接口

ServerWebExchange接口的全部方法:spring

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()方法生成一個新的實例,例如這樣:json

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的父類
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;
}
複製代碼

可知ReactorServerHttpResponse構造函數初始化實例的時候,存放響應Header的是HttpHeaders實例,也就是響應Header是能夠直接修改的。

ServerHttpResponse接口以下:

public interface HttpMessage {
    
    // 獲取響應Header,目前的實現中返回的是HttpHeaders實例,能夠直接修改
    HttpHeaders getHeaders();
}  

public interface ReactiveHttpOutputMessage extends HttpMessage {
    
    // 獲取DataBufferFactory實例,用於包裝或者生成數據緩衝區DataBuffer實例(建立響應體)
    DataBufferFactory bufferFactory();

    // 註冊一個動做,在HttpOutputMessage提交以前此動做會進行回調
    void beforeCommit(Supplier<? extends Mono<Void>> action);

    // 判斷HttpOutputMessage是否已經提交
    boolean isCommitted();
    
    // 寫入消息體到HTTP協議層
    Mono<Void> writeWith(Publisher<? extends DataBuffer> body);

    // 寫入消息體到HTTP協議層而且刷新緩衝區
    Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body);
    
    // 指明消息處理已經結束,通常在消息處理結束自動調用此方法,屢次調用不會產生反作用
    Mono<Void> setComplete();
}

public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
    
    // 設置響應狀態碼
    boolean setStatusCode(@Nullable HttpStatus status);
    
    // 獲取響應狀態碼
    @Nullable
    HttpStatus getStatusCode();
    
    // 獲取響應Cookie,封裝爲MultiValueMap實例,能夠修改
    MultiValueMap<String, ResponseCookie> getCookies();  
    
    // 添加響應Cookie
    void addCookie(ResponseCookie cookie);  
}    
複製代碼

這裏能夠看到除了響應體比較難修改以外,其餘的屬性都是可變的。

ServerWebExchangeUtils和上下文屬性

ServerWebExchangeUtils裏面存放了不少靜態公有的字符串KEY值(這些字符串KEY的實際值是org.springframework.cloud.gateway.support.ServerWebExchangeUtils. + 下面任意的靜態公有KEY),這些字符串KEY值通常是用於ServerWebExchange的屬性(Attribute,見上文的ServerWebExchange#getAttributes()方法)的KEY,這些屬性值都是有特殊的含義,在使用過濾器的時候若是時機適當能夠直接取出來使用,下面逐個分析。

  • PRESERVE_HOST_HEADER_ATTRIBUTE:是否保存Host屬性,值是布爾值類型,寫入位置是PreserveHostHeaderGatewayFilterFactory,使用的位置是NettyRoutingFilter,做用是若是設置爲true,HTTP請求頭中的Host屬性會寫到底層Reactor-Netty的請求Header屬性中。
  • CLIENT_RESPONSE_ATTR:保存底層Reactor-Netty的響應對象,類型是reactor.netty.http.client.HttpClientResponse
  • CLIENT_RESPONSE_CONN_ATTR:保存底層Reactor-Netty的鏈接對象,類型是reactor.netty.Connection
  • URI_TEMPLATE_VARIABLES_ATTRIBUTEPathRoutePredicateFactory解析路徑參數完成以後,把解析完成後的佔位符KEY-路徑Path映射存放在ServerWebExchange的屬性中,KEY就是URI_TEMPLATE_VARIABLES_ATTRIBUTE
  • CLIENT_RESPONSE_HEADER_NAMES:保存底層Reactor-Netty的響應Header的名稱集合。
  • GATEWAY_ROUTE_ATTR:用於存放RoutePredicateHandlerMapping中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route)實例,經過這個路由實例能夠得知當前請求會路由到下游哪一個服務。
  • GATEWAY_REQUEST_URL_ATTRjava.net.URI類型的實例,這個實例表明直接請求或者負載均衡處理以後須要請求到下游服務的真實URI。
  • GATEWAY_ORIGINAL_REQUEST_URL_ATTRjava.net.URI類型的實例,須要重寫請求URI的時候,保存原始的請求URI。
  • GATEWAY_HANDLER_MAPPER_ATTR:保存當前使用的HandlerMapping具體實例的類型簡稱(通常是字符串"RoutePredicateHandlerMapping")。
  • GATEWAY_SCHEME_PREFIX_ATTR:肯定目標路由URI中若是存在schemeSpecificPart屬性,則保存該URI的scheme在此屬性中,路由URI會被從新構造,見RouteToRequestUrlFilter
  • GATEWAY_PREDICATE_ROUTE_ATTR:用於存放RoutePredicateHandlerMapping中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route)實例的ID。
  • WEIGHT_ATTR:實驗性功能(此版本還不建議在正式版本使用)存放分組權重相關屬性,見WeightCalculatorWebFilter
  • ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR:存放響應Header中的ContentType的值。
  • HYSTRIX_EXECUTION_EXCEPTION_ATTRThrowable的實例,存放的是Hystrix執行異常時候的異常實例,見HystrixGatewayFilterFactory
  • GATEWAY_ALREADY_ROUTED_ATTR:布爾值,用於判斷是否已經進行了路由,見NettyRoutingFilter
  • GATEWAY_ALREADY_PREFIXED_ATTR:布爾值,用於判斷請求路徑是否被添加了前置部分,見PrefixPathGatewayFilterFactory

ServerWebExchangeUtils提供的上下文屬性用於Spring Cloud GatewayServerWebExchange組件處理請求和響應的時候,內部一些重要實例或者標識屬性的安全傳輸和使用,使用它們可能存在必定的風險,由於沒有人能夠肯定在版本升級以後,原有的屬性KEY或者VALUE是否會發生改變,若是評估過風險或者規避了風險以後,能夠安心使用。例如咱們在作請求和響應日誌(相似Nginx的Access Log)的時候,能夠依賴到GATEWAY_ROUTE_ATTR,由於咱們要打印路由的目標信息。舉個簡單例子:

@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();
        // 獲取路由的目標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("請求路徑:{},客戶端遠程IP地址:{},請求方法:{},目標URI:{},響應碼:{}",
                    path, remoteAddress, method, targetUri, statusCode);
        }));
    }
}
複製代碼

修改請求體

修改請求體是一個比較常見的需求。例如咱們使用Spring Cloud Gateway實現網關的時候,要實現一個功能:把存放在請求頭中的JWT解析後,提取裏面的用戶ID,而後寫入到請求體中。咱們簡化這個場景,假設咱們把userId明文存放在請求頭中的accessToken中,請求體是一個JSON結構:

{
    "serialNumber": "請求流水號",
    "payload" : {
        // ... 這裏是有效載荷,存放具體的數據
    }
}
複製代碼

咱們須要提取accessToken,也就是userId插入到請求體JSON中以下:

{
    "userId": "用戶ID",
    "serialNumber": "請求流水號",
    "payload" : {
        // ... 這裏是有效載荷,存放具體的數據
    }
}
複製代碼

這裏爲了簡化設計,用全局過濾器GlobalFilter實現,實際須要結合具體場景考慮:

@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");
        }
        // 新建一個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的節點
                        JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
                        Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式異常");
                        ObjectNode objectNode = (ObjectNode) jsonNode;
                        // JSON節點最外層寫入新的屬性
                        objectNode.put("userId", accessToken);
                        DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
                        String json = objectNode.toString();
                        log.info("最終的JSON數據爲:{}", json);
                        dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
                        return Flux.just(dataBuffer);
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                } else {
                    return super.getBody();
                }
            }
        };
        // 使用修改後的ServerHttpRequestDecorator從新生成一個新的ServerWebExchange
        return chain.filter(exchange.mutate().request(decorator).build());
    }

    private class InputStreamHolder {

        InputStream inputStream;
    }
}
複製代碼

測試一下:

// 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": "請求流水號",
    "payload": {
        "name": "doge"
    }
}

// 日誌輸出
最終的JSON數據爲:{"serialNumber":"請求流水號","payload":{"name":"doge"},"userId":"10086"}
複製代碼

最重要的是用到了ServerHttpRequest裝飾器ServerHttpRequestDecorator,主要覆蓋對應獲取請求體數據緩衝區的方法便可,至於怎麼處理其餘邏輯須要自行考慮,這裏只是作一個簡單的示範。通常的代碼邏輯以下:

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

     @Override
     public Flux<DataBuffer> getBody() {
         // 拿到承載原始請求體的Flux
         Flux<DataBuffer> body = super.getBody();
         // 這裏經過自定義方式生成新的承載請求體的Flux
         Flux<DataBuffer> newBody = ...
         return newBody;
     }            
}
return chain.filter(exchange.mutate().request(requestDecorator).build());    
複製代碼

修改響應體

修改響應體的需求也是比較常見的,具體的作法和修改請求體差很少。例如咱們想要實現下面的功能:第三方服務請求通過網關,原始報文是密文,咱們須要在網關實現密文解密,而後把解密後的明文路由到下游服務,下游服務處理成功響應明文,須要在網關把明文加密成密文再返回到第三方服務。如今簡化整個流程,用AES加密算法,統一密碼爲字符串"throwable",假設請求報文和響應報文明文以下:

// 請求密文
{
    "serialNumber": "請求流水號",
    "payload" : "加密後的請求消息載荷"
}

// 請求明文(僅僅做爲提示)
{
    "serialNumber": "請求流水號",
    "payload" : "{\"name:\":\"doge\"}"
}

// 響應密文
{
    "code": 200,
    "message":"ok",
    "payload" : "加密後的響應消息載荷"
}

// 響應明文(僅僅做爲提示)
{
    "code": 200,
    "message":"ok",
    "payload" : "{\"name:\":\"doge\",\"age\":26}"
}
複製代碼

爲了方便一些加解密或者編碼解碼的實現,須要引入Apachecommons-codec類庫:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.12</version>
</dependency>
複製代碼

這裏定義一個全局過濾器專門處理加解密,實際上最好結合真實的場景決定是否適合全局過濾器,這裏只是一個示例:

// 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("修改請求體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("修改響應體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;
    }
}  
複製代碼

先準備一份密文:

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

// 輸出
{"serialNumber":"請求流水號","payload":"144e3dc734743f5709f1adf857bca473da683246fd612f86ac70edeb5f2d2729"}
複製代碼

模擬請求:

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": "請求流水號",
    "payload": "FE49xzR0P1cJ8a34V7ykc9poMkb9YS+GrHDt618tJyk="
}

// 響應結果
{
    "serialNumber": "請求流水號",
    "payload": "oo/K1igg2t/S8EExkBVGWOfI1gAh5pBpZ0wyjNPW6e8="   # <--- 解密後:{"name":"doge","age":26}
}
複製代碼

遇到的問題:

  • 必須實現Ordered接口,返回一個小於-1的order值,這是由於NettyWriteResponseFilter的order值爲-1,咱們須要覆蓋返回響應體的邏輯,自定義的GlobalFilter必須比NettyWriteResponseFilter優先執行。
  • 網關每次重啓以後,第一個請求老是沒法從原始的ServerHttpRequest讀取到有效的Body,準確來講出現的現象是NettyRoutingFilter調用ServerHttpRequest#getBody()的時候獲取到一個空的對象,致使空指針;奇怪的是從第二個請求開始就能正常調用。筆者把Spring Cloud Gateway的版本下降到Finchley.SR3Spring Boot的版本下降到2.0.8.RELEASE,問題再也不出現,初步肯定是Spring Cloud Gateway版本升級致使的兼容性問題或者是BUG

最重要的是用到了ServerHttpResponse裝飾器ServerHttpResponseDecorator,主要覆蓋寫入響應體數據緩衝區的部分,至於怎麼處理其餘邏輯須要自行考慮,這裏只是作一個簡單的示範。通常的代碼邏輯以下:

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就是原始的響應數據的緩衝區
                        // 下面處理完畢以後返回新的響應數據的緩衝區便可
                        return bufferFactory.wrap(...);
                    }));
                }
                return super.writeWith(body);
            }
        };
return chain.filter(exchange.mutate().response(responseDecorator).build());    
複製代碼

請求體或者響應體報文過大的問題

有熱心的同窗告訴筆者,若是請求報文過大或者響應報文過大的時候,前面兩節的修改請求和響應報文的方法會出現問題,這裏嘗試重現一下遇到的具體問題。先把請求報文嘗試加長:

Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "請求流水號");
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));

// 請求的JSON報文以下:
{
    "serialNumber": "請求流水號",
    "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="
}
複製代碼

用上面的請求報文發起請求,確實存在問題:

s-c-g-e-r-r-1.png

主要問題是:

  • 請求體包數據裝成的Flux<DataBuffer>實例被訂閱以後,讀取到的字節數組的長度被截斷了,提供的原始請求報文裏面字符串長度要大於1000,轉換成byte數組絕對要大於1000,可是上面的示例中只讀取到長度爲673的byte數組。
  • 讀取到的字節數組被截斷後,則使用Jackson進行反序列化的時候提示沒有讀取到字符串的EOF標識,致使反序列化失敗。

既然遇到了問題,就想辦法解決。首先第一步定位一下是什麼緣由,直覺告訴筆者:要開啓一下DEBUG日誌進行觀察,若是尚未頭緒可能要跟蹤一下源碼

開啓DEBUG日誌級別以後作一次請求,發現了一些可疑的日誌信息:

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 複製代碼

注意一下關鍵字READ: 1024B,這裏應該是底層的Reactor-Netty讀取的最大數據報的長度限制,打印出來的數據報恰好也是1024B的大小,這個應該就是致使請求體被截斷的根本緣由;這個問題不僅僅會出如今請求體的獲取,也會出如今響應體的寫入。既然這個是共性的問題,那麼項目Github上確定有對應的Issue,找到一個互動比較長的gateway request size limit 1024B because netty default limit 1024,how to solve it? #581,從回答來看,官方建議使用ModifyRequestBodyGatewayFilterFactoryModifyResponseBodyGatewayFilterFactory完成對應的功能。這裏能夠嘗試借鑑一下ModifyRequestBodyGatewayFilterFactory的實現方式修改以前的代碼,由於代碼的邏輯比較長和複雜,解密請求體的過濾器拆分到新的類RequestEncryptionGlobalFilter,加密響應體的過濾器拆分到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("修改請求體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;
    }
}
複製代碼

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("修改響應體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;
        }
    }
}
複製代碼

模擬請求:

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": "請求流水號",
    "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="
}

// 響應
{"serialNumber":"請求流水號","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"}
複製代碼

完全解決了以前的請求或者響應報文截斷的問題,筆者發現了不少博文都在(照搬)更改讀取DataBuffer實例時候的代碼邏輯,其實那段邏輯是不相關的,能夠嘗試用BufferedReader基於行讀取而後用StringBuilder承載,或者像本文那樣直接讀取爲byte數組等等,由於根本的緣由是底層的Reactor-Netty的數據塊讀取大小限制致使獲取到的DataBuffer實例裏面的數據是不完整的,解決方案就是參照Spring Cloud Gateway自己提供的基礎類庫進行改造(暫時沒發現有入口能夠調整Reactor-Netty的配置),難度也不大。

小結

恰好遇到一個需求須要作網關的加解密包括請求體和響應體的修改,這裏順便把Spring Cloud Gateway一些涉及到這方面的一些內容梳理了一遍,順便把坑踩了而且填完。下一步嘗試按照目前官方提供的可用組件修改一下實現自定義的邏輯,包括Hystrix、基於EurekaRibbon的負載均衡、限流等等。

原文連接

(本文完 c-6-d e-a-20190518 r-a-20190519)

相關文章
相關標籤/搜索