Skip to content

Commit 5c6c8e4

Browse files
committed
Fixes #12652 - Jetty Reactive client hangs for HTTP 401 responses.
Fixed ResponseListeners.emitEvents() to emit the "contentSource" event in all cases. Signed-off-by: Simone Bordet <[email protected]>
1 parent 8667119 commit 5c6c8e4

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/ContentDecoder.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
package org.eclipse.jetty.client;
1515

16+
import java.io.IOException;
1617
import java.nio.ByteBuffer;
1718
import java.util.Iterator;
1819
import java.util.LinkedHashMap;
@@ -21,6 +22,7 @@
2122
import org.eclipse.jetty.http.HttpField;
2223
import org.eclipse.jetty.http.HttpHeader;
2324
import org.eclipse.jetty.io.RetainableByteBuffer;
25+
import org.eclipse.jetty.util.component.Dumpable;
2426

2527
/**
2628
* {@link ContentDecoder} decodes content bytes of a response.
@@ -109,7 +111,7 @@ public int hashCode()
109111
public abstract ContentDecoder newContentDecoder();
110112
}
111113

112-
public static class Factories implements Iterable<ContentDecoder.Factory>
114+
public static class Factories implements Iterable<ContentDecoder.Factory>, Dumpable
113115
{
114116
private final Map<String, Factory> factories = new LinkedHashMap<>();
115117
private HttpField acceptEncodingField;
@@ -138,5 +140,17 @@ public Factory put(Factory factory)
138140
acceptEncodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value);
139141
return result;
140142
}
143+
144+
@Override
145+
public String dump()
146+
{
147+
return Dumpable.dump(this);
148+
}
149+
150+
@Override
151+
public void dump(Appendable out, String indent) throws IOException
152+
{
153+
Dumpable.dumpObjects(out, indent, this, factories);
154+
}
141155
}
142156
}

jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/ResponseListeners.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.eclipse.jetty.http.HttpField;
2828
import org.eclipse.jetty.io.Content;
2929
import org.eclipse.jetty.io.content.ByteBufferContentSource;
30+
import org.eclipse.jetty.util.BufferUtil;
3031
import org.eclipse.jetty.util.ExceptionUtil;
3132
import org.eclipse.jetty.util.thread.AutoLock;
3233
import org.eclipse.jetty.util.thread.Invocable;
@@ -392,15 +393,14 @@ private void emitEvents(Response response)
392393
iterator.remove();
393394
}
394395
notifyHeaders(headersListener, response);
396+
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
395397
if (response instanceof ContentResponse contentResponse)
396398
{
397-
byte[] content = contentResponse.getContent();
398-
if (content != null && content.length > 0)
399-
{
400-
ByteBufferContentSource byteBufferContentSource = new ByteBufferContentSource(ByteBuffer.wrap(content));
401-
notifyContentSource(contentSourceListener, response, byteBufferContentSource);
402-
}
399+
byte[] bytes = contentResponse.getContent();
400+
if (bytes != null && bytes.length > 0)
401+
content = ByteBuffer.wrap(bytes);
403402
}
403+
notifyContentSource(contentSourceListener, response, new ByteBufferContentSource(content));
404404
}
405405

406406
public void emitSuccess(Response response)

jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/transport/ResponseListenersTest.java

+51
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@
1515

1616
import java.io.Closeable;
1717
import java.nio.ByteBuffer;
18+
import java.util.ArrayList;
1819
import java.util.Arrays;
1920
import java.util.List;
2021
import java.util.concurrent.CopyOnWriteArrayList;
2122
import java.util.concurrent.TimeoutException;
2223

2324
import org.eclipse.jetty.client.Response;
25+
import org.eclipse.jetty.client.internal.HttpContentResponse;
26+
import org.eclipse.jetty.http.HttpField;
27+
import org.eclipse.jetty.http.HttpFields;
2428
import org.eclipse.jetty.io.Content;
2529
import org.eclipse.jetty.io.content.ChunksContentSource;
30+
import org.eclipse.jetty.util.BufferUtil;
2631
import org.junit.jupiter.api.Test;
2732

2833
import static org.hamcrest.MatcherAssert.assertThat;
@@ -227,6 +232,52 @@ public void run()
227232
contentSource.close();
228233
}
229234

235+
@Test
236+
public void testEmitEventsInvokesContentSourceListenerForNoContent()
237+
{
238+
ResponseListeners responseListeners = new ResponseListeners();
239+
List<String> events = new ArrayList<>();
240+
responseListeners.addListener(new Response.Listener()
241+
{
242+
@Override
243+
public void onBegin(Response response)
244+
{
245+
events.add("BEGIN");
246+
}
247+
248+
@Override
249+
public boolean onHeader(Response response, HttpField field)
250+
{
251+
return events.add("HEADER");
252+
}
253+
254+
@Override
255+
public void onHeaders(Response response)
256+
{
257+
events.add("HEADERS");
258+
}
259+
260+
@Override
261+
public void onContentSource(Response response, Content.Source contentSource)
262+
{
263+
events.add("CONTENT-SOURCE");
264+
}
265+
266+
@Override
267+
public void onSuccess(Response response)
268+
{
269+
events.add("SUCCESS");
270+
}
271+
});
272+
273+
Response response = new HttpResponse(null).addHeader(HttpFields.CONTENT_LENGTH_0);
274+
Response contentResponse = new HttpContentResponse(response, BufferUtil.EMPTY_BYTES, null, null);
275+
responseListeners.emitSuccess(contentResponse);
276+
277+
List<String> expected = List.of("BEGIN", "HEADER", "HEADERS", "CONTENT-SOURCE", "SUCCESS");
278+
assertThat(events, is(expected));
279+
}
280+
230281
private static class TestSource extends ChunksContentSource implements Closeable
231282
{
232283
private Content.Chunk[] chunks;

0 commit comments

Comments
 (0)