The premise

  • At the time of writing this articleSpring Cloud GatewayVersion is the latest version at the timeGreenwich.SR1.

When we use the Spring Cloud Gateway, we notice that the filters (including the GatewayFilter, GlobalFilter and the GatewayFilterChain) all rely on 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);
}    
Copy the code

The design here is similar to that of filters in servlets, where the current Filter can determine whether to execute the logic of the next Filter, determined by whether GatewayFilterChain# Filter () is called. ServerWebExchange is the context for the current request and response. The ServerWebExchange instance not only stores the Request and Response objects, but also provides some extension methods. You must know more about ServerWebExchange if you want to implement modifications to Request or Response parameters.

Understand ServerWebExchange

Look first at the ServerWebExchange comment:

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.

Translation:

ServerWebExchange is a contract for HTTP request-response interactions. Provides access to HTTP requests and responses and exposes additional server-side processing related properties and features, such as request properties.

Actually, the ServerWebExchange, named the Service Network switch, holds important request-response properties, request instances, response instances, and so on, kind of like the role of Context.

ServerWebExchange interface

All methods of the ServerWebExchange interface:

public interface ServerWebExchange {

    / / log prefix attribute KEY, value of org. Springframework.. Web server ServerWebExchange. LOG_ID
    / / can be understood as the attributes. The set (" org. Springframework. Web. Server. ServerWebExchange. LOG_ID ", "log prefix concrete values");
    // The default value is ""
    String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
    String getLogPrefix(a);

    // Get the ServerHttpRequest object
    ServerHttpRequest getRequest(a);

    // Get the ServerHttpResponse object
    ServerHttpResponse getResponse(a);
    
    // Return the attributes of the current exchange request. The returned result is a mutable Map
    Map<String, Object> getAttributes(a);
    
    // Get the request attribute based on the KEY
    @Nullable
    default <T> T getAttribute(String name) {
        return (T) getAttributes().get(name);
    }
    
    // Obtain the request attribute based on the KEY, and make a non-null judgment
    @SuppressWarnings("unchecked")
    default <T> T getRequiredAttribute(String name) {
        T value = getAttribute(name);
        Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
        return value;
    }

     // To get the request attribute based on the KEY, provide the default value
    @SuppressWarnings("unchecked")
    default <T> T getAttributeOrDefault(String name, T defaultValue) {
        return (T) getAttributes().getOrDefault(name, defaultValue);
    } 

    // Return the current requested network session
    Mono<WebSession> getSession(a);

    // Return the currently requested authenticated user, if one exists
    <T extends Principal> Mono<T> getPrincipal(a);  
    
    // Return the requested form data or an empty Map. Only if the content-type is Application/X-www-form-urlencoded will this method return a non-empty Map, which is usually used for form data submission
    Mono<MultiValueMap<String, String>> getFormData();   
    
    // Return the part data of the multipart request or an empty Map. Only if the content-type is multipart/form-data will this method return a non-empty Map -- this is usually used for file upload
    Mono<MultiValueMap<String, Part>> getMultipartData();
    
    // Return the Spring context
    @Nullable
    ApplicationContext getApplicationContext(a);   

    // These methods are related to the lastModified property
    boolean isNotModified(a);
    boolean checkNotModified(Instant lastModified);
    boolean checkNotModified(String etag);
    boolean checkNotModified(@Nullable String etag, Instant lastModified);
    
    / / URL
    String transformUrl(String url);    
   
    // URL transform mapping
    void addUrlTransformer(Function<String, String> transformer); 

    ServerWebExchange returns an instance of Builder. Builder is an inner class of ServerWebExchange
    default Builder mutate(a) {
	     return new DefaultServerWebExchangeBuilder(this);
    }

    interface Builder {      
         
        / / ServerHttpRequest are covered
        Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer);
        Builder request(ServerHttpRequest request);
        
        / / ServerHttpResponse are covered
        Builder response(ServerHttpResponse response);
        
        // Override the authentication user for the current request
        Builder principal(Mono<Principal> principalMono);
    
        // Build a new ServerWebExchange instance
        ServerWebExchange build(a); }}Copy the code

Notice the ServerWebExchange#mutate() method. The ServerWebExchange instance can be interpreted as an immutable instance. If we want to change it, we need to generate a new instance with the mutate() method, for example:

public class CustomGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // You can modify the ServerHttpRequest instance here
        ServerHttpRequest newRequest = ...
        ServerHttpResponse response = exchange.getResponse();
        // You can modify the ServerHttpResponse instance here
        ServerHttpResponse newResponse = ...
        // Build a new ServerWebExchange instance
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).response(newResponse).build();
        returnchain.filter(newExchange); }}Copy the code

