bhavishya singh
bhavishya singh

Reputation: 121

Get request body string from ServerHttpRequest / Flux<DataBuffer>

I am using spring boot version - 2.0.6.RELEASE and spring cloud version - Finchley.SR2

and i have created my custom gateway filter to modify the request body.

but while converting the request body to string using Flux i am getting a empty string. i need a method to get the string corresponding to my request body.

@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();
    String s = resolveBodyFromRequest(request);
     /* s comes out to be "" */
    return chain.filter(newExchange);


}



private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){
    //Get the request body
    Flux<DataBuffer> body = serverHttpRequest.getBody();
    StringBuilder sb = new StringBuilder();

    body.subscribe(buffer -> {
        byte[] bytes = new byte[buffer.readableByteCount()];
        buffer.read(bytes);
        DataBufferUtils.release(buffer);
        String bodyString = new String(bytes, StandardCharsets.UTF_8);
        sb.append(bodyString);
    });
    return sb.toString();

}

Upvotes: 11

Views: 18443

Answers (5)

Mike D3ViD Tyson
Mike D3ViD Tyson

Reputation: 1771

From Spring Cloud Gateway official documentation:

in the routes configuration add Spring CacheRequestBody before your custom one:

spring:
  cloud:
    gateway:
      routes:
      - id: cache_request_body_route
        uri: lb://downstream
        predicates:
        - Path=/downstream/**
        filters:
        - name: CacheRequestBody
          args:
            bodyClass: java.util.LinkedHashMap

or if you are using .properties instead .yaml:

spring.cloud.gateway.routes[77].filters[2]=CacheRequestBody=java.util.LinkedHashMap
spring.cloud.gateway.routes[77].filters[3]=YourCustomFilter

now in every filter in the chain after Spring CacheRequestBody you can get a cached copy of the request body simply calling exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);:

@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    LinkedHashMap<String,Object> body = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);
    //do-stuff-with-body
    return chain.filter(exchange);
}

Upvotes: 0

Guillaume Berche
Guillaume Berche

Reputation: 3209

Refinement of @tony.hokan answer https://stackoverflow.com/a/64080867/1484823 using spring cloud gateway rewrite body request to save the request body (and possibly response body) as an attribute of the org.springframework.web.server.ServerWebExchange

    @Bean
    public RouteLocator myRouteSavingRequestBody(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("my-route-id",
                p -> p
                    .path("/v2/**") //your own path filter
                    .filters(f -> f
                        .modifyResponseBody(String.class, String.class,
                            (webExchange, originalBody) -> {
                                if (originalBody != null) {
                                    webExchange.getAttributes().put("cachedResponseBodyObject", originalBody);
                                    return Mono.just(originalBody);
                                } else {
                                    return Mono.empty();
                                }
                            })
                        .modifyRequestBody(String.class, String.class,
                            (webExchange, originalBody) -> {
                                if (originalBody != null) {
                                    webExchange.getAttributes().put("cachedRequestBodyObject", originalBody);
                                    return Mono.just(originalBody);
                                } else {
                                    return Mono.empty();
                                }
                            })

                    )
                    .uri("https://myuri.org")
            )
            .build();
    }

in your own filter, get requestBody like below:

    @Override
    public GatewayFilter apply(Object config)
    {
        return (exchange, chain) -> {

            String requestBody = exchange.getAttribute("cachedRequestBodyObject");

        };
    }

Upvotes: 3

tony.hokan
tony.hokan

Reputation: 571

This is another approach work in spring cloud gateway 2.2.5, we will use ReadBodyPredicateFactory, as this will cache requestBody to ServerWebExchange with attribute key cachedRequestBodyObject

create always true Predicate

@Component
public class TestRequestBody implements Predicate
{
    @Override
    public boolean test(Object o)
    {
        return true;
    }
}

in application.yml, add Predicate

spring:
  cloud:
    gateway:
      routes:
       ....
          predicates:
            .....
            - name: ReadBodyPredicateFactory
              args:
                inClass: "#{T(String)}" 
                predicate: "#{@testRequestBody}"

in your own filter, get requestBody like below:

    @Override
    public GatewayFilter apply(Object config)
    {
        return (exchange, chain) -> {

            String requestBody = exchange.getAttribute("cachedRequestBodyObject");

        };
    }

Upvotes: 5

Fabian
Fabian

Reputation: 3450

You could use the ModifyRequestBodyGatewayFilterFactory which I believe is included in Spring Cloud Gateway 2.0.2 which is part of Finchley.

For Ex:

@Override
public GatewayFilter apply(Config config) {
   return (exchange, chain) -> {
        ModifyRequestBodyGatewayFilterFactory.Config modifyRequestConfig = new ModifyRequestBodyGatewayFilterFactory.Config()
                .setContentType(ContentType.APPLICATION_JSON.getMimeType())
                .setRewriteFunction(String.class, String.class, (exchange1, originalRequestBody) -> {
                    String modifiedRequestBody = yourMethodToModifyRequestBody(originalRequestBody);
                    return Mono.just(modifiedRequestBody);
                });

        return new ModifyRequestBodyGatewayFilterFactory().apply(modifyRequestConfig).filter(exchange, chain);
    };
}

Upvotes: 9

jbaddam17
jbaddam17

Reputation: 327

once you read(log by reading) the request body, request drops there itself. spring cloud gateway needs to record the contents of the request body, but the request body can only be read once. If the request body is not encapsulated after reading it , the latter service will not be able to read the body data. follow this

Upvotes: 3

Related Questions