Coder Social home page Coder Social logo

jetty-reactive-httpclient's Introduction

GitHub CI

Jetty ReactiveStream HttpClient

A ReactiveStreams wrapper around Jetty's HttpClient.

Versions

Jetty ReactiveStream HttpClient Versions Min Java Version Jetty Version Status
4.0.x Java 17 Jetty 12.0.x Stable
3.0.x Java 11 Jetty 11.0.x End of Community Support (see #461)
2.0.x Java 11 Jetty 10.0.x End of Community Support (see #461)
1.1.x Java 8 Jetty 9.4.x End of Community Support (see #153)

Usage

Plain ReactiveStreams Usage

// Create and start Jetty's HttpClient.
HttpClient httpClient = new HttpClient();
httpClient.start();

// Create a request using the HttpClient APIs.
Request request = httpClient.newRequest("http://localhost:8080/path");

// Wrap the request using the API provided by this project.
ReactiveRequest reactiveRequest = ReactiveRequest.newBuilder(request).build();

// Obtain a ReactiveStream Publisher for the response, discarding the response content.
Publisher<ReactiveResponse> publisher = reactiveRequest.response(ReactiveResponse.Content.discard());

// Subscribe to the Publisher to send the request.
publisher.subscribe(new Subscriber<ReactiveResponse>() {
    @Override
    public void onSubscribe(Subscription subscription) {
        // This is where the request is actually sent.
        subscription.request(1);
    }

    @Override
    public void onNext(ReactiveResponse response) {
        // Use the response
    }

    @Override
    public void onError(Throwable failure) {
    }

    @Override
    public void onComplete() {
    }
});

RxJava 3 Usage

// Create and start Jetty's HttpClient.
HttpClient httpClient = new HttpClient();
httpClient.start();

// Create a request using the HttpClient APIs.
Request request = httpClient.newRequest("http://localhost:8080/path");

// Wrap the request using the API provided by this project.
ReactiveRequest reactiveRequest = ReactiveRequest.newBuilder(request).build();

// Obtain a ReactiveStreams Publisher for the response, discarding the response content.
Publisher<ReactiveResponse> publisher = reactiveRequest.response(ReactiveResponse.Content.discard());

// Wrap the ReactiveStreams Publisher with RxJava.
int status = Single.fromPublisher(publisher)
        .map(ReactiveResponse::getStatus)
        .blockingGet();

Response Content Processing

The response content is processed by passing a BiFunction to ReactiveRequest.response().

The BiFunction takes as parameters the ReactiveResponse and a Publisher for the response content, and must return a Publisher of items of type T that is the result of the response content processing.

Built-in utility functions can be found in ReactiveResponse.Content.

Example: discarding the response content

Publisher<ReactiveResponse> response = request.response(ReactiveResponse.Content.discard());

Example: converting the response content to a String

Publisher<String> string = request.response(ReactiveResponse.Content.asString());

Example: discarding non 200 OK response content

Publisher<ReactiveResponse.Result<String>> publisher = request.response((response, content) -> {
    if (response.getStatus() == HttpStatus.OK_200) {
        return ReactiveResponse.Content.asStringResult().apply(response, content);
    } else {
        return ReactiveResponse.Content.<String>asDiscardResult().apply(response, content);
    }
});

Class ReactiveResponse.Result is a Java record that holds the response and the response content to allow application code to implement logic that uses both response information such as response status code and response headers, and response content information.

Alternatively, you can write your own processing BiFunction using any ReactiveStreams library, such as RxJava 3 (which provides class Flowable):

Example: discarding non 200 OK response content

Publisher<Content.Chunk> publisher = reactiveRequest.response((reactiveResponse, contentPublisher) -> {
    if (reactiveResponse.getStatus() == HttpStatus.OK_200) {
        // Return the response content itself.
        return contentPublisher;
    } else {
        // Discard the response content.
        return Flowable.fromPublisher(contentPublisher)
                .filter(chunk -> {
                    // Tell HttpClient that you are done with this chunk.
                    chunk.release();
                    // Discard this chunk.
                    return false;
                });
    }
});

The response content (if any) can be further processed:

Single<Long> contentLength = Flowable.fromPublisher(publisher)
        .map(chunk -> {
            // Tell HttpClient that you are done with this chunk.
            chunk.release();
            // Return the number of bytes of this chunk.
            return chunk.remaining();
        })
        // Sum the bytes of the chunks.
        .reduce(0L, Long::sum);

Providing Request Content

Request content can be provided in a ReactiveStreams way, through the ReactiveRequest.Content class, which is-a Publisher with the additional specification of the content length and the content type.

Below you can find an example using the utility methods in ReactiveRequest.Content to create request content from a String:

HttpClient httpClient = ...;

String text = "content";
ReactiveRequest request = ReactiveRequest.newBuilder(httpClient, "http://localhost:8080/path")
        .content(ReactiveRequest.Content.fromString(text, "text/plain", StandardCharsets.UTF_8))
        .build();

Below another example of creating request content from another Publisher:

HttpClient httpClient = ...;

// The Publisher of request content.
Publisher<T> publisher = ...;

// Transform items of type T into ByteBuffer chunks.
Charset charset = StandardCharsets.UTF_8;
Flowable<Content.Chunk> chunks = Flowable.fromPublisher(publisher)
        .map((T t) -> toJSON(t))
        .map((String json) -> json.getBytes(charset))
        .map((byte[] bytes) -> ByteBuffer.wrap(bytes))
        .map(byteBuffer -> Content.Chunk.from(byteBuffer, false));

ReactiveRequest request = ReactiveRequest.newBuilder(httpClient, "http://localhost:8080/path")
        .content(ReactiveRequest.Content.fromPublisher(chunks, "application/json", charset))
        .build();

Events

If you are interested in the request and/or response events that are emitted by the Jetty HttpClient APIs, you can obtain a Publisher for request and/or response events, and subscribe a listener to be notified of the events.

The event Publishers are "hot" producers and do no buffer events.

If you subscribe to an event Publisher after the events have started, the Subscriber will not be notified of events that already happened, and will be notified of any event that will happen.

HttpClient httpClient = ...;

ReactiveRequest request = ReactiveRequest.newBuilder(httpClient, "http://localhost:8080/path").build();
Publisher<ReactiveRequest.Event> requestEvents = request.requestEvents();

// Subscribe to the request events before sending the request.
requestEvents.subscribe(new Subscriber<ReactiveRequest.Event>() {
    ...
});

// Similarly for response events.
Publisher<ReactiveResponse.Event> responseEvents = request.responseEvents();

// Subscribe to the response events before sending the request.
responseEvents.subscribe(new Subscriber<ReactiveResponse.Event>() {
    ...
});

// Send the request.
ReactiveResponse response = Single.fromPublisher(request.response(ReactiveResponse.Content.discard()))
        .blockingGet();

jetty-reactive-httpclient's People

Contributors

dependabot[bot] avatar hannosgit avatar joakime avatar olamy avatar sbordet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jetty-reactive-httpclient's Issues

Incompatible with Spring Boot 2.2.x

java.lang.AbstractMethodError: Receiver class org.eclipse.jetty.reactive.client.internal.ResponseListenerPublisher does not define or inherit an implementation of the resolved method abstract onContent(Lorg/eclipse/jetty/client/api/Response;Ljava/util/function/LongConsumer;Ljava/nio/ByteBuffer;Lorg/eclipse/jetty/util/Callback;)V of interface org.eclipse.jetty.client.api.Response$DemandedContentListener.
at org.eclipse.jetty.client.ResponseNotifier.notifyContent(ResponseNotifier.java:155) ~[jetty-client-9.4.22.v20191022.jar:9.4.22.v20191022]
at org.eclipse.jetty.client.ResponseNotifier.notifyContent(ResponseNotifier.java:146) ~[jetty-client-9.4.22.v20191022.jar:9.4.22.v20191022]

OSGi Headers are missing

Unlike the rest of Jetty, the reactive client has no OSGi headers. I think it makes sense to have them.

Support for read/write timeout.

Is it possible to set read and write connection timeout using HttpClient? From what I can see, HttpClient does not provide support for set read and write connection timeout.

Version for Jetty 12

Hi,

I work on the Spring Framework, where we use jetty-reactive-httpclient as one of the options for providing HTTP connections to our WebClient. We are currently considering upgrading to Jetty 12 as part of our 6.1 branch, but noticed that a version of jetty-reactive-httpclient for Jetty 12 has not been released yet.

Is a jetty-reactive-httpclient for Jetty 12 forthcoming? And if so, could you please share your release schedule?

Flushing strategy

Hi, I am starting adding support for Jetty ReactiveStream HttpClient in Spring Framework 5 as a second engine for our Reactive WebClient as an alternative to Reactor Netty (see SPR-15092 for more details).

As you can see in our ReactiveHttpOutputMessage interface, we have 2 flavors when writing the content : one where we let the underlying engine taking care of flushing when relevant, and one where we perform flushing explicitly.

Is there a way to control flushing with current API?

Using Jetty with Spring Webclient causes thread leak

Hi,

I am currently looking into using the jetty httpclient to replace netty for my Spring Webclient which is responsible for external calls. As there is a hard limit when these external requests shall be cancelled, I am using a timeout in the Webclient execution call:

ResponseEntity<String> response = request.retrieve()
    .toEntity(String.class)
    .timeout(Duration.ofMillis(timeoutMillis))
    .block();

I am using Jetty as ClientHttpConnector, simplified:

HttpClient httpClient = new HttpClient(SSL_CONTEXT_FACTORY);
WebClient.builder()
    .clientConnector(new JettyClientHttpConnector(httpClient))
...

In a load test scenario where all calls are cancelled before it can finish, I am observing a thread leak on the Jetty HttpClient Threads:
image
Even after a long cooldown they are not coming back. And at the peak new requests are failing due to no free resources.

Caused by: java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
	at java.base/java.lang.Thread.start0(Native Method)
	at java.base/java.lang.Thread.start(Thread.java:804)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.startThread(QueuedThreadPool.java:660)

`IllegalStateException` in `ContentSource.demand()`

Jetty 12.0.3, jetty-reactive-httpclient 4.0.1, java 21

Sometimes, if connection is slow, I'm getting the following error:

java.lang.IllegalStateException
	at org.eclipse.jetty.client.transport.ResponseListeners$ContentSourceDemultiplexer$ContentSource.demand(ResponseListeners.java:665)
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor$ContentPublisher.read(ResponseListenerProcessor.java:197)
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor$ContentPublisher.run(ResponseListenerProcessor.java:181)
	at org.eclipse.jetty.reactive.client.internal.QueuedSinglePublisher.tryProduce(QueuedSinglePublisher.java:70)
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor$ContentPublisher.onRequest(ResponseListenerProcessor.java:174)
	at org.eclipse.jetty.reactive.client.internal.AbstractSinglePublisher.request(AbstractSinglePublisher.java:90)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.request(BasicFuseableSubscriber.java:153)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.request(BasicFuseableSubscriber.java:153)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.request(BasicFuseableSubscriber.java:153)
	at io.reactivex.internal.subscribers.BasicFuseableSubscriber.request(BasicFuseableSubscriber.java:153)
	at io.reactivex.internal.subscriptions.SubscriptionHelper.deferredRequest(SubscriptionHelper.java:219)
	at io.reactivex.internal.subscribers.StrictSubscriber.request(StrictSubscriber.java:70)

Please, take a look. I'm not sure how to reproduce this error now, attach the code if I find the way.

Fails to follow redirects on HEAD, PUT or POST requests with content

Jetty 12.0.3, jetty-reactive-httpclient 4.0.1, java 21

Jetty http client fails to follow redirects when performing HEAD, PUT or POST requests with content (even if content is actually empty).

Check the following test code to reproduce, it contains several methods for various requests methods/reactive/not-reactive jetty, method failsSomeWithBody fails to indicate the case where jetty-reactive-httpclient does not work as expected.

import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Single;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.BytesRequestContent;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.reactive.client.ReactiveRequest;
import org.eclipse.jetty.reactive.client.ReactiveResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;
import org.hamcrest.MatcherAssert;
import org.hamcrest.core.IsEqual;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.reactivestreams.Publisher;

public class RedirectTest {

    private Server server;

    private HttpClient client;

    private int port;

    @BeforeEach
    void init() throws Exception {
        this.server = new Server();
        final ServerConnector conn = new ServerConnector(this.server);
        this.server.addConnector(conn);
        this.client = new HttpClient();
        this.client.setFollowRedirects(true);
        this.client.start();
        this.server.setHandler(
            new Handler.Abstract() {
                @Override
                public boolean handle(final Request request, final Response response, final Callback callback) throws Exception {
                    request.read().release();
                    if (request.getHttpURI().getPath().contains("final")) {
                        response.setStatus(200);
                    } else {
                        response.setStatus(307);
                        response.getHeaders().add("Location", "/final");
                    }
                    callback.succeeded();
                    return true;
                }
        });
        this.server.start();
        this.port = conn.getLocalPort();
    }

    @Test
    void getWithBodyWorks() {
        org.eclipse.jetty.client.Request rq = this.client.newRequest(
            String.format("http://localhost:%s", this.port)
        ).method("GET");
        ReactiveRequest build = ReactiveRequest.newBuilder(rq).content(
            ReactiveRequest.Content.fromPublisher(
                Flowable.just(Content.Chunk.from(ByteBuffer.wrap(new byte[]{}), true)), "*"
            )
        ).build();
        final Publisher<ReactiveResponse> publisher = build.response(ReactiveResponse.Content.discard());
        int status = Single.fromPublisher(publisher)
            .map(ReactiveResponse::getStatus)
            .blockingGet();
        MatcherAssert.assertThat(status, new IsEqual<>(200));
    }

    @ParameterizedTest
    @ValueSource(strings = {"HEAD", "PUT", "POST"})
    void failsSomeWithBody(final String method) {
        org.eclipse.jetty.client.Request rq =
            this.client.newRequest(String.format("http://localhost:%s/some/location", this.port)).method(method);
        final ReactiveRequest build = ReactiveRequest.newBuilder(rq)
            .content(
                ReactiveRequest.Content.fromPublisher(
                    Flowable.just(Content.Chunk.from(ByteBuffer.wrap(new byte[]{}), true)),
                    "*"
                )
            ).build();
        final Publisher<ReactiveResponse> publisher = build.response(ReactiveResponse.Content.discard());
        int status = Single.fromPublisher(publisher)
            .map(ReactiveResponse::getStatus)
            .blockingGet();
        MatcherAssert.assertThat(status, new IsEqual<>(200));
    }

    @ParameterizedTest
    @ValueSource(strings = {"HEAD", "PUT", "POST"})
    void notReactiveWorksFine(final String method) throws ExecutionException, InterruptedException, TimeoutException {
        ContentResponse rs = this.client.newRequest(String.format("http://localhost:%s/some/location", this.port))
            .method(method).body(new BytesRequestContent("abc")).send();
        MatcherAssert.assertThat(rs.getStatus(), new IsEqual<>(200));
    }

    @ParameterizedTest
    @ValueSource(strings = {"HEAD", "PUT", "POST"})
    void worksWithNoBody(final String method) {
        org.eclipse.jetty.client.Request rq =
            this.client.newRequest(String.format("http://localhost:%s/some/location", this.port)).method(method);
        final ReactiveRequest build = ReactiveRequest.newBuilder(rq).build();
        final Publisher<ReactiveResponse> publisher = build.response(ReactiveResponse.Content.discard());
        int status = Single.fromPublisher(publisher)
            .map(ReactiveResponse::getStatus)
            .blockingGet();
        MatcherAssert.assertThat(status, new IsEqual<>(200));
    }

    @AfterEach
    void stop() throws Exception {
        this.server.stop();
        this.client.stop();
    }

}

Intermittent onComplete observed without onNext, response content dropped

I am using the jetty-reactive-httpclient via the Spring 5 WebClient/JettyClientHttpConnector in a Spring WebFlux service, and I see that intermittently the body content from a response is not emitted as expected - instead I see an onComplete call without a prior onNext. However, the debug log lines from the jetty-client indicate that the response body was received/decoded. I seem to be able to reproduce the issue more easily if my machine is busy using lots of CPU / cores in the background - so I suspect some sort of race condition or other multi-threading issue is behind this unexpected behavior. Even with CPU busy in the background, it still usually takes many tries to reproduce - perhaps something like 1 in every 100 tries. I am not sure that the problem is in jetty-reactive-httpclient code, but some of the relevant/potentially interesting debug log lines come from here so I am hoping at least for some ideas/guidance.

I have captured debug logging for a request that encountered this problem, as well as debug logging for the same sort of request that worked as expected for comparison. One detail that seems potentially significant is the name of the thread which various log lines origniate from. In the normal "working as expected" case only the first few log lines come from the WebFlux/tomcat event loop thread named like "http-nio-16027-exec-#", whereas when the problem occurs some of the ResponseListenerPublisher messages etc. also end up coming from this thread rather than from a thread named like "HttpClient*" as in the normal "working as expected" case.
I've attached the captured debug log output:
jetty_reactive_webclient_problem_log.txt
jetty_reactive_webclient_normal_log.txt

It can be seen in the normal log that after the response content is decoded there is an onNext call made prior to onComplete. However, in the problem log after the response content is decoded, there is an onComplete call but no prior onNext call - and it is then subsequently logged by the default reactor operators that there was an onNextDropped with a DefaultDataBuffer (possibly leaked?).

For reference, the client I am using is essentially instantiated like:

final HttpClient httpClient = new HttpClient(new SslContextFactory(true));
httpClient.setAddressResolutionTimeout(httpConfig.getConnectionRequestTimeout());
httpClient.setConnectTimeout(httpConfig.getConnectionRequestTimeout());
httpClient.setIdleTimeout(httpConfig.getSocketTimeout());
httpClient.setTCPNoDelay(true);
httpClient.setMaxConnectionsPerDestination(httpConfig.getMaxTotalConnections());
httpClient.setFollowRedirects(false);
httpClient.start();

final ClientHttpConnector clientHttpConnector = new JettyClientHttpConnector(httpClient);
final WebClient webClient = WebClient.builder()
        .clientConnector(clientHttpConnector)
        .build();

The WebClient is then being used like:

return client.get()
        .uri(uri)
        .retrieve()
        .bodyToMono(ResponseModel.class)
        .log() // You will see the events such as onSubscribe / onComplete in the attatched/linked log files emitted due to this Mono.log call
        .map(this::processResult)
...

Relevant library versions my project is using are:
org.eclipse.jetty:jetty-reactive-httpclient:1.0.2
org.eclipse.jetty:jetty-client:9.4.14.v20181114
And using version 5.1.4.RELEASE of the spring framework.

Given the intermittent nature of the problem, I haven't been able to produce a simple test case that reliably reproduces the issue. Please do let me know if any further information from my end would be useful. Any ideas/guidance will be appreciated. In the interim, I will keep digging into the relevant library source code to see if I can better understand the problem and/or propose a fix.

Thank you!

jetty reactive client bottle neck for spring reactive flapmap operator

What I am trying to achieve:

Send as many http requests as possible, in parallel, to a very reliable third party service from an aggressive Flux

Background:

The third party service is very reliable and can sustain a very high number of requests. So far, I am the only client of this third party service. I am invited to hit the third party server as hard as possible. The third party service, which I have no control over, does not offer any bulk/list API. I can only send requests one by one. On their side, each request takes a constant one second to process.

This third party API has keep-alive enabled. But does not support GRPC, http2, socket. There is no rate limit at any point of the end to end flow.

What did I try:

Here is the configuration of my http client, as well as the logic to send the http requests (hopefully as many requests, as fast as possible)

client

    public WebClient webClient(final WebClient.Builder builder) {
        final var clientConnector = new ClientConnector();
        final var httpClient = new org.eclipse.jetty.client.HttpClient(new HttpClientTransportDynamic(clientConnector));
        httpClient.setMaxRequestsQueuedPerDestination(9999);
        httpClient.setMaxConnectionsPerDestination(9999);
        return builder.baseUrl(hostAndPort).defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).clientConnector(new JettyClientHttpConnector(httpClient)).build();
    }

the flux:

// some very fast flux, this line is just an example
        Flux<String> inputFlux = Flux.interval(Duration.ofMillis(1)).map(i -> "transform to request payload number " + i);
        //send as many requests as fast as possible, note the 4096
        Flux<String> resultFlux = inputFlux.flatMap(oneInput -> webClient.post().bodyValue(oneInput).retrieve().bodyToMono(String.class), 4096);
        //doing something with the result
        return resultFlux.map(oneResult -> doSomething(oneResult));

Using this, I asked the third party service and they gave me a number N, my number of requests per second.

First observation, the number of concurrency for flatmap here is 4096. And since the third party takes one second to process the request, I would have expected a rate N of 4096 requests per second.

However, I am nowhere close. The third party service told me I am at 16ish requests per second.

Issue:

I believe the underlying limitation comes from jetty http client.
I interchanged the webclient (which uses jetty) with a dummy operation, and could see much higher throughput.

I believe the issue here is that the scheduling policy of the jetty-reactive-httpclient library is limiting the throughput.
What parameters, the number of concurrent connections, possibly IO threads, keep alive, I should use in order to "unleash" jetty reactive http client?

Cannot invoke "org.eclipse.jetty.io.Content$Chunk.hasRemaining()" because "chunk" is null

This test code with version 4.0.0 of jetty-reactive-httpclient on java 20

import io.reactivex.Flowable;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.reactive.client.ReactiveRequest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class MavenTest {

    @Test
    void test() throws Exception {
        final HttpClient client = new HttpClient();
        client.start();
        final CountDownLatch contentLatch = new CountDownLatch(1);
        final CountDownLatch responseLatch = new CountDownLatch(1);
        final ReactiveRequest rq = ReactiveRequest.newBuilder(
            client.newRequest("https://repo.maven.apache.org/maven2/args4j/args4j/2.32/args4j-2.32.jar")
        ).build();
        Flowable.fromPublisher(
            rq.response(
                (rs, chunks) -> Flowable.fromPublisher(chunks).map(
                    chunk -> {
                        ByteBuffer byteBuffer = chunk.getByteBuffer();
                        chunk.release();
                        return byteBuffer.remaining();
                    }
                ).doOnComplete(contentLatch::countDown)
            )
        ).doOnComplete(responseLatch::countDown).subscribe();
        assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
    }
}

leads to the following exception

[HttpClient@1623b78d-37] WARN org.eclipse.jetty.util.thread.SerializedInvoker - Serialized invocation error
java.lang.NullPointerException: Cannot invoke "org.eclipse.jetty.io.Content$Chunk.hasRemaining()" because "chunk" is null
	at org.eclipse.jetty.client.transport.ResponseListeners$ContentSourceDemultiplexer$ContentSource.onChunk(ResponseListeners.java:605)
	at org.eclipse.jetty.client.transport.ResponseListeners$ContentSourceDemultiplexer.onDemandCallback(ResponseListeners.java:492)
	at org.eclipse.jetty.util.thread.SerializedInvoker$Link.run(SerializedInvoker.java:191)
	at org.eclipse.jetty.util.thread.SerializedInvoker.run(SerializedInvoker.java:117)
	at org.eclipse.jetty.client.transport.HttpReceiver$ContentSource.invokeDemandCallback(HttpReceiver.java:722)
	at org.eclipse.jetty.client.transport.HttpReceiver$ContentSource.onDataAvailable(HttpReceiver.java:661)
	at org.eclipse.jetty.client.transport.HttpReceiver.responseContentAvailable(HttpReceiver.java:303)
	at org.eclipse.jetty.client.transport.internal.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:82)
	at org.eclipse.jetty.client.transport.internal.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
	at org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.onFillable(SslConnection.java:554)
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:373)
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:478)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:441)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:293)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:201)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:410)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
	at java.base/java.lang.Thread.run(Thread.java:1623)