ServerHttpRequest interface

The ServerHttpRequest instance is used to host the properties and body associated with the request. The bottom layer of the Spring Cloud Gateway uses Netty to handle network requests. Can learn from ReactorHttpHandlerAdapter ServerWebExchange instances of specific implementation is ReactorServerHttpRequest holdings ServerHttpRequest instance. The relationship between these instances is listed because it makes it easier to untangle some underlying issues, such as:

  • ReactorServerHttpRequestThe parent classAbstractServerHttpRequestEncapsulate the HTTP header of a request as a read-only instance when initializing the headers internal property in
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 method, in which readOnlyHttpHeaders blocked all change requests the head, the method of direct throw UnsupportedOperationException
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
	Assert.notNull(headers, "HttpHeaders must not be null");
	if (headers instanceof ReadOnlyHttpHeaders) {
		return headers;
	}
	else {
		return newReadOnlyHttpHeaders(headers); }}Copy the code

So you can’t get the HttpHeaders instance directly from the ServerHttpRequest instance and modify it.

The ServerHttpRequest interface is as follows:

public interface HttpMessage {
    
    // Get the request header. The current implementation returns ReadOnlyHttpHeaders instance, read-only
    HttpHeaders getHeaders(a);
}    

public interface ReactiveHttpInputMessage extends HttpMessage {
    
    // Return the Flux encapsulation of the request body
    Flux<DataBuffer> getBody(a);
}

public interface HttpRequest extends HttpMessage {

    // Return the HTTP request method, which resolves to the HttpMethod instance
    @Nullable
    default HttpMethod getMethod(a) {
        return HttpMethod.resolve(getMethodValue());
    }
    
    // Return the HTTP request method, a string
    String getMethodValue(a);    
    
    // The requested URI
    URI getURI(a);
}    

public interface ServerHttpRequest extends HttpRequest.ReactiveHttpInputMessage {
    
    // Unique identification of the connection or used for log processing
    String getId(a);   
    
    // obtain the RequestPath and wrap it as a RequestPath object
    RequestPath getPath(a);
    
    // Return the query parameter, which is a read-only MultiValueMap instance
    MultiValueMap<String, String> getQueryParams(a);

    // Returns a set of cookies that are read-only instances of MultiValueMap
    MultiValueMap<String, HttpCookie> getCookies(a);  
    
    // Remote server address information
    @Nullable
    default InetSocketAddress getRemoteAddress(a) {
       return null;
    }

    // SSL session implementation information
    @Nullable
    default SslInfo getSslInfo(a) {
       return null;
    }  
    
    // Modify the requested method to return an instance Builder, which is an inner class
    default ServerHttpRequest.Builder mutate(a) {
        return new DefaultServerHttpRequestBuilder(this);
    } 

    interface Builder {

        Override the request method
        Builder method(HttpMethod httpMethod);
		 
        // Overwrite the request URI, request path, or context, which are dependent on each other. See API comments for details
        Builder uri(URI uri);
        Builder path(String path);
        Builder contextPath(String contextPath);

        // Override the request header
        Builder header(String key, String value);
        Builder headers(Consumer<HttpHeaders> headersConsumer);
        
        / / SslInfo are covered
        Builder sslInfo(SslInfo sslInfo);
        
        // Build a new ServerHttpRequest instance
        ServerHttpRequest build(a); }}Copy the code

If you want to modify the ServerHttpRequest instance, you need to do this:

ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest newRequest = request.mutate().headers("key"."value").path("/myPath").build();
Copy the code

The most important thing to note here is that the ServerHttpRequest or HttpMessage interface provides the HttpHeaders getHeaders() method for getting the request header; The result is a read-only instance of type ReadOnlyHttpHeaders. Once again, I wrote this post using the Version of Spring Cloud Gateway Greenwich.SR1.

ServerHttpResponse interface

The ServerHttpResponse instance is used to host the properties and body associated with the response. The bottom layer of the Spring Cloud Gateway uses Netty to process network requests. Can learn from ReactorHttpHandlerAdapter ServerWebExchange instances of specific implementation is ReactorServerHttpResponse holdings ServerHttpResponse instance. The relationship between these instances is listed because it makes it easier to untangle some underlying issues, such as:

/ / ReactorServerHttpResponse parent class
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;
}
Copy the code

