Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I get the request body in the global filter method? #1205

Closed
venkatnpedada opened this issue Jul 25, 2019 · 8 comments
Closed

How do I get the request body in the global filter method? #1205

venkatnpedada opened this issue Jul 25, 2019 · 8 comments

Comments

@venkatnpedada
Copy link

How do I get the request body in the global filter method?

Spring boot 2.1.6
Spring Cloud 2.1.2

Based on this comment (#747 (comment)), I tried to replicate the code, but with spring cloud 2.1.2 some of the classes are removed (CachedBodyOutputMessage)

@venkatnpedada
Copy link
Author

@ryanjbaxter
Copy link
Contributor

Fair point, but it doesnt mean you can't copy that class if you need it (I realize that is not ideal).

@spencergibb maybe we should make that class public?

@spencergibb
Copy link
Member

we could I guess.

@voidbean
Copy link

voidbean commented Aug 2, 2019

I used the old version of ReadBodyPredicateFactory to implement this function.
https://github.com/spring-cloud/spring-cloud-gateway/blob/dc4181bbb0/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyPredicateFactory.java
And it like this in my global filter:

        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    DataBufferUtils.retain(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
                    ServerWebExchange build = exchange.mutate().request(mutatedRequest).build();
                    return Mono.just(build)
                            .map(ex -> {
                                ServerRequest.create(ex, MESSAGE_READERS)
                                        .bodyToMono(String.class)
                                        .subscribe(
                                                bodyString -> {
                                                    ex.getAttributes().put("requestBody",bodyString);
                                                }
                                        );
                                return ex;
                            })
                            .doOnNext(objectValue -> {
                                exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
                                exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux);
                            });
                }).defaultIfEmpty(exchange).flatMap(webExchange -> Mono.just(exchange));

But if you post a request withi empty body,it will cause "Only one connection receive subscriber allowed".So I copied the DataBufferUtils#join and rewrite it to avoid the empty body.

        Mono<DataBuffer> dataBufferMono = Flux.from(exchange.getRequest().getBody()).collectList().filter((list) -> {
            if(list.isEmpty()){
                list.add(new NettyDataBufferFactory(ByteBufAllocator.DEFAULT).allocateBuffer(0));
            }
            return true;
        }).map((list) -> {
            return ((DataBuffer)list.get(0)).factory().join(list);
        }).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
dataBufferMono.flatMap(dataBuffer->(...))

@endypark
Copy link

Did not find any good method so far.
You could read the body in customed GlobalFilter,but could not modify the body.
you could @bean r.readBody( sssss ) with DSL , then you could read the body by exchange.getAttribute("cachedRequestBodyObject");

(remember, you could not modify and reset the body. Or I am fool, so did not find the way)

the only method to modify the body content is use Java DSL r.path("/aaa").filters( f -> f.modifyRequestBody( xxxxxx )).uri("lb://aaa") for each route, I did not find any method for all routes ( or default-filters) so far.

@ScarWar
Copy link

ScarWar commented Aug 27, 2019

I use this code to parse the body

private static final String CACHED_REQUEST_BODY_OBJ = "cached_request_body_obj";

private static List<HttpMessageReader<?>> messageReaders =
        HandlerStrategies.withDefaults().messageReaders();

/**
 * This method is inspired by {@link ReadBodyPredicateFactory}
 *
 * @param exchange the current exchange
 * @param inClass  the class to parse the body to
 * @param <T>      the type the body is parsed to
 * @return Mono of type T of the parsed body
 */
static public <T> Mono<T> readBody(ServerWebExchange exchange, Class<T> inClass) {
  T bodyObject = exchange.getAttribute(CACHED_REQUEST_BODY_OBJ);

  return bodyObject != null
          ? Mono.just(bodyObject)
          : getBody(exchange, inClass);
}

private static <T> Mono<T> getBody(ServerWebExchange exchange, Class<T> inClass) {
  return ServerWebExchangeUtils.cacheRequestBodyAndRequest(
          exchange,
          request -> ServerRequest.create(exchange.mutate()
                  .request(request)
                  .build(), messageReaders)
                  .bodyToMono(inClass)
  ).doOnNext(bodyObject -> exchange.getAttributes()
          .put(CACHED_REQUEST_BODY_OBJ, bodyObject)
  );

Sadly it does not work as a standalone. It only works when I use the readBody predicate before as a dummy.

.readBody(inClass, o -> true)
.filters(f -> f
        .filter((exchange, chain) -> {
          return readBody(exchange, inClass)
                  // Do logic with body
                  .then(chain.filter(exchange));
        }))
.uri("http://exmaple.com"))

without it I get

java.lang.IllegalStateException: Only one connection receive subscriber allowed.

The ability to use the request body for custom logic in not a nice-to-have feature but rather a necessity.

@spring-projects-issues
Copy link

Closing due to age of the question. If you would like us to look at this issue, please comment and we will look at re-opening the issue.

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

No branches or pull requests

7 participants