Please, take a look.

Announcement of End of Community Support for Jetty Reactive HttpClient 1.1.x

The Jetty Reactive HttpClient project announces the End of Community Support for version 1.1.x, following Jetty's announcement of End of Community Support for Jetty 9.x.

November 1st, 2022 marks the official End of Community Support for Jetty Reactive HttpClient 1.1.x.

While users of Jetty Reactive HttpClient 1.1.x will continue to see releases for security and critical issues until Jetty Reactive HttpClient 1.1.x goes End of Life, the only on-demand support provided will be for Webtide customers.

As for the official End of Life of Jetty Reactive HttpClient 1.1.x, we will give at least 6 months notice after the last Webtide customer has transitioned off of using Jetty 9.x. We can’t provide a firm date, but it is unlikely to happen before 2025.

Thank you for your continued usage and support of Jetty Reactive HttpClient.
Please take a moment to look at the newer Jetty Reactive HttpClient releases 2.0.x and 3.0.x.

Action Before November 1st, 2022 End of Community Support (November 1st, 2022) End of Life (Date TBD)
Community PRs reviewed and integrated ✔️
Webtide Customer PRs reviewed and integrated ✔️ ✔️
Community triggered releases ✔️
Webtide Customer triggered releases ✔️ ✔️
Security / Vulnerability triggered releases ✔️ ✔️