Known ReactorServerHttpResponse constructor initializes the instance, response headers are placed is a HttpHeaders instance, namely response headers can be directly modified.

The ServerHttpResponse interfaces are:

public interface HttpMessage {
    
    // Get the response Header. The current implementation returns an HttpHeaders instance, which can be modified directly
    HttpHeaders getHeaders(a);
}  

public interface ReactiveHttpOutputMessage extends HttpMessage {
    
    // retrieve the DataBufferFactory instance to wrap or generate the DataBuffer instance for the DataBuffer (creating the response body)
    DataBufferFactory bufferFactory(a);

    // Register an action that will be called back before the HttpOutputMessage is submitted
    void beforeCommit(Supplier<? extends Mono<Void>> action);

    // Check whether the HttpOutputMessage has been committed
    boolean isCommitted(a);
    
    // Writes the message body to the HTTP protocol layer
    Mono<Void> writeWith(Publisher<? extends DataBuffer> body);

    // Write the message body to the HTTP protocol layer and flush the buffer
    Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body);
    
    // Indicates that the message processing has finished. This method is automatically called when the message processing is finished. Multiple calls do not cause side effects
    Mono<Void> setComplete(a);
}

public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
    
    // Set the response status code
    boolean setStatusCode(@Nullable HttpStatus status);
    
    // Get the response status code
    @Nullable
    HttpStatus getStatusCode(a);
    
    // Get the response Cookie encapsulated as an instance of MultiValueMap, which can be modified
    MultiValueMap<String, ResponseCookie> getCookies(a);  
    
    // Add a response Cookie
    void addCookie(ResponseCookie cookie);  
}    
Copy the code

You can see here that all the attributes are mutable except for the response body, which is harder to modify.

ServerWebExchangeUtils and context properties

ServerWebExchangeUtils stored inside a lot of static public string KEY value (the practical value of these KEY string is org. Springframework. Cloud. Gateway. Support. ServerWebExchangeUtils. + any of the following static public keys), these string KEY values are typically keys used in the ServerWebExchange Attribute (see ServerWebExchange#getAttributes() method above), These attribute values have special meanings and can be used directly when using the filter if the time is appropriate. The following analysis is one by one.

  • PRESERVE_HOST_HEADER_ATTRIBUTE: whether to save the Host attribute. The value is of Boolean type and the writing position isPreserveHostHeaderGatewayFilterFactory, the position used isNettyRoutingFilterIf this parameter is set to true, the Host attribute in the HTTP request Header will be written to the underlying reactor-netty request Header.
  • CLIENT_RESPONSE_ATTR: Saves the response objects of the underlying reactor-netty Reactor. The type isreactor.netty.http.client.HttpClientResponse.
  • CLIENT_RESPONSE_CONN_ATTR: Saves the connection object of the underlying reactor-netty Reactor. The type isreactor.netty.Connection.
  • URI_TEMPLATE_VARIABLES_ATTRIBUTE:PathRoutePredicateFactoryAfter the Path parameters are resolved, the placeholder key-path mapping is savedServerWebExchangeThe KEY isURI_TEMPLATE_VARIABLES_ATTRIBUTE.
  • CLIENT_RESPONSE_HEADER_NAMES: Holds the name set of the response headers for the underlying reactor-netty Reactor.
  • GATEWAY_ROUTE_ATTR: Used for storageRoutePredicateHandlerMappingThe specific route (org.springframework.cloud.gateway.route.Route) instance that tells you which downstream service the current request will be routed to.
  • GATEWAY_REQUEST_URL_ATTR:java.net.URIThis instance represents the real URI that needs to be requested to the downstream service after direct requests or load balancing processing.
  • GATEWAY_ORIGINAL_REQUEST_URL_ATTR:java.net.URIIf the request URI needs to be overridden, save the original request URI.
  • GATEWAY_HANDLER_MAPPER_ATTR: Saves the current oneHandlerMappingSpecific instances of the type referred to as “RoutePredicateHandlerMapping”) (typically strings.
  • GATEWAY_SCHEME_PREFIX_ATTRIf the target route URI has the schemeSpecificPart attribute, the scheme that saves the URI is in this attribute, and the route URI will be reconstructed. SeeRouteToRequestUrlFilter.
  • GATEWAY_PREDICATE_ROUTE_ATTR: Used for storageRoutePredicateHandlerMappingThe specific route (org.springframework.cloud.gateway.route.Route) instance ID.
  • WEIGHT_ATTR: experimental feature (this version is not recommended in the official version) to store attributes related to grouping weights, seeWeightCalculatorWebFilter.
  • ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR: Holds the value of ContentType in the response Header.
  • HYSTRIX_EXECUTION_EXCEPTION_ATTR:Throwable, which is the exception instance when Hystrix executes an exception. SeeHystrixGatewayFilterFactory.
  • GATEWAY_ALREADY_ROUTED_ATTR: A Boolean value used to determine whether a route has been routed. SeeNettyRoutingFilter.
  • GATEWAY_ALREADY_PREFIXED_ATTR: A Boolean value that determines whether the request path has a front part added, seePrefixPathGatewayFilterFactory.

ServerWebExchangeUtils provides context properties for the secure transmission and use of some important instances or identity properties when the Spring Cloud Gateway ServerWebExchange component processes requests and responses. Using these properties may have certain risks. Since no one can determine whether the original attribute KEY or VALUE will change after the version upgrade, it can be safely used if the risk has been assessed or avoided. For example, when making request and response logs (similar to Nginx’s Access Log), we can rely on GATEWAY_ROUTE_ATTR because we want to print the destination information of the route. Here’s a simple example:

@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();
        // Obtain the target URI of the route
        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("Request path :{}, client remote IP address :{}, request method :{}, target URI:{}, response code :{}", path, remoteAddress, method, targetUri, statusCode); })); }}Copy the code

