20
20
import static com .github .tomakehurst .wiremock .client .WireMock .any ;
21
21
import static com .github .tomakehurst .wiremock .client .WireMock .containing ;
22
22
import static com .github .tomakehurst .wiremock .client .WireMock .equalTo ;
23
+ import static com .github .tomakehurst .wiremock .client .WireMock .getRequestedFor ;
23
24
import static com .github .tomakehurst .wiremock .client .WireMock .postRequestedFor ;
24
25
import static com .github .tomakehurst .wiremock .client .WireMock .urlEqualTo ;
25
26
import static com .github .tomakehurst .wiremock .client .WireMock .urlMatching ;
26
27
import static com .github .tomakehurst .wiremock .client .WireMock .urlPathEqualTo ;
27
28
import static com .github .tomakehurst .wiremock .core .WireMockConfiguration .wireMockConfig ;
28
29
import static org .assertj .core .api .Assertions .assertThat ;
29
30
import static org .assertj .core .api .Assertions .assertThatThrownBy ;
31
+ import static org .assertj .core .api .AssertionsForClassTypes .assertThatCode ;
30
32
31
33
import com .github .tomakehurst .wiremock .WireMockServer ;
32
34
import com .github .tomakehurst .wiremock .client .ResponseDefinitionBuilder ;
@@ -236,6 +238,147 @@ public void doesNotRetryOn429StatusCode() throws Exception {
236
238
}
237
239
}
238
240
241
+ @ Test
242
+ public void handlesNoContentResponse () throws Exception {
243
+ SdkHttpClient client = createSdkHttpClient ();
244
+
245
+ mockServer .stubFor (any (urlPathEqualTo ("/no-content" ))
246
+ .willReturn (aResponse ()
247
+ .withStatus (204 )));
248
+
249
+ SdkHttpFullRequest req = mockSdkRequest ("http://localhost:" + mockServer .port () + "/no-content" ,
250
+ SdkHttpMethod .DELETE );
251
+ HttpExecuteResponse rsp = client .prepareRequest (HttpExecuteRequest .builder ()
252
+ .request (req )
253
+ .build ())
254
+ .call ();
255
+
256
+ assertThat (rsp .httpResponse ().statusCode ()).isEqualTo (204 );
257
+ assertThat (rsp .responseBody ()).isEmpty ();
258
+ }
259
+
260
+ @ Test
261
+ public void handlesLargeResponseBody () throws Exception {
262
+ SdkHttpClient client = createSdkHttpClient ();
263
+ // Create a large response body (1MB)
264
+ byte [] largeBody = new byte [1024 * 1024 ];
265
+ for (int i = 0 ; i < largeBody .length ; i ++) {
266
+ largeBody [i ] = (byte ) (i % 256 );
267
+ }
268
+ mockServer .stubFor (any (urlPathEqualTo ("/large" ))
269
+ .willReturn (aResponse ()
270
+ .withStatus (200 )
271
+ .withBody (largeBody )));
272
+
273
+ SdkHttpFullRequest req = mockSdkRequest ("http://localhost:" + mockServer .port () + "/large" , SdkHttpMethod .GET );
274
+ HttpExecuteResponse rsp = client .prepareRequest (HttpExecuteRequest .builder ()
275
+ .request (req )
276
+ .build ())
277
+ .call ();
278
+
279
+ assertThat (rsp .httpResponse ().statusCode ()).isEqualTo (200 );
280
+ assertThat (rsp .responseBody ()).isPresent ();
281
+
282
+ // Read the entire response and verify
283
+ byte [] readBuffer = IoUtils .toByteArray (rsp .responseBody ().get ());
284
+ assertThat (readBuffer ).isEqualTo (largeBody );
285
+ }
286
+
287
+ @ Test
288
+ public void testAbortResponseStream () throws Exception {
289
+ SdkHttpClient client = createSdkHttpClient ();
290
+
291
+ mockServer .stubFor (any (urlPathEqualTo ("/streaming" ))
292
+ .willReturn (aResponse ()
293
+ .withStatus (200 )
294
+ .withBody ("This is a streaming response that should be aborted" )));
295
+
296
+ SdkHttpFullRequest req = mockSdkRequest ("http://localhost:" + mockServer .port () + "/streaming" , SdkHttpMethod .POST );
297
+ ExecutableHttpRequest executableRequest =
298
+ client .prepareRequest (HttpExecuteRequest .builder ()
299
+ .request (req )
300
+ .contentStreamProvider (req .contentStreamProvider ().orElse (null ))
301
+ .build ());
302
+ HttpExecuteResponse rsp = executableRequest .call ();
303
+
304
+ assertThat (rsp .httpResponse ().statusCode ()).isEqualTo (200 );
305
+ assertThat (rsp .responseBody ()).isPresent ();
306
+
307
+ // Verify the stream is abortable
308
+ AbortableInputStream stream = rsp .responseBody ().get ();
309
+ assertThat (stream ).isInstanceOf (AbortableInputStream .class );
310
+
311
+ // Read a few bytes
312
+ byte [] buffer = new byte [10 ];
313
+ int bytesRead = stream .read (buffer );
314
+ assertThat (bytesRead ).isGreaterThan (0 );
315
+ assertThatCode (() -> stream .abort ()).doesNotThrowAnyException ();
316
+ stream .close ();
317
+ }
318
+
319
+ @ Test
320
+ public void handlesMultipleSequentialRequests () throws Exception {
321
+ SdkHttpClient client = createSdkHttpClient ();
322
+
323
+ mockServer .stubFor (any (urlPathEqualTo ("/sequential" ))
324
+ .willReturn (aResponse ()
325
+ .withStatus (200 )
326
+ .withBody ("Response body" )));
327
+
328
+ // Execute multiple requests sequentially
329
+ for (int i = 0 ; i < 5 ; i ++) {
330
+ SdkHttpFullRequest req = mockSdkRequest ("http://localhost:" + mockServer .port () + "/sequential" , SdkHttpMethod .GET );
331
+ HttpExecuteResponse rsp = client .prepareRequest (HttpExecuteRequest .builder ()
332
+ .request (req )
333
+ .build ())
334
+ .call ();
335
+
336
+ assertThat (rsp .httpResponse ().statusCode ()).isEqualTo (200 );
337
+ assertThat (IoUtils .toUtf8String (rsp .responseBody ().orElse (null ))).isEqualTo ("Response body" );
338
+ }
339
+ mockServer .verify (5 , getRequestedFor (urlEqualTo ("/sequential" )));
340
+ }
341
+
342
+ @ Test
343
+ public void handlesVariousContentLengths () throws Exception {
344
+ SdkHttpClient client = createSdkHttpClient ();
345
+ int [] contentLengths = {0 , 1 , 100 , 1024 , 65536 };
346
+
347
+ for (int length : contentLengths ) {
348
+ String path = "/content-length-" + length ;
349
+ byte [] body = new byte [length ];
350
+ for (int i = 0 ; i < length ; i ++) {
351
+ body [i ] = (byte ) ('A' + (i % 26 ));
352
+ }
353
+
354
+ mockServer .stubFor (any (urlPathEqualTo (path ))
355
+ .willReturn (aResponse ()
356
+ .withStatus (200 )
357
+ .withHeader ("Content-Length" , String .valueOf (length ))
358
+ .withBody (body )));
359
+
360
+ SdkHttpFullRequest req = mockSdkRequest ("http://localhost:" + mockServer .port () + path , SdkHttpMethod .GET );
361
+ HttpExecuteResponse rsp = client .prepareRequest (HttpExecuteRequest .builder ()
362
+ .request (req )
363
+ .build ())
364
+ .call ();
365
+
366
+ assertThat (rsp .httpResponse ().statusCode ()).isEqualTo (200 );
367
+
368
+ if (length == 0 ) {
369
+ // Empty body should still have a response body present, but EOF immediately
370
+ if (rsp .responseBody ().isPresent ()) {
371
+ assertThat (rsp .responseBody ().get ().read ()).isEqualTo (-1 );
372
+ }
373
+ } else {
374
+ assertThat (rsp .responseBody ()).isPresent ();
375
+ byte [] readBody = IoUtils .toByteArray (rsp .responseBody ().get ());
376
+ assertThat (readBody ).isEqualTo (body );
377
+ }
378
+ }
379
+ }
380
+
381
+
239
382
private void validateStatusCodeWithRetryCheck (SdkHttpClient client ,
240
383
int expectedStatusCode ,
241
384
int expectedRequestCount ) throws IOException {
@@ -294,15 +437,13 @@ protected void testForResponseCodeUsingHttps(SdkHttpClient client, int returnCod
294
437
.orElse (null ))
295
438
.build ())
296
439
.call ();
297
-
298
440
validateResponse (rsp , returnCode , sdkHttpMethod );
299
441
}
300
442
301
443
protected void stubForMockRequest (int returnCode ) {
302
444
ResponseDefinitionBuilder responseBuilder = aResponse ().withStatus (returnCode )
303
445
.withHeader ("Some-Header" , "With Value" )
304
446
.withBody ("hello" );
305
-
306
447
if (returnCode >= 300 && returnCode <= 399 ) {
307
448
responseBuilder .withHeader ("Location" , "Some New Location" );
308
449
}
@@ -316,7 +457,6 @@ private void validateResponse(HttpExecuteResponse response, int returnCode, SdkH
316
457
RequestPatternBuilder patternBuilder = RequestPatternBuilder .newRequestPattern (requestMethod , urlMatching ("/" ))
317
458
.withHeader ("Host" , containing ("localhost" ))
318
459
.withHeader ("User-Agent" , equalTo ("hello-world!" ));
319
-
320
460
if (method == SdkHttpMethod .HEAD ) {
321
461
patternBuilder .withRequestBody (absent ());
322
462
} else {
@@ -359,13 +499,23 @@ private static SdkHttpFullRequest.Builder mockSdkRequestBuilder(String uriString
359
499
return requestBuilder ;
360
500
}
361
501
362
-
363
502
protected SdkHttpFullRequest mockSdkRequest (String uriString , SdkHttpMethod method ) {
364
- SdkHttpFullRequest .Builder requestBuilder = mockSdkRequestBuilder (uriString , method );
365
- if (method != SdkHttpMethod .HEAD ) {
503
+ URI uri = URI .create (uriString );
504
+ SdkHttpFullRequest .Builder requestBuilder = SdkHttpFullRequest .builder ()
505
+ .uri (uri )
506
+ .method (method )
507
+ .putHeader ("Host" , uri .getHost ())
508
+ .putHeader ("User-Agent" , "hello-world!" );
509
+
510
+ // Only add body for methods that typically have a body
511
+ if (method != SdkHttpMethod .HEAD && method != SdkHttpMethod .GET && method != SdkHttpMethod .DELETE ) {
366
512
byte [] content = "Body" .getBytes (StandardCharsets .UTF_8 );
367
513
requestBuilder .putHeader ("Content-Length" , Integer .toString (content .length ));
368
514
requestBuilder .contentStreamProvider (() -> new ByteArrayInputStream (content ));
515
+ } else if (method == SdkHttpMethod .GET || method == SdkHttpMethod .DELETE ) {
516
+ // For GET and DELETE, explicitly set Content-Length to 0 or don't set it at all
517
+ // Some clients like AWS CRT are strict about this
518
+ requestBuilder .contentStreamProvider (() -> new ByteArrayInputStream (new byte [0 ]));
369
519
}
370
520
371
521
return requestBuilder .build ();
0 commit comments