Use HttpClient demand feature

Since jetty/jetty.project#2429 has been fixed, HttpClient now has a demand feature similar to reactive streams that can be used to request more content without having to succeed the callback and recycle the buffer, so that demand and buffer recycling can now be performed independently.

Update the reactive HTTP client to leverage this new feature.

org.eclipse.jetty.io.EofException: null Suppressed: javax.net.ssl.SSLHandshakeException: null

Jetty version(s)
Latest Jetty from Maven as of this writing

Java version/vendor (use: java -version)
Java 17

OS type/version
Issue observed for Windows Unix and Mac

Description
I have a very simple reactive project and a jetty http client configured as such:

@Bean
    public WebClient webClient(final WebClient.Builder builder) {
        final var sslContextFactory = new SslContextFactory.Client();
        sslContextFactory.setSslContext(getMyCustomSSLContext());
        final var clientConnector = new ClientConnector();
        clientConnector.setSslContextFactory(sslContextFactory);
        final var httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
        return builder.baseUrl(hostAndPort).defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).clientConnector(new JettyClientHttpConnector(httpClient)).build();
    }

reproducible 100% of the time, I am getting this issue while sending requests at high load to a third party server.
The third party server is handling correct requests to others clients when I am failing with this:

2023-05-10 09:59:46 1 ERROR --- [HttpClient@787e4357-88] [,] reactor.core.publisher.Operators : Operator called default onErrorDropped
org.springframework.web.reactive.function.client.WebClientRequestException: null
        at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136)
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
        *__checkpoint ? Request to POST null [DefaultWebClient]
