|
40 | 40 | import org.eclipse.jetty.client.Origin;
|
41 | 41 | import org.eclipse.jetty.client.Response;
|
42 | 42 | import org.eclipse.jetty.client.Result;
|
| 43 | +import org.eclipse.jetty.http.HttpFields; |
43 | 44 | import org.eclipse.jetty.http.HttpHeader;
|
44 | 45 | import org.eclipse.jetty.http.HttpMethod;
|
45 | 46 | import org.eclipse.jetty.http.HttpStatus;
|
|
68 | 69 | import static org.hamcrest.MatcherAssert.assertThat;
|
69 | 70 | import static org.hamcrest.Matchers.containsString;
|
70 | 71 | import static org.hamcrest.Matchers.is;
|
| 72 | +import static org.hamcrest.Matchers.nullValue; |
71 | 73 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
72 | 74 | import static org.junit.jupiter.api.Assertions.assertEquals;
|
73 | 75 | import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
79 | 81 |
|
80 | 82 | public class HttpClientTest extends AbstractTest
|
81 | 83 | {
|
| 84 | + @ParameterizedTest |
| 85 | + @MethodSource("transports") |
| 86 | + public void testClientUseContentSourceInSpawnedThreadEmptyResponseContent(Transport transport) throws Exception |
| 87 | + { |
| 88 | + start(transport, new Handler.Abstract() |
| 89 | + { |
| 90 | + @Override |
| 91 | + public boolean handle(Request request, org.eclipse.jetty.server.Response response, Callback callback) |
| 92 | + { |
| 93 | + response.write(true, BufferUtil.EMPTY_BUFFER, callback); |
| 94 | + return true; |
| 95 | + } |
| 96 | + }); |
| 97 | + |
| 98 | + var listener = new OnContentSourceListener(); |
| 99 | + |
| 100 | + client.newRequest(newURI(transport)) |
| 101 | + .method("POST") |
| 102 | + .send(listener); |
| 103 | + |
| 104 | + OnContentSourceListener.ClientResponseContent clientResponseContent = listener.clientResponseContent.get(5, TimeUnit.SECONDS); |
| 105 | + assertThat(clientResponseContent.body(), is("")); |
| 106 | + assertThat(clientResponseContent.status(), is(200)); |
| 107 | + assertThat(clientResponseContent.trailers(), nullValue()); |
| 108 | + } |
| 109 | + |
| 110 | + @ParameterizedTest |
| 111 | + @MethodSource("transports") |
| 112 | + public void testClientUseContentSourceInSpawnedThreadWithResponseContent(Transport transport) throws Exception |
| 113 | + { |
| 114 | + start(transport, new Handler.Abstract() |
| 115 | + { |
| 116 | + @Override |
| 117 | + public boolean handle(Request request, org.eclipse.jetty.server.Response response, Callback callback) |
| 118 | + { |
| 119 | + response.write(true, BufferUtil.toBuffer("some response content", StandardCharsets.UTF_8), callback); |
| 120 | + return true; |
| 121 | + } |
| 122 | + }); |
| 123 | + |
| 124 | + var listener = new OnContentSourceListener(); |
| 125 | + |
| 126 | + client.newRequest(newURI(transport)) |
| 127 | + .method("POST") |
| 128 | + .send(listener); |
| 129 | + |
| 130 | + OnContentSourceListener.ClientResponseContent clientResponseContent = listener.clientResponseContent.get(5, TimeUnit.SECONDS); |
| 131 | + assertThat(clientResponseContent.body(), is("some response content")); |
| 132 | + assertThat(clientResponseContent.status(), is(200)); |
| 133 | + assertThat(clientResponseContent.trailers(), nullValue()); |
| 134 | + } |
| 135 | + |
| 136 | + @ParameterizedTest |
| 137 | + @MethodSource("transportsNoFCGI") |
| 138 | + public void testClientUseContentSourceInSpawnedThreadWithTrailer(Transport transport) throws Exception |
| 139 | + { |
| 140 | + start(transport, new Handler.Abstract() |
| 141 | + { |
| 142 | + @Override |
| 143 | + public boolean handle(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws IOException |
| 144 | + { |
| 145 | + response.setTrailersSupplier(() -> HttpFields.build().add("X-Trailer-test", "foobar")); |
| 146 | + // start chunked mode |
| 147 | + try (Blocker.Callback blocker = Blocker.callback()) |
| 148 | + { |
| 149 | + response.write(false, BufferUtil.EMPTY_BUFFER, blocker); |
| 150 | + blocker.block(); |
| 151 | + } |
| 152 | + |
| 153 | + response.write(true, BufferUtil.toBuffer("some response content", StandardCharsets.UTF_8), callback); |
| 154 | + return true; |
| 155 | + } |
| 156 | + }); |
| 157 | + |
| 158 | + var listener = new OnContentSourceListener(); |
| 159 | + |
| 160 | + client.newRequest(newURI(transport)) |
| 161 | + .method("POST") |
| 162 | + .send(listener); |
| 163 | + |
| 164 | + OnContentSourceListener.ClientResponseContent clientResponseContent = listener.clientResponseContent.get(5, TimeUnit.SECONDS); |
| 165 | + assertThat(clientResponseContent.body(), is("some response content")); |
| 166 | + assertThat(clientResponseContent.status(), is(200)); |
| 167 | + assertThat(clientResponseContent.trailers().get("X-Trailer-test"), is("foobar")); |
| 168 | + } |
| 169 | + |
82 | 170 | @ParameterizedTest
|
83 | 171 | @MethodSource("transports")
|
84 | 172 | public void testRequestWithoutResponseContent(Transport transport) throws Exception
|
@@ -1155,4 +1243,45 @@ public boolean await(long timeout, TimeUnit unit) throws InterruptedException
|
1155 | 1243 | return latch.await(timeout, unit);
|
1156 | 1244 | }
|
1157 | 1245 | }
|
| 1246 | + |
| 1247 | + private static class OnContentSourceListener implements Response.Listener |
| 1248 | + { |
| 1249 | + record ClientResponseContent(int status, String body, HttpFields trailers) |
| 1250 | + { |
| 1251 | + } |
| 1252 | + |
| 1253 | + final CompletableFuture<ClientResponseContent> clientResponseContent = new CompletableFuture<>(); |
| 1254 | + final StringBuffer buffer = new StringBuffer(); |
| 1255 | + |
| 1256 | + @Override |
| 1257 | + public void onContentSource(Response response, Content.Source contentSource) |
| 1258 | + { |
| 1259 | + new Thread(() -> |
| 1260 | + { |
| 1261 | + Content.Chunk chunk = contentSource.read(); |
| 1262 | + if (chunk == null) |
| 1263 | + { |
| 1264 | + contentSource.demand(() -> onContentSource(response, contentSource)); |
| 1265 | + return; |
| 1266 | + } |
| 1267 | + |
| 1268 | + buffer.append(BufferUtil.toString(chunk.getByteBuffer(), StandardCharsets.UTF_8)); |
| 1269 | + chunk.release(); |
| 1270 | + |
| 1271 | + if (!chunk.isLast()) |
| 1272 | + { |
| 1273 | + contentSource.demand(() -> onContentSource(response, contentSource)); |
| 1274 | + } |
| 1275 | + else |
| 1276 | + { |
| 1277 | + Content.Chunk afterLastChunk = contentSource.read(); |
| 1278 | + if (afterLastChunk != chunk) |
| 1279 | + clientResponseContent.completeExceptionally(new AssertionError("afterLastChunk != chunk")); |
| 1280 | + else |
| 1281 | + clientResponseContent.complete(new ClientResponseContent(response.getStatus(), buffer.toString(), response.getTrailers())); |
| 1282 | + } |
| 1283 | + } |
| 1284 | + ).start(); |
| 1285 | + } |
| 1286 | + } |
1158 | 1287 | }
|
0 commit comments