1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 package software.amazon.awssdk.crt.test; 6 7 import org.junit.Assert; 8 import software.amazon.awssdk.crt.CRT; 9 import software.amazon.awssdk.crt.CrtResource; 10 import software.amazon.awssdk.crt.http.HttpClientConnection; 11 import software.amazon.awssdk.crt.http.HttpClientConnectionManager; 12 import software.amazon.awssdk.crt.http.HttpHeader; 13 import software.amazon.awssdk.crt.http.HttpRequestBase; 14 import software.amazon.awssdk.crt.http.HttpStream; 15 import software.amazon.awssdk.crt.http.HttpStreamBase; 16 import software.amazon.awssdk.crt.http.HttpStreamBaseResponseHandler; 17 import software.amazon.awssdk.crt.http.HttpStreamMetrics; 18 import software.amazon.awssdk.crt.http.HttpVersion; 19 20 import java.net.URI; 21 import java.nio.ByteBuffer; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.List; 25 import java.nio.charset.Charset; 26 import java.nio.charset.StandardCharsets; 27 import java.security.MessageDigest; 28 import java.security.NoSuchAlgorithmException; 29 import java.util.concurrent.CompletableFuture; 30 import java.util.concurrent.TimeUnit; 31 import java.util.concurrent.atomic.AtomicReference; 32 33 public class HttpRequestResponseFixture extends HttpClientTestFixture { 34 35 protected final static Charset UTF8 = StandardCharsets.UTF_8; 36 protected final String EMPTY_BODY = ""; 37 protected final static String TEST_DOC_LINE = "This is a sample to prove that http downloads and uploads work. It doesn't really matter what's in here, we mainly just need to verify the downloads and uploads work."; 38 protected final static String TEST_DOC_SHA256 = "C7FDB5314B9742467B16BD5EA2F8012190B5E2C44A005F7984F89AAB58219534"; 39 40 protected class TestHttpResponse { 41 int statusCode = -1; 42 int blockType = -1; 43 List<HttpHeader> headers = new ArrayList<>(); 44 ByteBuffer bodyBuffer = ByteBuffer.wrap(new byte[16 * 1024 * 1024]); // Allow up to 16 MB Responses 45 int onCompleteErrorCode = -1; 46 getBody()47 public String getBody() { 48 bodyBuffer.flip(); 49 return UTF8.decode(bodyBuffer).toString(); 50 } 51 52 @Override toString()53 public String toString() { 54 StringBuilder builder = new StringBuilder(); 55 builder.append("Status: " + statusCode); 56 int i = 0; 57 for (HttpHeader h : headers) { 58 builder.append("\nHeader[" + i + "]: " + h.toString()); 59 } 60 61 builder.append("\nBody:\n"); 62 builder.append(getBody()); 63 64 return builder.toString(); 65 } 66 } 67 shouldRetry(TestHttpResponse response)68 protected boolean shouldRetry(TestHttpResponse response) { 69 // Retry if we couldn't connect or if we got flaky 5xx server errors 70 if (response.onCompleteErrorCode != CRT.AWS_CRT_SUCCESS || response.statusCode == 503 || response.statusCode == 504) { 71 return true; 72 } 73 return false; 74 } 75 byteArrayToHex(byte[] input)76 public static String byteArrayToHex(byte[] input) { 77 StringBuilder output = new StringBuilder(input.length * 2); 78 for (byte b : input) { 79 output.append(String.format("%02X", b)); 80 } 81 return output.toString(); 82 } 83 calculateBodyHash(ByteBuffer bodyBuffer)84 public String calculateBodyHash(ByteBuffer bodyBuffer) throws NoSuchAlgorithmException { 85 MessageDigest digest = MessageDigest.getInstance("SHA-256"); 86 digest.update(bodyBuffer); 87 return byteArrayToHex(digest.digest()); 88 } 89 getResponse(URI uri, HttpRequestBase request, byte[] chunkedData, HttpVersion expectedVersion)90 public TestHttpResponse getResponse(URI uri, HttpRequestBase request, byte[] chunkedData, 91 HttpVersion expectedVersion) throws Exception { 92 boolean actuallyConnected = false; 93 94 final CompletableFuture<Void> reqCompleted = new CompletableFuture<>(); 95 final AtomicReference<HttpStreamMetrics> metricsRef = new AtomicReference<>(null); 96 97 final TestHttpResponse response = new TestHttpResponse(); 98 99 CompletableFuture<Void> shutdownComplete = null; 100 101 try (HttpClientConnectionManager connPool = createConnectionPoolManager(uri, expectedVersion)) { 102 shutdownComplete = connPool.getShutdownCompleteFuture(); 103 try (HttpClientConnection conn = connPool.acquireConnection().get(60, TimeUnit.SECONDS)) { 104 actuallyConnected = true; 105 HttpStreamBaseResponseHandler streamHandler = new HttpStreamBaseResponseHandler() { 106 @Override 107 public void onResponseHeaders(HttpStreamBase stream, int responseStatusCode, int blockType, 108 HttpHeader[] nextHeaders) { 109 response.statusCode = responseStatusCode; 110 Assert.assertEquals(responseStatusCode, stream.getResponseStatusCode()); 111 response.headers.addAll(Arrays.asList(nextHeaders)); 112 } 113 114 @Override 115 public void onResponseHeadersDone(HttpStreamBase stream, int blockType) { 116 response.blockType = blockType; 117 } 118 119 @Override 120 public int onResponseBody(HttpStreamBase stream, byte[] bodyBytesIn) { 121 response.bodyBuffer.put(bodyBytesIn); 122 int amountRead = bodyBytesIn.length; 123 124 // Slide the window open by the number of bytes just read 125 return amountRead; 126 } 127 128 @Override 129 public void onMetrics(HttpStreamBase stream, HttpStreamMetrics metrics) { 130 Assert.assertTrue(metricsRef.compareAndSet(null, metrics)); 131 } 132 133 @Override 134 public void onResponseComplete(HttpStreamBase stream, int errorCode) { 135 response.onCompleteErrorCode = errorCode; 136 reqCompleted.complete(null); 137 } 138 }; 139 HttpStreamBase stream = conn.makeRequest(request, streamHandler); 140 stream.activate(); 141 if (chunkedData != null) { 142 HttpStream h1_stream = (HttpStream)stream; 143 h1_stream.writeChunk(chunkedData, true).get(5, TimeUnit.SECONDS); 144 } 145 146 // Give the request up to 60 seconds to complete, otherwise throw a 147 // TimeoutException 148 reqCompleted.get(60, TimeUnit.SECONDS); 149 stream.close(); 150 } 151 } catch (Exception e) { 152 throw new RuntimeException(e); 153 } 154 155 Assert.assertTrue(actuallyConnected); 156 Assert.assertNotNull(metricsRef.get()); 157 158 shutdownComplete.get(60, TimeUnit.SECONDS); 159 160 CrtResource.waitForNoResources(); 161 162 return response; 163 164 } 165 166 } 167