Original Stack Trace:
                at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136)
                at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:55)
                at reactor.core.publisher.Mono.subscribe(Mono.java:4485)
                at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
                at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
                at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:222)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:278)
                at org.eclipse.jetty.reactive.client.internal.AbstractSingleProcessor.onError(AbstractSingleProcessor.java:120)
                at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor.onComplete(ResponseListenerProcessor.java:141)
                at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:213)
                at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:205)
                at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:477)
                at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:457)
                at org.eclipse.jetty.client.HttpReceiver.abort(HttpReceiver.java:553)
                at org.eclipse.jetty.client.HttpReceiver.responseFailure(HttpReceiver.java:449)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.failAndClose(HttpReceiverOverHTTP.java:428)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:208)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:91)
                at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
                at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
                at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
                at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint$IncompleteWriteCallback.succeeded(SslConnection.java:1599)
                at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:291)
                at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:254)
                at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:386)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.needsFillInterest(SslConnection.java:923)
                at org.eclipse.jetty.io.AbstractEndPoint$1.needsFillInterest(AbstractEndPoint.java:45)
                at org.eclipse.jetty.io.FillInterest.tryRegister(FillInterest.java:78)
                at org.eclipse.jetty.io.FillInterest.register(FillInterest.java:50)
                at org.eclipse.jetty.io.AbstractEndPoint.fillInterested(AbstractEndPoint.java:367)
                at org.eclipse.jetty.io.AbstractConnection.fillInterested(AbstractConnection.java:135)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.fillInterested(HttpReceiverOverHTTP.java:269)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:192)
                at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:91)
                at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
                at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
                at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
                at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:558)
                at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
                at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
                at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
                at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
                at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416)
                at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385)
                at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272)
                at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140)
                at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
                at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
                at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
                at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
                at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.eclipse.jetty.io.EofException: null
        at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:116)
        at org.eclipse.jetty.io.ssl.SslConnection.networkFlush(SslConnection.java:486)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.flush(SslConnection.java:1115)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:661)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:180)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:91)
        at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
        at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint$IncompleteWriteCallback.succeeded(SslConnection.java:1599)
        at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:291)
        at org.eclipse.jetty.io.WriteFlusher.write(WriteFlusher.java:254)
        at org.eclipse.jetty.io.AbstractEndPoint.write(AbstractEndPoint.java:386)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.needsFillInterest(SslConnection.java:923)
        at org.eclipse.jetty.io.AbstractEndPoint$1.needsFillInterest(AbstractEndPoint.java:45)
        at org.eclipse.jetty.io.FillInterest.tryRegister(FillInterest.java:78)
        at org.eclipse.jetty.io.FillInterest.register(FillInterest.java:50)
        at org.eclipse.jetty.io.AbstractEndPoint.fillInterested(AbstractEndPoint.java:367)
        at org.eclipse.jetty.io.AbstractConnection.fillInterested(AbstractConnection.java:135)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.fillInterested(HttpReceiverOverHTTP.java:269)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:192)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:91)
        at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:90)
        at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:558)
        at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
        at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
        at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
        at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416)
        at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385)
        at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272)
        at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140)
        at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
        at java.base/java.lang.Thread.run(Unknown Source)
        Suppressed: org.eclipse.jetty.io.EofException: null
                at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:116)
                at org.eclipse.jetty.io.ssl.SslConnection.networkFlush(SslConnection.java:486)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.flush(SslConnection.java:1115)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.doShutdownOutput(SslConnection.java:1338)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.doClose(SslConnection.java:1434)
                at org.eclipse.jetty.io.AbstractEndPoint.doOnClose(AbstractEndPoint.java:258)
                at org.eclipse.jetty.io.AbstractEndPoint.close(AbstractEndPoint.java:227)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.flush(SslConnection.java:1204)
                ... 38 common frames omitted
        Caused by: java.io.IOException: Broken pipe
                at java.base/sun.nio.ch.FileDispatcherImpl.writev0(Native Method)
                at java.base/sun.nio.ch.SocketDispatcher.writev(Unknown Source)
                at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
                at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
                at java.base/sun.nio.ch.SocketChannelImpl.write(Unknown Source)
                at java.base/java.nio.channels.SocketChannel.write(Unknown Source)
                at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:110)
                ... 45 common frames omitted
        Suppressed: javax.net.ssl.SSLHandshakeException: null
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.handshakeFailed(SslConnection.java:962)
                at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.flush(SslConnection.java:1192)
                ... 38 common frames omitted
        Caused by: [CIRCULAR REFERENCE: org.eclipse.jetty.io.EofException: null]