Modifying the request body

Modifying the request body is a common requirement. For example, when we use the Spring Cloud Gateway to implement the Gateway, we need to implement a function: after the JWT stored in the request header is parsed, the user ID is extracted from it, and then written to the request body. Let’s simplify this scenario by assuming that we store the userId in plain text in the request header accessToken and that the request body is a JSON structure:

{"serialNumber": "payload" : {//... This is the payload, the actual data}}Copy the code

We need to extract the accessToken, that is, the userId, and insert it into the request body JSON as follows:

{" userId ", "user ID", "from" : "request serial number", "content" : {/ /... This is the payload, the actual data}}Copy the code

Here, in order to simplify the design, the GlobalFilter GlobalFilter is used to implement, and the actual situation needs to be considered:

@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");
        }
        // Create a new ServerHttpRequest decorator that overrides the methods to be decorated
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {

            @Override
            public Flux<DataBuffer> getBody(a) {
                Flux<DataBuffer> body = super.getBody();
                InputStreamHolder holder = new InputStreamHolder();
                body.subscribe(buffer -> holder.inputStream = buffer.asInputStream());
                if (null! = holder.inputStream) {try {
                        // Parse the JSON node
                        JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
                        Assert.isTrue(jsonNode instanceof ObjectNode, "Abnormal JSON format");
                        ObjectNode objectNode = (ObjectNode) jsonNode;
                        // Write new attributes to the outermost layer of the JSON node
                        objectNode.put("userId", accessToken);
                        DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
                        String json = objectNode.toString();
                        log.info("The final JSON data is :{}", json);
                        dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
                        return Flux.just(dataBuffer);
                    } catch (Exception e) {
                        throw newIllegalStateException(e); }}else {
                    return super.getBody(); }}};/ / using a modified ServerHttpRequestDecorator to regenerate a new ServerWebExchange
        return chain.filter(exchange.mutate().request(decorator).build());
    }

    private class InputStreamHolder { InputStream inputStream; }}Copy the code

Test it out:

// 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": "Request serial number"."payload": {
        "name": "doge"}} // Log output the final JSON data :{"serialNumber":"Request serial number"."payload": {"name":"doge"},"userId":"10086"}
Copy the code

The most important thing is to use the ServerHttpRequest decorator ServerHttpRequestDecorator, mainly covering the corresponding method can obtain the request body data buffer, as to how to deal with other logic need to consider, here are just a simple example. The general code logic is as follows:

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

     @Override
     public Flux<DataBuffer> getBody(a) {
         // Get the Flux that hosted the original request body
         Flux<DataBuffer> body = super.getBody();
         // Generate a new Flux to host the request body in a custom way
         Flux<DataBuffer> newBody = ...
         returnnewBody; }}return chain.filter(exchange.mutate().request(requestDecorator).build());    
Copy the code

Modify response body

The need to modify the response body is also common, and the practice is similar to that of modifying the request body. For example we want to achieve the following functions: the third party service request through the gateway, the original message is the ciphertext, we need to implement cipher decryption in the gateway, and then put the decrypted plaintext is routed to the downstream service, the downstream service handle a successful response plaintext, need in the gateway to plaintext encrypted into ciphertext back again to the third party service. Now simplify the whole process by using the AES encryption algorithm and the unified password is the string “throwable”. Assume that the plaintext of the request and response packets is as follows:

// request ciphertext {"serialNumber": "request serialNumber", "payload" :" encrypted request payload"} // request plaintext (only as a prompt) {"serialNumber": "request serialNumber", "payload" : Name: "{\" \ ": \" doge \ "} "} / / response ciphertext {" code ": 200, the" message ":" ok ", "content" : "the encrypted message response load"} / / response clear (just as a hint) {" code ": 200, "message":"ok", "payload" : "{\"name:\":\"doge\",\"age\":26}" }Copy the code

To facilitate some implementations of encryption, decryption, or encoding and decoding, we need to introduce the Apache Commons-Codec class library:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.12</version>
</dependency>
Copy the code

A global filter is defined to handle encryption and decrypting. In fact, it is best to determine whether a global filter is suitable for a real scenario. Here is just an example:

// AES encryption/decryption tool class
public enum AesUtils {

    / / the singleton
    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 newIllegalArgumentException(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 newIllegalArgumentException(e); }}private SecretKey provideSecretKey(a) {
        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 newIllegalArgumentException(e); }}}// EncryptionGlobalFilter
@Slf4j
@Component
public class EncryptionGlobalFilter implements GlobalFilter.Ordered {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public int getOrder(a) {
        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(a) {
                int contentLength = holder.length;
                if (contentLength > 0) {
                    headers.setContentLength(contentLength);
                } else {
                    headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return headers;
            }

            @Override
            public Flux<DataBuffer> getBody(a) {
                returnFlux.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 newIllegalStateException(e); }}private void setPayloadTextNode(String text, JsonNode root) {
        try {
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload".new TextNode(text));
        } catch (Exception e) {
            throw newIllegalStateException(e); }}private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw newIllegalStateException(e); }}private class DataBufferHolder {

        DataBuffer dataBuffer;
        intlength; }}Copy the code

Prepare a cipher message:

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

/ / output
{"serialNumber":"Request serial number"."payload":"144e3dc734743f5709f1adf857bca473da683246fd612f86ac70edeb5f2d2729"}
Copy the code

Mock request:

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 - 478 - c - fe6f37200 b4d7-754, 634734 d9 - 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": "Request serial number"."payload": "FE49xzR0P1cJ8a34V7ykc9poMkb9YS+GrHDt618tJyk="} // Response result {"serialNumber": "Request serial number"."payload": "oo/K1igg2t/S8EExkBVGWOfI1gAh5pBpZ0wyjNPW6e8="   {"name":"doge","age":26}
}
Copy the code

Problems encountered:

  • Must be implementedOrderedInterface that returns an order value less than -1 becauseNettyWriteResponseFilterThe order value is -1, we need to override the logic that returns the response body, customGlobalFilterMust be better thanNettyWriteResponseFilterPriority execution.
  • After each restart of the gateway, the first request always fails from the originalServerHttpRequestRead a valid Body, exactly what happensNettyRoutingFiltercallServerHttpRequest#getBody()When an empty object is obtained, resulting in a null pointer; The odd thing is that it works from the second request.The author put theSpring Cloud GatewayThe version ofFinchley.SR3.Spring BootThe version ofMid-atlantic moved. RELEASE, the problem no longer appears, preliminary determination isSpring Cloud GatewayCompatibility issues or bugs caused by version upgrades.

The most important thing is to use the ServerHttpResponse decorator ServerHttpResponseDecorator, mainly covering writes data buffer response body part, as to how to deal with other logic need to consider, here are just a simple example. The general code logic is as follows:

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 -> {
                        // The buffer is the buffer of the original response data
                        // Return the new buffer of response data after processing
                        returnbufferFactory.wrap(...) ; })); }return super.writeWith(body); }};return chain.filter(exchange.mutate().response(responseDecorator).build());    
Copy the code

The request body or response body is too large

Some enthusiastic students told me that if the request packet or response packet is too large, the methods of modifying the request and response packet in the previous two sections will have problems. Here, I try to reproduce the specific problems encountered. First try to lengthen the request packet:

Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber"."Request serial number");
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));

// The requested JSON message is as follows:
{
    "serialNumber": "Request serial number"."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="
}
Copy the code

Using the above request packet to initiate a request does have a problem:

