1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 6 package software.amazon.awssdk.crt.http; 7 8 import java.util.concurrent.CompletableFuture; 9 import java.util.Map; 10 import java.util.HashMap; 11 12 import software.amazon.awssdk.crt.CRT; 13 import software.amazon.awssdk.crt.CrtResource; 14 import software.amazon.awssdk.crt.CrtRuntimeException; 15 import software.amazon.awssdk.crt.http.HttpStreamResponseHandler; 16 import software.amazon.awssdk.crt.http.HttpStream; 17 18 import static software.amazon.awssdk.crt.CRT.awsLastError; 19 20 /** 21 * This class wraps aws-c-http to provide the basic HTTP request/response functionality via the AWS Common Runtime. 22 * 23 * HttpClientConnection represents a single connection to a HTTP service endpoint. 24 * 25 * This class is not thread safe and should not be called from different threads. 26 */ 27 public class HttpClientConnection extends CrtResource { 28 HttpClientConnection(long connectionBinding)29 protected HttpClientConnection(long connectionBinding) { 30 acquireNativeHandle(connectionBinding); 31 } 32 33 /** 34 * Schedules an HttpRequest on the Native EventLoop for this HttpClientConnection specific to HTTP/1.1 connection. 35 * 36 * @param request The Request to make to the Server. 37 * @param streamHandler The Stream Handler to be called from the Native EventLoop 38 * @throws CrtRuntimeException if stream creation fails 39 * @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the 40 * request/response, but must be closed by the user thread making this request when it's done. 41 */ makeRequest(HttpRequest request, HttpStreamResponseHandler streamHandler)42 public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler streamHandler) 43 throws CrtRuntimeException { 44 if (isNull()) { 45 throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it."); 46 } 47 if (getVersion() == HttpVersion.HTTP_2) { 48 throw new IllegalArgumentException("HTTP/1 only method called on an HTTP/2 connection."); 49 } 50 HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(), 51 request.marshalForJni(), 52 request.getBodyStream(), 53 new HttpStreamResponseHandlerNativeAdapter(streamHandler)); 54 55 return (HttpStream)stream; 56 } 57 58 /** 59 * Schedules an HttpRequestBase on the Native EventLoop for this HttpClientConnection applies to both HTTP/2 and HTTP/1.1 connection. 60 * 61 * @param request The Request to make to the Server. 62 * @param streamHandler The Stream Handler to be called from the Native EventLoop 63 * @throws CrtRuntimeException if stream creation fails 64 * @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the 65 * request/response, but must be closed by the user thread making this request when it's done. 66 */ makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler)67 public HttpStreamBase makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler) throws CrtRuntimeException { 68 if (isNull()) { 69 throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it."); 70 } 71 HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(), 72 request.marshalForJni(), 73 request.getBodyStream(), 74 new HttpStreamResponseHandlerNativeAdapter(streamHandler)); 75 76 return stream; 77 } 78 79 /** 80 * Determines whether a resource releases its dependencies at the same time the native handle is released or if it waits. 81 * Resources that wait are responsible for calling releaseReferences() manually. 82 */ 83 @Override canReleaseReferencesImmediately()84 protected boolean canReleaseReferencesImmediately() { return true; } 85 86 /** 87 * Releases this HttpClientConnection back into the Connection Pool, and allows another Request to acquire this connection. 88 */ 89 @Override releaseNativeHandle()90 protected void releaseNativeHandle() { 91 if (!isNull()){ 92 httpClientConnectionReleaseManaged(getNativeHandle()); 93 } 94 } 95 96 /** 97 * Shuts down the underlying http connection. Even if this function is called, you still need to properly close 98 * the connection as well in order to release the native resources. 99 */ shutdown()100 public void shutdown() { 101 if (isNull()) { 102 throw new IllegalStateException("HttpClientConnection has been closed and released back to the pool, cannot shutdown the connection."); 103 } 104 httpClientConnectionShutdown(getNativeHandle()); 105 } 106 107 /** 108 * Check the underlying http connection is still open or not. 109 * 110 * @return true unless the underlying http connection is shutting down, or has been shutdown. 111 */ isOpen()112 public boolean isOpen() { 113 if (isNull()) { 114 throw new IllegalStateException("HttpClientConnection has been closed."); 115 } 116 return httpClientConnectionIsOpen(getNativeHandle()); 117 } 118 getVersion()119 public HttpVersion getVersion() { 120 if (isNull()) { 121 throw new IllegalStateException("HttpClientConnection has been closed."); 122 } 123 short version = httpClientConnectionGetVersion(getNativeHandle()); 124 return HttpVersion.getEnumValueFromInteger((int) version); 125 }; 126 127 /** Called from Native when a new connection is acquired **/ onConnectionAcquired(CompletableFuture<HttpClientConnection> acquireFuture, long nativeConnectionBinding, int errorCode)128 private static void onConnectionAcquired(CompletableFuture<HttpClientConnection> acquireFuture, long nativeConnectionBinding, int errorCode) { 129 if (errorCode != CRT.AWS_CRT_SUCCESS) { 130 acquireFuture.completeExceptionally(new HttpException(errorCode)); 131 return; 132 } 133 if (HttpVersion.getEnumValueFromInteger( 134 (int) httpClientConnectionGetVersion(nativeConnectionBinding)) == HttpVersion.HTTP_2) { 135 HttpClientConnection h2Conn = new Http2ClientConnection(nativeConnectionBinding); 136 if (!acquireFuture.complete(h2Conn)) { 137 // future was already completed/cancelled, return it immediately to the pool to not leak it 138 h2Conn.close(); 139 } 140 } else { 141 HttpClientConnection conn = new HttpClientConnection(nativeConnectionBinding); 142 if (!acquireFuture.complete(conn)) { 143 // future was already completed/cancelled, return it immediately to the pool to not leak it 144 conn.close(); 145 } 146 } 147 } 148 149 /** 150 * Certain exceptions thrown by this HTTP API are from invalid boundary conditions 151 * that, if the request isn't altered, will never succeed. This function returns 152 * false if the exception is caused by such a condition. 153 * <p> 154 * It does not mean the request that generated the error SHOULD be retried: 155 * only that as far as this client is concerned, the request might, 156 * possibly succeed with a subsequent attempt. 157 * 158 * @param exception, an exception thrown by the CRT HTTP API--for any reason. 159 * @return true if the error that generated the exception makes sense for a retry, and 160 * false otherwise. 161 */ isErrorRetryable(HttpException exception)162 public static boolean isErrorRetryable(HttpException exception) { 163 // why take an exception rather than an error code directly? 164 // to give us breathing room for changing our mind later about how we convey 165 // retry information on the exceptions we throw. 166 return isErrorRetryable(exception.getErrorCode()); 167 } 168 169 /******************************************************************************* 170 * Native methods 171 ******************************************************************************/ httpClientConnectionMakeRequest(long connectionBinding, byte[] marshalledRequest, HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler)172 private static native HttpStreamBase httpClientConnectionMakeRequest(long connectionBinding, 173 byte[] marshalledRequest, 174 HttpRequestBodyStream bodyStream, 175 HttpStreamResponseHandlerNativeAdapter responseHandler) throws CrtRuntimeException; 176 httpClientConnectionShutdown(long connectionBinding)177 private static native void httpClientConnectionShutdown(long connectionBinding) throws CrtRuntimeException; httpClientConnectionIsOpen(long connectionBinding)178 private static native boolean httpClientConnectionIsOpen(long connectionBinding) throws CrtRuntimeException; 179 httpClientConnectionReleaseManaged(long connectionBinding)180 private static native void httpClientConnectionReleaseManaged(long connectionBinding) throws CrtRuntimeException; httpClientConnectionGetVersion(long connectionBinding)181 private static native short httpClientConnectionGetVersion(long connectionBinding) throws CrtRuntimeException; 182 isErrorRetryable(int errorCode)183 private static native boolean isErrorRetryable(int errorCode); 184 } 185