Caused by: java.io.IOException: Broken pipe
        at java.base/sun.nio.ch.FileDispatcherImpl.writev0(Native Method)
        at java.base/sun.nio.ch.SocketDispatcher.writev(Unknown Source)
        at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
        at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
        at java.base/sun.nio.ch.SocketChannelImpl.write(Unknown Source)
        at java.base/java.nio.channels.SocketChannel.write(Unknown Source)
        at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:110)
        ... 40 common frames omitted

Could you please help on this issue?

java.util.concurrent.ExecutionException: org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: Authentication challenge without WWW-Authenticate header

Jetty version

Gradle Dependency
implementation 'org.eclipse.jetty:jetty-reactive-httpclient:3.0.0'

Java version
1.11

OS type/version
macOS High Sierra/10.13.6

Other
Using Springs WebFlux (2.4.0) WebClient with Jetty

Description
When executing request I'm getting 401 status code from response. Because of that 401 status code Jetty is throwing this exception:

java.util.concurrent.ExecutionException: org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: Authentication challenge without WWW-Authenticate header
	at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395)
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999)
	at com.company.test.css.api.security.clients.tests.authn.AuthnTests.verifyAuthnHelper(AuthnTests.java:261)
	at com.company.test.css.api.security.clients.tests.authn.AuthnTests.verifyAuthn(AuthnTests.java:66)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:132)
	at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:238)
	at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:181)
	at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:252)
	at org.testng.internal.TestInvoker.invokeMethod(TestInvoker.java:595)
	at org.testng.internal.TestInvoker.invokeTestMethod(TestInvoker.java:174)
	at org.testng.internal.MethodRunner.runInSequence(MethodRunner.java:46)
	at org.testng.internal.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:822)
	at org.testng.internal.TestInvoker.invokeTestMethods(TestInvoker.java:147)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:128)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: Authentication challenge without WWW-Authenticate header
	at org.eclipse.jetty.client.AuthenticationProtocolHandler$AuthenticationListener.onComplete(AuthenticationProtocolHandler.java:163)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ Body from POST https://hostname/v1/authn [DefaultClientResponse]
Stack trace:
		at org.eclipse.jetty.client.AuthenticationProtocolHandler$AuthenticationListener.onComplete(AuthenticationProtocolHandler.java:163)
		at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:218)
		at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:210)
		at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:481)
		at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:461)
		at org.eclipse.jetty.client.HttpReceiver.responseSuccess(HttpReceiver.java:424)
		at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.messageComplete(HttpReceiverOverHTTP.java:365)
		at org.eclipse.jetty.http.HttpParser.handleContentMessage(HttpParser.java:585)
		at org.eclipse.jetty.http.HttpParser.parseContent(HttpParser.java:1702)
		at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1531)
		at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.parse(HttpReceiverOverHTTP.java:204)
		at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:144)
		at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:79)
		at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:131)
		at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:169)
		at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
		at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
		at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:540)
		at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:395)
		at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:161)
		at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
		at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
		at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
		at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
		at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
		at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)
		at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)
		at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:773)
		at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:905)
		at java.base/java.lang.Thread.run(Thread.java:834)

The request looks like this:

URI: https://company.com/v1/authn
Method: POST
Request Headers =>
		Accept-Encoding : gzip
		User-Agent : Jetty/9.4.34.v20201102
		Content-Type : application/json
		Accept : application/json
		Content-Length : 155
		Host : company.com

When I use another type of client like RestTemplate it doesn't happen but here is RestTemplates request/response so you can see what actual response headers/etc look like using different httpclient:

===============================request begin=================================
 URI         : https://company.com/v1/authn
Method      : POST
Headers     : [Accept:"text/plain, */*", Content-Type:"application/json", Content-Length:"155"]
Request body: {"context":"field1":"blahblah"},"field2":"blah","field3":"blah","field4":"blah"}
================================request end==================================
================================response begin================================
Status code  : 401 UNAUTHORIZED
Status text  : Unauthorized
Headers      : [Access-Control-Allow-Origin:"*", Content-Length:"177", Content-Type:"application/json; charset=utf-8", Strict-Transport-Security:"max-age=15552000; includeSubDomains", Vary:"Accept-Encoding", X-Content-Type-Options:"nosniff", X-Dns-Prefetch-Control:"off", X-Download-Options:"noopen", X-Frame-Options:"SAMEORIGIN", X-Xss-Protection:"1; mode=block", Date:"Sat, 19 Dec 2020 00:03:44 GMT", Connection:"close", Set-Cookie:"ADRUM_BT=R:0|i:131751|g:3c8c635d-a65f-4bba-944a-e9ed94b3639096|e:571|s:f|h:e|n:fig_f08e7d36-53bd-409c-a070-91e6f5d79d0f; Path=/; Expires=Sat, 19 Dec 2020 00:04:14 GMT", Server-Timing:"cdn-cache; desc=MISS", "edge; dur=118", "origin; dur=671"]
Response body: {"code":"12345","description":"The password you entered is incorrect, Please try again","x-request-id":"b71adb1a-652e","debug":"Authentication failed"}
=================================response end=================================