The main issues are:

  • Request body dataFlux<DataBuffer>After the instance is subscribed, the byte array read is truncated. If the original request message is a string larger than 1000, the conversion to a byte array must be larger than 1000, but in the example above, only the byte array of 673 is read.
  • When the read byte array is truncated, Jackson is used for deserialization, prompting that the EOF identifier of the string is not read, resulting in deserialization failure.

If you have a problem, try to solve it. First of all, the first step is to find out what is the cause. My intuition tells me to turn on the DEBUG log for observation, and maybe trace the source code if there is no clue.

After making a request with the DEBUG log level enabled, some suspicious log information is found:

11:16:15 2019-05-19. 660 - nio - 2] [reactor - HTTP DEBUG ty. Reactor.ipc.net. The HTTP server. The 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:9,090! R:/ 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 73 6 f e | 2 f 6 a POST/order/json | | 00000010 | 20 48 54 54 50 31 2 e 2 f 0 d 0 a 31 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 6 e 52, 75 e, 74, 69 d, 65 2 e 2 f 37 and 33 2 e | 30 anRuntime / 7.13.0 | | 00000070 | 0 d 0 a 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: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 11:16:15 2019-05-19. 671 - nio - 3] [reactor - HTTP DEBUG ty. Reactor.ipc.net. The HTTP server. The HttpServer - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] READ COMPLETECopy the code

Note that the keyword READ: 1024B is the maximum size of the reactor-netty datagram that can be READ. The size of the printed datagram is also 1024B. This is the root cause of the truncated request body. This problem occurs not only in the fetching of the request body, but also in the writing of the response body. Since this is a common Issue, there must be an Issue for the project on Github, Gateway Request size limit 1024B because netty default limit 1024 how to solve it? # 581, from the answer, the official recommended ModifyRequestBodyGatewayFilterFactory and ModifyResponseBodyGatewayFilterFactory to complete the corresponding functions. Here you can try to examine ModifyRequestBodyGatewayFilterFactory modify the code before the implementation of the way, because the logic of the code is long and complex, decryption request body RequestEncryptionGlobalFilter filter split into the new class, Encryption response body filter split into ResponseDecryptionGlobalFilter:

RequestEncryptionGlobalFilter code is as follows:

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

    @Autowired
    private ObjectMapper objectMapper;

    private finalList<HttpMessageReader<? >> messageReaders = HandlerStrategies.withDefaults().messageReaders();@Override
    public int getOrder(a) {
        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(a) {
                    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(a) {
                    returnFlux.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 newIllegalStateException(e); }}private void setPayloadTextNode(String text, JsonNode root) {
        try {
            ObjectNode objectNode = (ObjectNode) root;
            objectNode.set("payload".new TextNode(text));
        } catch (Exception e) {
            throw newIllegalStateException(e); }}private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw newIllegalStateException(e); }}private class DataBufferHolder {

        DataBuffer dataBuffer;
        intlength; }}Copy the code

ResponseDecryptionGlobalFilter code is as follows:

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

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public int getOrder(a) {
        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())); }returngetDelegate().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 newIllegalStateException(e); }}private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw newIllegalStateException(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(a) {
            return flux;
        }

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

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

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

        @Override
        public MultiValueMap<String, ResponseCookie> getCookies(a) {
            return null; }}}Copy the code

Mock request:

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": "Request serial number"."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="} // Response {"serialNumber":"Request serial number"."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"}
Copy the code

This completely resolved the issue of request or response truncation. I found that many blog posts were changing the code logic when reading the DataBuffer instance. That logic is actually irrelevant. Try using a BufferedReader to read it on a line basis and then loading it on a StringBuilder. Or, as in this article, directly read as a byte array, etc., because the underlying reason is that the size limit of the underlying reactor-Netty data block reading causes the data in the DataBuffer instance to be incomplete. The solution is to transform the base class library provided by Spring Cloud Gateway (there is no entrance to adjust the configuration of reactor-netty), which is not difficult.

summary

Just encountered a requirement to do Gateway encryption and decrypting including request body and response body modification, here by the way, some Spring Cloud Gateway involved in this aspect of some content comb over, by the way, the pit stepped and filled. The next step is to try to customize the logic based on the available components, including Hystrix, Eureka and Ribbon based load balancing, traffic limiting, and so on.

The original link

  • Making Page: www.throwable.club/2019/05/18/…
  • Coding Page: throwable. Coding. Me / 2019/05/18 /…

(C-6-D E-A-20190518 R-A-20190519)

The technical public account (Throwable Digest), which will push the author’s original technical articles from time to time (never plagiarize or reprint) :