1

is there a good way to generate curl from java.net.http.HttpRequest? It seems like there is no good way to get the body (if there is a body), the closest thing to it is bodyPublisher() that returns Optional<BodyPublisher> that has only long contentLength() method.

Roy Ash
  • 1,078
  • 1
  • 11
  • 28

1 Answers1

0

Indeed, it's way trickier than it looks like.

The HttpRequest of java.net.http package is a generic object that can be used as request for any type of HttpClient<?>, hence it doesn't know what the body is, and generically provides a body publisher (type unknown) to which subscribers of a specific type can subscribe in a custom way in order to get the content from it. This is very different from HttpResponse<?>, that you usually get from a HttpClient<?> with a specific type (usually String) and that can so trivially return you a String getBody().

If you don't want to use any third-party library (which already implement the below logic), this is the way you can extract the (String) body from the publisher:

String requestBody = httpRequest.bodyPublisher().map(p -> {
        HttpResponse.BodySubscriber<String> bodySubscriber = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8);
        StringSubscriber stringSubscriber = new StringSubscriber(bodySubscriber);
        p.subscribe(stringSubscriber);
        return bodySubscriber.getBody().toCompletableFuture().join();
    }).orElse("");

... where httpRequest is your java.net.http.HttpRequest and StringSubscriber is an implementation of Flow.Subscriber<ByteBuffer> like follows:

class StringSubscriber implements Flow.Subscriber<ByteBuffer> {

    private final HttpResponse.BodySubscriber<String> wrapped;

    private 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();
    }
}

Explanation:

If there is no BodyPublisher in your request, you simply return an empty string (assuming that means no body, you can customize it to null or whatever you want). Else, if there is a BodyPublisher in your request, then you will do the following:

  • You will create a BodySubscriber of type String (meaning you will subscribe to a publisher that can provide a String version of its body)
  • Create a new instance of StringSubscriber (your own implementation that I posted above)
  • Subscribe to the publisher with your subscriber, so that the publisher can call you back with the stream content.
  • Get the future result (containing the body) when you're called back from the publisher (the call is asynchronous so you need to .join() it)
Matteo NNZ
  • 11,930
  • 12
  • 52
  • 89
  • amazing, I'll try it later, thanks, I guess you don't know of any existing (maven?) implementation... – Roy Ash Feb 06 '23 at 14:06
  • 1
    @RoyAsh not really, I think that Spring and ApacheHttp do that but I've written the code once and always use that ever since (it's not trivial for me to add new libraries in my code so I tend to avoid) – Matteo NNZ Feb 07 '23 at 09:27
  • does it work for all the publishers? no matter the underlying type of data? – Roy Ash Feb 07 '23 at 16:26
  • 1
    Since the subscriber implementation is handling a buffer of bytes then I would say so – Matteo NNZ Feb 07 '23 at 21:21
  • another question, does it work even if I haven't sent the request yet and even if I have sent it? – Roy Ash Feb 08 '23 at 14:24
  • I would say so, else you wouldn't be able to use the same HttpRequest more than once for the same reason. But this is easily testable, no? – Matteo NNZ Feb 08 '23 at 16:02
  • Absolutely, I haven't tested it yet though, thanks for your help – Roy Ash Feb 08 '23 at 17:14