SoFunction
Updated on 2025-03-06

Spring Cloud Gateway How to modify HTTP response information

Gateway Modify HTTP response information

In the process of practicing Spring Cloud, Gateway is used as the routing component, and permission verification, interception, and filtering is implemented based on Gateway. For the response results of downstream microservices, we always need to modify the unified data format, or modify the data information that users see without permission. At this time, we need a Filter that can modify the response body.

Spring Cloud Gateway version is 2.1.0

In the current version, ModifyRequestBodyGatewayFilterFactory is the official reference class for modifying the response body. This filter is BETA and may be subject to change in a future release. The comments of the class indicate that this class will be improved in future versions. In actual use, you can refer to the implementation function, but the performance impact is great, but there is no other choice but you still have to choose this.

Official Documentation:

accomplish

Final code

Post the final code first

public class ResponseDecryptionGlobalFilter implements GlobalFilter, Ordered {
    private static Logger log = ();
    @Override
    public int getOrder() {
    	// Control is executed after NettyWriteResponseFilter        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) {
        // If filtering is not required in the route, no filtering is performed        if (!()) {
            return (exchange);
        }
        ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(()) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                String originalResponseContentType = (ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                HttpHeaders httpHeaders = new HttpHeaders();
                (HttpHeaders.CONTENT_TYPE, originalResponseContentType);
                ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
                DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ());
                Mono<String> rawBody = ().map(s -> s);
                BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = (rawBody, );
                CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, ().getHeaders());
                return (outputMessage, new BodyInserterContext())
                        .then((() -> {
                            Flux<DataBuffer> messageBody = ();
                            Flux<DataBuffer> flux = (buffer -> {
                                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(());
                                (buffer);
								// Convert response information into a string                                String responseStr = ();
                                if ((responseStr)) {
                                    try {
                                        JSONObject result = (responseStr);
                                        (dataFilter(result));
                                        if (("data")) {
                                            responseStr = dataFilter(result);
                                        } else {
                                            ("Response result serialization exception:{}", responseStr);
                                        }
                                    } catch (JSONException e) {
                                        ("Response result serialization exception:{}", responseStr);
                                    }
                                }
                                return getDelegate().bufferFactory().wrap((StandardCharsets.UTF_8));
                            });
                            HttpHeaders headers = getDelegate().getHeaders();
                            // Modify the size of the response package. If you do not modify it, it will be thrown away by the browser because of the different package sizes.                            flux = (data -> (()));
                            return getDelegate().writeWith(flux);
                        }));
            }
        };
        return (().response(responseDecorator).build());
    }
    /**
      * Permission data filtering
      *
      * @param result
      * @return
      */
    private String dataFilter(JSONObject result) {
        Object data = ("data");
        return ();
    }
    private class ResponseAdapter implements ClientHttpResponse {
        private final Flux<DataBuffer> flux;
        private final HttpHeaders headers;
        @SuppressWarnings("unchecked")
        private ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders 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;
        }
    }
}

The pits that have been stepped on

  • The response body message is too large: At first, the buffer's response information is directly read. There is no problem with the small package, but if the package is large, the json cannot be converted exceptions, because the complete response content cannot be read. Please refer to ModifyRequestBodyGatewayFilter, wait for the buffer to be read before converting it into an array, and then execute the processing. The essential reason is that the data in the dataBuffer instance obtained is incomplete due to the limitation of the underlying Reactor-Netty data block read size.
  • After modifying the response information, the ContentLength of the response will change. Forgot to modify the Content-Length length in the response, resulting in the front-end request being unable to obtain the modified response result.
flux = (data -> (()));
  • The order value must be less than -1, because the overwrite returns the response body, and the customized GlobalFilter must be executed after processing than NettyWriteResponseFilter. The smaller the order, the earlier it is processed, and the later it is processed.

Understand ServerWebExchange

Let’s first look at the comments of ServerWebExchange:

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

Translate it roughly:

ServerWebExchange is a contract that ** HTTP request-response interaction. ** Provides access to HTTP requests and responses, and exposes additional server-side processing related attributes and features, such as request attributes.

ServerWebExchange is a bit like the role of Context. I understand it as a container that passes through http request information in Filter. It is called a container because it can store data we put in.

Notice:

ServerHttpRequest is a read-only class, so it needs to be modified through the following example method. For scenarios where more reads and fewer writes, this design pattern is worth learning from.

ServerHttpRequest newRequest = ().headers("key","value").path("/myPath").build();
ServerWebExchange newExchange = ().response(responseDecorator).build();

Gateway Modify the returned response body

Problem description:

Modify the returned response body in the gateway and add the following code to the global Filter:

import ;
import ;
import ;
import ;
import ;
import ;
@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
 //...
 
 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  //...
  ResponseDecorator decorator = new ResponseDecorator(());
  return (().response(decorator).build());
 }
 @Override
 public int getOrder() {
  return -1000;
 }
}

Set up a response decorator (custom) through .response(decorator). The following is the specific implementation of the decorator:

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
  * @author
  * @desc response decorator (reconstruct the response body)
  */
public class ResponseDecorator extends ServerHttpResponseDecorator{
 public ResponseDecorator(ServerHttpResponse delegate){
  super(delegate);
 }
 @Override
 @SuppressWarnings(value = "unchecked")
 public Mono&lt;Void&gt; writeWith(Publisher&lt;? extends DataBuffer&gt; body) {
  if(body instanceof Flux) {
   Flux&lt;DataBuffer&gt; fluxBody = (Flux&lt;DataBuffer&gt;) body;
   return (().map(dataBuffers -&gt; {
    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
    DataBuffer join = (dataBuffers);
    byte[] content = new byte[()];
    (content);
    (join);// Release the memory    
    String bodyStr = new String(content, ("UTF-8"));
                //Modify the response body    bodyStr = modifyBody(bodyStr);
    getDelegate().getHeaders().setContentLength(().length);
    return bufferFactory().wrap(());
   }));
  }
  return (body);
 }
    //Rewrite this function private String modifyBody(String jsonStr){
  JSONObject json = new JSONObject(jsonStr);
        //TODO...Modify the response body  return ();
 }
}

The above is personal experience. I hope you can give you a reference and I hope you can support me more.