Another thing to note is the service I'm calling calls a downstream Okta service. All we are doing is bubbling up the response body and response status from them. Also, there is no WWW-Authenticate header returned from them.

Why is this happening with Jetty? Seems like a defect/issue?

Can't use 1.0.3 client with latest version of jetty-client 9.4.22

java.lang.AbstractMethodError: Receiver class org.eclipse.jetty.reactive.client.internal.ResponseListenerPublisher does not define or inherit an implementation of the resolved method abstract onContent(Lorg/eclipse/jetty/client/api/Response;Ljava/util/function/LongConsumer;Ljava/nio/ByteBuffer;Lorg/eclipse/jetty/util/Callback;)V of interface org.eclipse.jetty.client.api.Response$DemandedContentListener.

Provide BlockHoundIntegration

builder.allowBlockingCallsInside("org.eclipse.jetty.client.AbstractConnectionPool", "acquire");
builder.allowBlockingCallsInside("org.eclipse.jetty.client.MultiplexConnectionPool", "acquire");
builder.allowBlockingCallsInside("org.eclipse.jetty.util.BlockingArrayQueue", "poll");
builder.allowBlockingCallsInside("org.eclipse.jetty.util.BlockingArrayQueue", "offer");

2.x.x and 3.x.x dependency issues

I am getting NoClassDefFoundError. On inspecting the issue, I find that PublisherContentProvider cannot be resolved.

Rolling back to 1.x.x works fine.

	@Override
	public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
		return Mono.<Void>create(sink -> {
			ReactiveRequest.Content content = Flux.from(body)
					.map(buffer -> toContentChunk(buffer, sink))
					.as(chunks -> ReactiveRequest.Content.fromPublisher(chunks, getContentType()));
			this.jettyRequest.content(new PublisherContentProvider(content));
			sink.success();
		})
				.then(doCommit(this::completes));
	}

NPE thrown by AbstractSingleProcessor

When running JettyWebClientIntegrationTests#shouldSendLargeTextFile test from this repro project, a NullPointerException is thrown. The same test is green with Reactor Netty and the new JDK HTTP client (and Reactor is validated against Reactive Streams TCK).

java.lang.NullPointerException
	at org.eclipse.jetty.reactive.client.internal.AbstractSingleProcessor.cancel(AbstractSingleProcessor.java:38)
	at reactor.core.publisher.MonoNext$NextSubscriber.cancel(MonoNext.java:108)
	at reactor.core.publisher.Operators.terminate(Operators.java:642)
	at reactor.core.publisher.MonoIgnoreThen$ThenAcceptInner.cancel(MonoIgnoreThen.java:312)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.cancel(MonoIgnoreThen.java:180)
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.cancel(FluxPeekFuseable.java:153)
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.cancel(FluxPeekFuseable.java:153)
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.cancel(FluxPeekFuseable.java:153)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.cancel(FluxMapFuseable.java:161)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.drainLoop(Operators.java:1497)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.drain(Operators.java:1466)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.cancel(Operators.java:1278)
	at reactor.core.publisher.Operators.terminate(Operators.java:642)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.cancel(MonoFlatMap.java:180)
	at reactor.core.publisher.BlockingSingleSubscriber.dispose(BlockingSingleSubscriber.java:63)
	at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:109)
	at reactor.core.publisher.Mono.block(Mono.java:1199)
	at org.springframework.web.reactive.function.client.JettyWebClientIntegrationTests.shouldSendLargeTextFile(JettyWebClientIntegrationTests.java:345)

Could you please check if there is something to fix on Jetty side, and maybe plan TCK validation of the RS infrastructure provided with this project when you will have some free cycle?

Issue closed without investigation

I have noticed you have closed #38

Problem is not related to my application code. This library is internally referencing something that does not exist.

I am just being persistent here so you do not assume this is not a problem. This will be my final attempt to bring your attention to this.

Prepare for 4.0.0 release

Remember to:

  • Restore RxJava2Test.testResponseEvents() after Jetty fix.
  • Write a test case with leak tracking for Chunks.
  • Move to JUnit 5.

Max requests queued per destination 1024 exceeded for HttpDestination

Hello team,

I would like to leverage reactive jetty http client in order to send as much request as possible in parallel.

However, when the load is climbing, I am always facing this issue:

