Skip to content

Instantly share code, notes, and snippets.

@WeirdBob
Created November 21, 2017 12:55
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save WeirdBob/b25569d461f0f54444d2c0eab51f3c48 to your computer and use it in GitHub Desktop.
Save WeirdBob/b25569d461f0f54444d2c0eab51f3c48 to your computer and use it in GitHub Desktop.
Spring cloud gateway response body modification
spring:
cloud:
gateway:
routes:
- id: test
uri: https://httpbin.org:443
predicates:
- Path=/uuid
- Method=GET
server:
port: 8080
package examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableAutoConfiguration
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>examples</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M5</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.0.M3</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>spring</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
</project>
package examples;
import java.nio.charset.Charset;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
public class ToUppercaseGlobalFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return -2; // -1 is response write filter, must be called before that
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
// probably should reuse buffers
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).toUpperCase().getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body); // if body is not a flux. never got there.
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build()); // replace response with decorator
}
}
@arun2dot0
Copy link

This works only for simple use case when full payload is not required for for filter .

@LaxmiPeri
Copy link

How can we tell if this is a post filter or pre filter?. Is that determined by Order?

@wentjiang
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

@magic3lon
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

have you solved it? i meet same question

@leiyazhen
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

have you solved it? i meet same question
i have the same problem,have you solved it?

@weichaojie
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

have you solved it? i meet same question
i have the same problem,have you solved it?

me too

@trajano
Copy link

trajano commented Feb 9, 2019

Seems so complicated when all you want is to just pass a simple response when an Authorization header is missing

@TouSS
Copy link

TouSS commented May 14, 2019

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this
have you solved it? I have the same question.

@chenkunyun
Copy link

There are one problem with your ToUppercaseGlobalFilter:

  1. When you are done with the buffer, you should release it with the following code. Or under stress test, netty will detect memory leak
DataBufferUtils.release(buffer);

@chenkunyun
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

You should set the response length to a new value with the following code, or the response body may be truncated

response.getHeaders().setContentLength(YOUR_NEW_RESPONSE_LENGTH_IN_BYTES);

@gewure
Copy link

gewure commented Jul 15, 2019

Seems so complicated when all you want is to just pass a simple response when an Authorization header is missing

true

@arpitkh12
Copy link

Can you please elaborate how can you do the same in requestBody. I have a requirement to log it for audit purposes.

@chenkunyun
Copy link

Can you please elaborate how can you do the same in requestBody. I have a requirement to log it for audit purposes.

Sure, man. Refer to my demo code here

@arpitkh12
Copy link

arpitkh12 commented Aug 24, 2019

@chenkunyun My requirements are to log all incoming request with request body and also make another call to audit service passing the request body and api. The example you have provided is for response body. Can you please tell how would you do that for incoming request ?

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

		ServerHttpRequest originalRequest = exchange.getResponse();
                // This doesn't work
                String requestBody = originalRequest.getRequestBody().toString();
		ServerHttpRequestDecorator decoratedResponse = new ServerHttpRequestDecorator(originalRequest) {
                 // what needs to be implemented here ?
            }			
		};
                log.info("Incoming request is [{}] for path [{}]" requestBody, originalRequest.getPath() );
		return chain.filter(exchange.mutate().response(originalRequest).build()); // replace response with decorator
	}

I am using DiscoveryClient Route Definition Locator to build routes so cannot add ReadBodyPredicateFactory as it is JAVA DSL based config.

@nedijahij
Copy link

@arpitkh12 have you found solution yet?

@wujian0325
Copy link

when i use this to modify a large content,it usually read a part of my json string ,server has no response,do you have meet this question?how can i solve this

you can remove the Content-Length from headers to handle this problem;

@dalegaspi
Copy link

dalegaspi commented Jan 5, 2021

thanks for this gist. it steered me towards what i need to modify the response of a body via Spring Cloud Gateway. A lot can be learned from the source code of ModifyResponseBodyGatewayFilterFactory.java to maybe improve on this but for my purpose the gist works as it is.

edit: oof. that didn't last long 😝. the above approach will work to some degree but it is limited...like, for example, if you intend to modify a JSON response body prior to returning to the client, the above gist will not work (i know because i tried). also note that the gist doesn't take the decoders into account from upstream like here.

I found an article that tackled this and the solution seems to be a modification of ModifyResponseBodyGatewayFilterFactory.

edit 2: after tinkering around, i was able to write a quick and dirty custom filter that modifies response body based on what ModifyResponseBodyGatewayFilterFactorydoes today ...i wrote up a gist here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment