Reputation: 43
I have a filter created where I access the body of the payload and do some logic on it (for now let's say I log the body). In the last step, I return Mono but when the request proceeds through the controller to services it throws a bad request error that the body is missing.
Code for the filter:
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
String domain = headers.getFirst("domain");
return serverWebExchange.getRequest().getBody()
.single()
.flatMap(body -> Mono.just(parseBody(body)))
.flatMap(s -> webFilterChain.filter(serverWebExchange));
}
private String parseBody(DataBuffer fbody) {
System.out.println("parsing body");
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
Channels.newChannel(baos).write(fbody.asByteBuffer().asReadOnlyBuffer());
} catch (IOException e) {
e.printStackTrace();
}
return baos.toString(StandardCharsets.UTF_8);
}
The error: org.springframework.web.server.ServerWebInputException: 400 BAD_REQUEST "Request body is missing"
What can cause this behavior?
Upvotes: 4
Views: 2608
Reputation: 2054
We can modify ServerWebExchange and construct new request to continue:
You can replace " System.out.println("Request Body in Filter: " + requestBody);" with your custom logic.
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
public class MyWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
Flux<DataBuffer> body = request.getBody();
return DataBufferUtils.join(body)
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
})
.map(bytes -> {
String requestBody = new String(bytes, StandardCharsets.UTF_8);
System.out.println("Request Body in Filter: " + requestBody);
ServerHttpRequest modifiedRequest = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
}
};
ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
return chain.filter(modifiedExchange);
})
.flatMap(result -> result);
}
}
Upvotes: 0
Reputation: 4416
The reason is that you can only read the body once (https://github.com/spring-cloud/spring-cloud-gateway/issues/1587)
You are reading it here: serverWebExchange.getRequest().getBody()
and therefore it is omitted in the request.
A solution is to cache the body, you can use for instance the ReadBodyRoutePredicateFactory (https://github.com/spring-cloud/spring-cloud-gateway/issues/1307).
Make sure the RemoveCachedBodyFilter is enabled so the body is released so you do not have memory leaks.
Upvotes: 2