Search code examples
javacurlhttprequestjava-http-client

generate CURL from java.net.http.HttpRequest


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.


Solution

  • 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)