/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ package software.amazon.awssdk.crt.http; import java.util.concurrent.CompletableFuture; import java.util.Map; import java.util.HashMap; import software.amazon.awssdk.crt.CRT; import software.amazon.awssdk.crt.CrtResource; import software.amazon.awssdk.crt.CrtRuntimeException; import software.amazon.awssdk.crt.http.HttpStreamResponseHandler; import software.amazon.awssdk.crt.http.HttpStream; import static software.amazon.awssdk.crt.CRT.awsLastError; /** * This class wraps aws-c-http to provide the basic HTTP request/response functionality via the AWS Common Runtime. * * HttpClientConnection represents a single connection to a HTTP service endpoint. * * This class is not thread safe and should not be called from different threads. */ public class HttpClientConnection extends CrtResource { protected HttpClientConnection(long connectionBinding) { acquireNativeHandle(connectionBinding); } /** * Schedules an HttpRequest on the Native EventLoop for this HttpClientConnection specific to HTTP/1.1 connection. * * @param request The Request to make to the Server. * @param streamHandler The Stream Handler to be called from the Native EventLoop * @throws CrtRuntimeException if stream creation fails * @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the * request/response, but must be closed by the user thread making this request when it's done. */ public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler streamHandler) throws CrtRuntimeException { if (isNull()) { throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it."); } if (getVersion() == HttpVersion.HTTP_2) { throw new IllegalArgumentException("HTTP/1 only method called on an HTTP/2 connection."); } HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(), request.marshalForJni(), request.getBodyStream(), new HttpStreamResponseHandlerNativeAdapter(streamHandler)); return (HttpStream)stream; } /** * Schedules an HttpRequestBase on the Native EventLoop for this HttpClientConnection applies to both HTTP/2 and HTTP/1.1 connection. * * @param request The Request to make to the Server. * @param streamHandler The Stream Handler to be called from the Native EventLoop * @throws CrtRuntimeException if stream creation fails * @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the * request/response, but must be closed by the user thread making this request when it's done. */ public HttpStreamBase makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler) throws CrtRuntimeException { if (isNull()) { throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it."); } HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(), request.marshalForJni(), request.getBodyStream(), new HttpStreamResponseHandlerNativeAdapter(streamHandler)); return stream; } /** * Determines whether a resource releases its dependencies at the same time the native handle is released or if it waits. * Resources that wait are responsible for calling releaseReferences() manually. */ @Override protected boolean canReleaseReferencesImmediately() { return true; } /** * Releases this HttpClientConnection back into the Connection Pool, and allows another Request to acquire this connection. */ @Override protected void releaseNativeHandle() { if (!isNull()){ httpClientConnectionReleaseManaged(getNativeHandle()); } } /** * Shuts down the underlying http connection. Even if this function is called, you still need to properly close * the connection as well in order to release the native resources. */ public void shutdown() { if (isNull()) { throw new IllegalStateException("HttpClientConnection has been closed and released back to the pool, cannot shutdown the connection."); } httpClientConnectionShutdown(getNativeHandle()); } /** * Check the underlying http connection is still open or not. * * @return true unless the underlying http connection is shutting down, or has been shutdown. */ public boolean isOpen() { if (isNull()) { throw new IllegalStateException("HttpClientConnection has been closed."); } return httpClientConnectionIsOpen(getNativeHandle()); } public HttpVersion getVersion() { if (isNull()) { throw new IllegalStateException("HttpClientConnection has been closed."); } short version = httpClientConnectionGetVersion(getNativeHandle()); return HttpVersion.getEnumValueFromInteger((int) version); }; /** Called from Native when a new connection is acquired **/ private static void onConnectionAcquired(CompletableFuture acquireFuture, long nativeConnectionBinding, int errorCode) { if (errorCode != CRT.AWS_CRT_SUCCESS) { acquireFuture.completeExceptionally(new HttpException(errorCode)); return; } if (HttpVersion.getEnumValueFromInteger( (int) httpClientConnectionGetVersion(nativeConnectionBinding)) == HttpVersion.HTTP_2) { HttpClientConnection h2Conn = new Http2ClientConnection(nativeConnectionBinding); if (!acquireFuture.complete(h2Conn)) { // future was already completed/cancelled, return it immediately to the pool to not leak it h2Conn.close(); } } else { HttpClientConnection conn = new HttpClientConnection(nativeConnectionBinding); if (!acquireFuture.complete(conn)) { // future was already completed/cancelled, return it immediately to the pool to not leak it conn.close(); } } } /** * Certain exceptions thrown by this HTTP API are from invalid boundary conditions * that, if the request isn't altered, will never succeed. This function returns * false if the exception is caused by such a condition. *

* It does not mean the request that generated the error SHOULD be retried: * only that as far as this client is concerned, the request might, * possibly succeed with a subsequent attempt. * * @param exception, an exception thrown by the CRT HTTP API--for any reason. * @return true if the error that generated the exception makes sense for a retry, and * false otherwise. */ public static boolean isErrorRetryable(HttpException exception) { // why take an exception rather than an error code directly? // to give us breathing room for changing our mind later about how we convey // retry information on the exceptions we throw. return isErrorRetryable(exception.getErrorCode()); } /******************************************************************************* * Native methods ******************************************************************************/ private static native HttpStreamBase httpClientConnectionMakeRequest(long connectionBinding, byte[] marshalledRequest, HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler) throws CrtRuntimeException; private static native void httpClientConnectionShutdown(long connectionBinding) throws CrtRuntimeException; private static native boolean httpClientConnectionIsOpen(long connectionBinding) throws CrtRuntimeException; private static native void httpClientConnectionReleaseManaged(long connectionBinding) throws CrtRuntimeException; private static native short httpClientConnectionGetVersion(long connectionBinding) throws CrtRuntimeException; private static native boolean isErrorRetryable(int errorCode); }