Caused by: java.util.concurrent.RejectedExecutionException: Max requests queued per destination 1024 exceeded for HttpDestination[Origin@6ce2a85a[https://the-host.com:9200,tag=null,protocol=Protocol@7f4624a[proto=[http/1.1],nego=false]]]@12518d38,state=STARTED,queue=1024,pool=MultiplexConnectionPool@aa9711e[s=STARTED,c=5/64/64,a=59,i=0,q=1024,p=@6187c71b[inUse=59,size=64,max=64,closed=false]],stale=false,idle=-1
	at org.eclipse.jetty.client.HttpDestination.send(HttpDestination.java:332)
	at org.eclipse.jetty.client.HttpDestination.send(HttpDestination.java:304)
	at org.eclipse.jetty.client.HttpClient.send(HttpClient.java:591)
	at org.eclipse.jetty.client.HttpRequest.sendAsync(HttpRequest.java:819)
	at org.eclipse.jetty.client.HttpRequest.send(HttpRequest.java:807)
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor.send(ResponseListenerProcessor.java:169)
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor.onRequest(ResponseListenerProcessor.java:155)
	at org.eclipse.jetty.reactive.client.internal.AbstractSinglePublisher.request(AbstractSinglePublisher.java:90)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onSubscribe(MonoIgnoreThen.java:134)
	at org.eclipse.jetty.reactive.client.internal.AbstractSinglePublisher.subscribe(AbstractSinglePublisher.java:62)
	at reactor.core.publisher.MonoSource.subscribe(MonoSource.java:71)

May I ask how to bump this number from 1024 to something higher, how to configure this number please?

Thank you

Add support of multipart form data

Provide utility method
Request.Content.fromParts(Publisher<Part>)

where Part

public interface Part {

	/**
	 * Return the name of the part in the multipart form.
	 * @return the name of the part, never {@code null} or empty
	 */
	String name();

	/**
	 * Return the headers associated with the part.
	 */
	HttpHeaders headers();

	/**
	 * Return the content for this part.
	 * <p>Note that for a {@link FormFieldPart} the content may be accessed
	 * more easily via {@link FormFieldPart#value()}.
	 */
	Flux<DataBuffer> content();

	/**
	 * Return a mono that, when subscribed to, deletes the underlying storage
	 * for this part.
	 * @since 5.3.13
	 */
	default Mono<Void> delete() {
		return Mono.empty();
	}

}

Upgrade to 4.0.2 causes timeouts in Spring Framework's WebClient

Upgrading to jetty-reactive-httpclient:4.0.2 causes timeouts in Spring Framework's WebClient when using Jetty's Reactive HTTP Client. We believe that this might be caused by the changes done as part of #323 or #194.

To reproduce the timeouts, use this branch of Spring framework: https://github.com/poutsma/spring-framework/tree/gh-31931 (the main branch is kept on 4.0.1 until this is resolved), and run the org.springframework.web.reactive.function.client.WebClientIntegrationTests in the spring-webflux module. All tests in this class result in timeouts when using JettyClientHttpConnector, while other connectors (Reactor Netty, JDK HttpClient, Apache HTTP Components) run fine.

Note that the WebClientIntegrationTests do not connect to a "real" web server, but use MockWebServer from OkHttp 3 instead. Using the WebClient with the JettyClientHttpConnector to connect to a real host, such as http://example.com, seems to work fine. So this could very well be an issue in MockWebServer, if it wasn't for the fact that other connectors work fine, and that we have not made any recent changes in the code that uses the Jetty's reactive HTTP client.

Additionally, it could very well be that we are using Jetty's reactive HTTP client in an incorrect manner. If so, please suggest any changes we can make. All relevant code is in JettyClientHttpConnector, JettyClientHttpRequest and JettyClientHttpResponse in the org.springframework.http.client.reactive package of the spring-webflux module.

Metrics integration for Jetty reactive httpclient

Hello team,

This is my first post in this repo, if not anything else, just wanted to say big thanks for this cool project.

Just wanted to reach out asking for a possible feature / enhancement request.

Would it be possible to add metrics integration to this project? (For instance, micrometer)

For example, the netty client (just taking as an example, not saying it is the correct way) offers this construct :

public reactor.netty.http.client.HttpClient getHttpClient() {
        return reactor.netty.http.client.HttpClient
                .create()
                .metrics(true, Function.identity())  // <-- this is super cool
                .wiretap(true)
                .protocol(HttpProtocol.HTTP11)
                ;
    }

With this, out of the box, one would get very useful metrics such as:

reactor_netty_bytebuf_allocator_active_direct_memory
reactor_netty_bytebuf_allocator_active_heap_memory
reactor_netty_bytebuf_allocator_chunk_size
reactor_netty_bytebuf_allocator_direct_arenas
reactor_netty_bytebuf_allocator_heap_arenas
reactor_netty_bytebuf_allocator_normal_cache_size
reactor_netty_bytebuf_allocator_small_cache_size
reactor_netty_bytebuf_allocator_threadlocal_caches
reactor_netty_bytebuf_allocator_used_direct_memory
reactor_netty_bytebuf_allocator_used_heap_memory
reactor_netty_connection_provider_active_connections
reactor_netty_connection_provider_idle_connections
reactor_netty_connection_provider_max_connections
reactor_netty_connection_provider_max_pending_connections
reactor_netty_connection_provider_pending_connections
reactor_netty_connection_provider_total_connections
reactor_netty_eventloop_pending_tasks
reactor_netty_http_client_address_resolver
reactor_netty_http_client_connect_time
reactor_netty_http_client_data_received
reactor_netty_http_client_data_received_time
reactor_netty_http_client_data_sent
reactor_netty_http_client_data_sent_time
reactor_netty_http_client_response_time
reactor_netty_http_client_tls_handshake_time

Would it be possible for this repo to offer a likewise feature, please?
It will make this project much more production-ready with greater observability.

Thank you so much!

h2c fails on 9.4.23.v20191118 Jetty Server and 1.1.0-SNAPSHOT jetty-reactive-httpclient

After upgrade of ReactiveFeign Jetty-based implementation to 9.4.23.v20191118 Jetty Server and 1.1.0-SNAPSHOT jetty-reactive-httpclient H2c test began to fail. H1 version works correctly.

Test just tries to run 500 queries in parallel.
I get a lot of this warnings on server side
[qtp1378612814-18] WARN org.eclipse.jetty.server.HttpOutput - java.io.IOException: Closed while Pending/Unready
And then on client
java.io.IOException: cancel_stream_error

Last stable version that this test was passing is 9.4.14 and 1.0.2 jetty-reactive-httpclient

Here is the link to this test AllFeaturesTest
To run it locally you need to have 1.1.0-SNAPSHOT jetty-reactive-httpclient in local maven repository.

Post request not setting Auth Header Correctly When Using Digest Auth

Version: 1.1.9

When trying to upload a file to remote server with Digest Auth. After receiving the challenge request doesn't set correct auth headers. This causes the request to fail with 401. After some debugging found that in AuthenticationProtocolHandler.AuthenticationListener the check !requestContent.isReproducible() is incorrectly calling forwardSuccessComplete before updating headers and trying again.

  ContentProvider requestContent = request.getContent();
  if (requestContent != null && !requestContent.isReproducible())
  {
      if (LOG.isDebugEnabled())
          LOG.debug("Request content not reproducible for {}", request);
      forwardSuccessComplete(request, response);
      return;
  }

  try
  {
      Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
      if (LOG.isDebugEnabled())
          LOG.debug("Authentication result {}", authnResult);

One approach I can think of is we can have a variant of PublisherContent where isReproducible() returns true, idk this might be a bit contradicting reactive stream there might be a better/more correct approach to handle this.

Is there a workaround for this? My current work around using jetty send(<BufferedResponseListener>) approach.

Timeout issue

The timeout and idle timeout don't terminate the stream.

If I take the RxJava2 example from the readme, and add timeouts and a doOnSuccess / doOnError listener like this:

	public static void main(String[] args) throws Exception {
		// Create and start Jetty's HttpClient.
		HttpClient httpClient = new HttpClient();
		httpClient.start();

		// Create a request using the HttpClient APIs.
		Request request = httpClient.newRequest("http://localhost:1234/path");
		request = request.idleTimeout(3000, TimeUnit.MILLISECONDS)
			.timeout(5000, TimeUnit.MILLISECONDS);
		
		// Wrap the request using the API provided by this project.
		ReactiveRequest reactiveRequest = ReactiveRequest.newBuilder(request).build();

		// Obtain a ReactiveStreams Publisher for the response, discarding the response content.
		Publisher<ReactiveResponse> publisher = reactiveRequest.response(ReactiveResponse.Content.discard());

		// Wrap the ReactiveStreams Publisher with RxJava.
		int status = Single.fromPublisher(publisher)
		        .map(ReactiveResponse::getStatus)
		        .doOnSuccess(s->System.err.println("Completed!"))
		        .doOnError(e->e.printStackTrace())
		        .blockingGet();				
		System.err.println("status: "+status);
	}

And I point it at a 'hanging socket':

Using

nc -l 1234

... it will close the connection after the timeout, but it won't terminate the stream (neither doOnSuccess or doOnError gets called)

Granted, I could also add RxJava-level timeouts to the stream, but I still think this is a bug.

End of Community Support for 2.0.x & 3.0.x

The Jetty Reactive HttpClient project announces the End of Community Support for versions 2.0.x and 3.0.x, following Jetty's announcement of End of Community Support for Jetty 10.0.x and 11.0.x.

September 1st, 2024 marks the official End of Community Support for Jetty Reactive HttpClient 2.0.x & 3.0.x.

While users of Jetty Reactive HttpClient 2.0.x & 3.0.x will continue to see releases for security and critical issues until Jetty Reactive HttpClient 2.0.x & 3.0.x go End of Life, the only on-demand support provided will be for Webtide customers.

As for the official End of Life of Jetty Reactive HttpClient 2.0.x & 3.0.x, we will give at least 6 months notice after the last Webtide customer has transitioned off of using Jetty 10.0.x & 11.0.x. We can’t provide a firm date, but it is unlikely to happen before 2025.

Thank you for your continued usage and support of Jetty Reactive HttpClient.
Please take a moment to look at the newer Jetty Reactive HttpClient release 4.0.x.

Action Before September 1st, 2024 End of Community Support (September 1st, 2024) End of Life (Date TBD)
Community PRs reviewed and integrated ✔️
Webtide Customer PRs reviewed and integrated ✔️ ✔️
Community triggered releases ✔️
Webtide Customer triggered releases ✔️ ✔️
Security / Vulnerability triggered releases ✔️ ✔️

Feedback

What Reactive HttpClient version do you use?

/polls "Reactive HttpClient 1.1.x" "Reactive HttpClient 2.0.x" "Reactive HttpClient 3.0.x"

Using Callback in ReactiveRequest's ContentChunk to release a pooled ByteBuffer

Greetings,

I would like to pool ByteBuffers used in ReactiveRequests. I've chosen ArrayByteBufferPool as the ByteBufferPool implementation.

I am releasing the ByteBuffer in ContentChunk's callback like so

new ContentChunk(byteBuffer, new Callback() {

    @Override
    public void succeeded() {
        SomeClass.this.byteBufferPool.release(byteBuffer);
    }

    @Override
    public void failed(Throwable x) {
        SomeClass.this.byteBufferPool.release(byteBuffer);
    }
})

I am not yet very familiar with Jetty client code base but the approach outlined above seems to work.

Unfortunately, the example in README.md does not showcase the pooled scenario. It this the correct way to release ByteBuffer consumed by ReactiveRequest?

Thank you.

Concern about code that guards against multiple Subscribers in AbstractSinglePublisher

Hi,
I noticed in the code of AbstractSinglePublisher.subscribe(Subscriber) that the publisher is passing itself as a Subscription to incoming Subscribers. This publisher obviously guards against multiple subscriptions, only allowing one Subscriber at a time. Extra subscribers are rejected with an onError signal, which is good.

The trouble is that in that guarding logic, even if a second Subscriber is rejected it will receive the AbstractSinglePublisher instance as its Subscription (subscriber.doOnSubscribe(this))...

My concern is that the extraneous Subscriber could make use of this ("shared") Subscription (e.g. perform a request), which would lead to potentially corrupted state for the publisher and by extension the legit Subscriber.

Note that the Reactive Streams specification does mention that a Subscription-Subscriber pair should be unique, even though there is no numbered rule to refer to. See the note at the end of the Subscription section (right above that link):

A Subscription is shared by exactly one Publisher and one Subscriber for the purpose of mediating the data exchange between this pair.

When using jetty-reactive-httpclient, calling retry on WebClient.exchange throws CancellationException

When using the jetty-reactive-httpclient client connector with WebClient, whenever we call retry after WebClient.exchange (to retry the HTTP request), a CancellationException is thrown instead of the request being retried. When using reactor-netty client, the WebClient HTTP request is retried as expected.

It is unclear to us whether this is a Spring issue, or one with Reactor or Jetty. Please feel free to redirect us to the correct project.

Versions:
Spring up to 5.1.7.RELEASE
Spring Boot up to 2.1.4.RELEASE
Jetty 9.4.18.v20190429
Reactor 3.2.8.RELEASE

See this sample repository for a full demonstration of the problem: https://github.com/scottjohnson/webclient-retry-repro

In part:

webClient.get()
                .uri("https://postman-echo.com/status/404")
                .exchange()
                .flatMap(cr -> {
                    if (!cr.statusCode().is2xxSuccessful()) {
                        throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "Didn't get a 200, retrying...");
                    }
                    else return cr.bodyToMono(String.class);
                })
                .retry(2);

We would expect this code to retry the requested URI two more times. This happens as expected when using the reactor-netty client.

When using jetty-reactive-http-client, the original request is made, but on retry instead of retrying the following exception is thrown:

java.util.concurrent.CancellationException: null
	at org.eclipse.jetty.reactive.client.internal.AbstractSinglePublisher.subscribe(AbstractSinglePublisher.java:54) ~[jetty-reactive-httpclient-1.0.3.jar:na]
	at reactor.core.publisher.MonoFromPublisher.subscribe(MonoFromPublisher.java:43) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.Mono.subscribe(Mono.java:3710) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxRetry$RetrySubscriber.resubscribe(FluxRetry.java:109) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxRetry$RetrySubscriber.onError(FluxRetry.java:93) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:122) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:204) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:204) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen$ThenAcceptInner.onNext(MonoIgnoreThen.java:296) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at org.eclipse.jetty.reactive.client.internal.AbstractSingleProcessor.downStreamOnNext(AbstractSingleProcessor.java:110) ~[jetty-reactive-httpclient-1.0.3.jar:na]
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerPublisher.onNext(ResponseListenerPublisher.java:130) ~[jetty-reactive-httpclient-1.0.3.jar:na]
	at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2070) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.StrictSubscriber.onSubscribe(StrictSubscriber.java:77) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at reactor.core.publisher.Mono.subscribe(Mono.java:3710) ~[reactor-core-3.2.8.RELEASE.jar:3.2.8.RELEASE]
	at org.eclipse.jetty.reactive.client.internal.ResponseListenerPublisher.onHeaders(ResponseListenerPublisher.java:72) ~[jetty-reactive-httpclient-1.0.3.jar:na]
	at org.eclipse.jetty.client.ResponseNotifier.notifyHeaders(ResponseNotifier.java:98) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.ResponseNotifier.notifyHeaders(ResponseNotifier.java:90) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.HttpReceiver.responseHeaders(HttpReceiver.java:267) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.headerComplete(HttpReceiverOverHTTP.java:256) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.http.HttpParser.parseFields(HttpParser.java:1218) ~[jetty-http-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1502) ~[jetty-http-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.parse(HttpReceiverOverHTTP.java:172) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:135) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:73) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:133) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:155) ~[jetty-client-9.4.18.v20190429.jar:9.4.18.v20190429]
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:427) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:321) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:159) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117) ~[jetty-io-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683) ~[jetty-util-9.4.15.v20190215.jar:9.4.15.v20190215]
	at java.base/java.lang.Thread.run(Thread.java:844) ~[na:na]

Reopen Previous issue: java.util.concurrent.ExecutionException: org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: Authentication challenge without WWW-Authenticate header

Hi,

I opened an issue here:

#28
jetty/jetty.project#5829

I reported this to Okta in which they replied with teh following:


After investigating this internally I can now provide an answer, Okta does not reply with the www-authenticate header because it does not perform basic authentication, the header tells the client that the user must authenticate but Okta provides that information in the response body: https://developer.okta.com/docs/reference/api/authn/#response-parameters.

In this case, I would recommend submitting this as a New Feature Request, under our Support Portal's Ideas section https://support.okta.com/help/s/ideas.
Features suggested in our community are reviewed and can be voted and commented on by other members of the community, therefore making it much easier for our Product Management Team to prioritize our customers’ needs.

For reference: https://www.okta.com/blog/tag/authentication-methods.


From what got from Okta is that this header is only required when using basic authentication. Most corporate companies are using Okta today, hence, this header restriction/enforcement shouldn't be there unless this restriction doesn't only apply to basic authentication but all methods?

If what Okta is saying is true Is there a way this can be turned off when not using basic authentication?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.