Reputation: 3950
In a test, I'd like to look inside the body of a HttpRequest. I'd like to get the body as a string. It seems that the only way to do that, is to subscribe to the BodyPublisher but how does that work?
Upvotes: 18
Views: 10412
Reputation: 613
Nice solution @daniel, It is useful for testing purposes.
I have wrapped this code in a class and created a new method so we can call it as simple as:
String body = HttpRequestBodyTestUtility.extractBody(httpClientCallCaptor.getValue());
where "httpClientCallCaptor.getValue()" is a "java.net.http.HttpRequest" object.
here is the whole code:
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.Flow;
public class HttpRequestBodyTestUtility {
public static String extractBody(HttpRequest httpRequest) {
return httpRequest.bodyPublisher().map(p -> {
var bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8);
var flowSubscriber = new HttpRequestBodyTestUtility.StringSubscriber(bodySubscriber);
p.subscribe(flowSubscriber);
return bodySubscriber.getBody().toCompletableFuture().join();
}).orElseThrow();
}
static final class StringSubscriber implements Flow.Subscriber<ByteBuffer> {
final HttpResponse.BodySubscriber<String> wrapped;
StringSubscriber(HttpResponse.BodySubscriber<String> wrapped) {
this.wrapped = wrapped;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
wrapped.onSubscribe(subscription);
}
@Override
public void onNext(ByteBuffer item) {
wrapped.onNext(List.of(item));
}
@Override
public void onError(Throwable throwable) {
wrapped.onError(throwable);
}
@Override
public void onComplete() {
wrapped.onComplete();
}
}
}
Upvotes: 2
Reputation: 3288
This is an interesting question. Where do you get your HttpRequest
from? The easiest way would be to obtain the body directly from the code that creates the HttpRequest. If that's not possible then the next thing would be to clone that request and wraps its body publisher in your own implementation of BodyPublisher
before sending the request through the HttpClient. It should be relatively easy (if tedious) to write a subclass of HttpRequest
that wraps an other instance of HttpRequest
and delegates every calls to the wrapped instance, but overrides HttpRequest::bodyPublisher
to do something like:
return request.bodyPublisher().map(this::wrapBodyPublisher);
Otherwise, you might also try to subscribe to the request body publisher and obtain the body bytes from it - but be aware that not all implementations of BodyPublisher
may support multiple subscribers (whether concurrent or sequential).
To illustrate my suggestion above: something like below may work, depending on the concrete implementation of the body publisher, and provided that you can guard against concurrent subscriptions to the body publisher. That is - in a controlled test environment where you know all the parties, then it might be workable. Don't use anything this in production:
public class HttpRequestBody {
// adapt Flow.Subscriber<List<ByteBuffer>> to Flow.Subscriber<ByteBuffer>
static final class StringSubscriber implements Flow.Subscriber<ByteBuffer> {
final BodySubscriber<String> wrapped;
StringSubscriber(BodySubscriber<String> wrapped) {
this.wrapped = wrapped;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
wrapped.onSubscribe(subscription);
}
@Override
public void onNext(ByteBuffer item) { wrapped.onNext(List.of(item)); }
@Override
public void onError(Throwable throwable) { wrapped.onError(throwable); }
@Override
public void onComplete() { wrapped.onComplete(); }
}
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(new URI("http://example.com/blah"))
.POST(BodyPublishers.ofString("Lorem ipsum dolor sit amet"))
.build();
// you must be very sure that nobody else is concurrently
// subscribed to the body publisher when executing this code,
// otherwise one of the subscribers is likely to fail.
String reqbody = request.bodyPublisher().map(p -> {
var bodySubscriber = BodySubscribers.ofString(StandardCharsets.UTF_8);
var flowSubscriber = new StringSubscriber(bodySubscriber);
p.subscribe(flowSubscriber);
return bodySubscriber.getBody().toCompletableFuture().join();
}).get();
System.out.println(reqbody);
}
}
Upvotes: 22