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 software.amazon.awssdk.crt.AsyncCallback; 9 import software.amazon.awssdk.crt.CrtRuntimeException; 10 11 import java.util.concurrent.CompletableFuture; 12 import java.util.List; 13 14 /** 15 * This class wraps aws-c-http to provide the basic HTTP/2 request/response 16 * functionality via the AWS Common Runtime. 17 * 18 * Http2ClientConnection represents a single connection to a HTTP/2 service 19 * endpoint. 20 * 21 * This class is not thread safe and should not be called from different 22 * threads. 23 */ 24 public class Http2ClientConnection extends HttpClientConnection { 25 26 /* 27 * Error codes that may be present in HTTP/2 RST_STREAM and GOAWAY frames 28 * (RFC-7540 7). 29 */ 30 public enum Http2ErrorCode { 31 PROTOCOL_ERROR(1), INTERNAL_ERROR(2), FLOW_CONTROL_ERROR(3), SETTINGS_TIMEOUT(4), STREAM_CLOSED(5), 32 FRAME_SIZE_ERROR(6), REFUSED_STREAM(7), CANCEL(8), COMPRESSION_ERROR(9), CONNECT_ERROR(10), 33 ENHANCE_YOUR_CALM(11), INADEQUATE_SECURITY(12), HTTP_1_1_REQUIRED(13); 34 35 private int errorCode; 36 Http2ErrorCode(int value)37 Http2ErrorCode(int value) { 38 errorCode = value; 39 } 40 getValue()41 public int getValue() { 42 return errorCode; 43 } 44 } 45 Http2ClientConnection(long connectionBinding)46 public Http2ClientConnection(long connectionBinding) { 47 super(connectionBinding); 48 } 49 50 /** 51 * Send a SETTINGS frame. SETTINGS will be applied locally when SETTINGS ACK is 52 * received from peer. 53 * 54 * @param settings The array of settings to change. Note: each setting has its 55 * boundary. 56 * 57 * @return When this future completes without exception, the peer has 58 * acknowledged the settings and the change has been applied. 59 */ updateSettings(final List<Http2ConnectionSetting> settings)60 public CompletableFuture<Void> updateSettings(final List<Http2ConnectionSetting> settings) { 61 CompletableFuture<Void> future = new CompletableFuture<>(); 62 if (isNull()) { 63 future.completeExceptionally( 64 new IllegalStateException("Http2ClientConnection has been closed, can't change settings on it.")); 65 return future; 66 } 67 AsyncCallback updateSettingsCompleted = AsyncCallback.wrapFuture(future, null); 68 try { 69 http2ClientConnectionUpdateSettings(getNativeHandle(), updateSettingsCompleted, 70 Http2ConnectionSetting.marshallSettingsForJNI(settings)); 71 } catch (CrtRuntimeException ex) { 72 future.completeExceptionally(ex); 73 } 74 return future; 75 } 76 77 /** 78 * Send a PING frame. Round-trip-time is calculated when PING ACK is received 79 * from peer. 80 * 81 * @param pingData 8 Bytes data with the PING frame or null for not include data 82 * in ping 83 * 84 * @return When this future completes without exception, the peer has 85 * acknowledged the PING and future will be completed with the round 86 * trip time in nano seconds for the connection. 87 */ sendPing(final byte[] pingData)88 public CompletableFuture<Long> sendPing(final byte[] pingData) { 89 CompletableFuture<Long> completionFuture = new CompletableFuture<>(); 90 if (isNull()) { 91 completionFuture.completeExceptionally( 92 new IllegalStateException("Http2ClientConnection has been closed, can't send ping on it.")); 93 return completionFuture; 94 } 95 AsyncCallback pingCompleted = AsyncCallback.wrapFuture(completionFuture, 0L); 96 try { 97 http2ClientConnectionSendPing(getNativeHandle(), pingCompleted, pingData); 98 } catch (CrtRuntimeException ex) { 99 completionFuture.completeExceptionally(ex); 100 } 101 return completionFuture; 102 } 103 sendPing()104 public CompletableFuture<Long> sendPing() { 105 return this.sendPing(null); 106 } 107 108 /** 109 * Send a custom GOAWAY frame. 110 * 111 * Note that the connection automatically attempts to send a GOAWAY during 112 * shutdown (unless a GOAWAY with a valid Last-Stream-ID has already been sent). 113 * 114 * This call can be used to gracefully warn the peer of an impending shutdown 115 * (http2_error=0, allow_more_streams=true), or to customize the final GOAWAY 116 * frame that is sent by this connection. 117 * 118 * The other end may not receive the goaway, if the connection already closed. 119 * 120 * @param Http2ErrorCode The HTTP/2 error code (RFC-7540 section 7) to send. 121 * `enum Http2ErrorCode` lists official codes. 122 * @param allowMoreStreams If true, new peer-initiated streams will continue to 123 * be acknowledged and the GOAWAY's Last-Stream-ID will 124 * be set to a max value. If false, new peer-initiated 125 * streams will be ignored and the GOAWAY's 126 * Last-Stream-ID will be set to the latest acknowledged 127 * stream. 128 * @param debugData Optional debug data to send. Size must not exceed 129 * 16KB. null is acceptable to not include debug data. 130 */ sendGoAway(final Http2ErrorCode Http2ErrorCode, final boolean allowMoreStreams, final byte[] debugData)131 public void sendGoAway(final Http2ErrorCode Http2ErrorCode, final boolean allowMoreStreams, 132 final byte[] debugData) { 133 if (isNull()) { 134 throw new IllegalStateException("Http2ClientConnection has been closed."); 135 } 136 http2ClientConnectionSendGoAway(getNativeHandle(), (long) Http2ErrorCode.getValue(), allowMoreStreams, 137 debugData); 138 } 139 sendGoAway(final Http2ErrorCode Http2ErrorCode, final boolean allowMoreStreams)140 public void sendGoAway(final Http2ErrorCode Http2ErrorCode, final boolean allowMoreStreams) { 141 this.sendGoAway(Http2ErrorCode, allowMoreStreams, null); 142 } 143 144 /** 145 * Increment the connection's flow-control window to keep data flowing. 146 * 147 * If the connection was created with `manualWindowManagement` set true, the 148 * flow-control window of the connection will shrink as body data is received 149 * for all the streams created on it. (headers, padding, and other metadata do 150 * not affect the window). The initial connection flow-control window is 65,535. 151 * Once the connection's flow-control window reaches to 0, all the streams on 152 * the connection stop receiving any further data. 153 * 154 * If `manualWindowManagement` is false, this call will have no effect. The 155 * connection maintains its flow-control windows such that no back-pressure is 156 * applied and data arrives as fast as possible. 157 * 158 * If you are not connected, this call will have no effect. 159 * 160 * Crashes when the connection is not http2 connection. The limit of the Maximum 161 * Size is 2**31 - 1. If the increment size cause the connection flow window 162 * exceeds the Maximum size, this call will result in the connection lost. 163 * 164 * @param incrementSize The size to increment for the connection's flow control 165 * window 166 */ updateConnectionWindow(long incrementSize)167 public void updateConnectionWindow(long incrementSize) { 168 if (incrementSize > 4294967296L || incrementSize < 0) { 169 throw new IllegalArgumentException("increment size cannot exceed 4294967296"); 170 } 171 http2ClientConnectionUpdateConnectionWindow(getNativeHandle(), incrementSize); 172 } 173 174 /** 175 * Schedules an HttpRequest on the Native EventLoop for this 176 * HttpClientConnection. The HTTP/1.1 request will be transformed to HTTP/2 177 * request under the hood. 178 * 179 * @param request The Request to make to the Server. 180 * @param streamHandler The Stream Handler to be called from the Native 181 * EventLoop 182 * @throws CrtRuntimeException if stream creation fails 183 * @return The Http2Stream that represents this Request/Response Pair. It can be 184 * closed at any time during the request/response, but must be closed by 185 * the user thread making this request when it's done. 186 */ 187 @Override makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler)188 public Http2Stream makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler) 189 throws CrtRuntimeException { 190 if (isNull()) { 191 throw new IllegalStateException("Http2ClientConnection has been closed, can't make requests on it."); 192 } 193 194 Http2Stream stream = http2ClientConnectionMakeRequest(getNativeHandle(), request.marshalForJni(), 195 request.getBodyStream(), new HttpStreamResponseHandlerNativeAdapter(streamHandler)); 196 return stream; 197 } 198 199 /** 200 * @TODO: bindings for getters of local/remote setting and goaway. 201 */ 202 /******************************************************************************* 203 * Native methods 204 ******************************************************************************/ 205 http2ClientConnectionMakeRequest(long connectionBinding, byte[] marshalledRequest, HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler)206 private static native Http2Stream http2ClientConnectionMakeRequest(long connectionBinding, byte[] marshalledRequest, 207 HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler) 208 throws CrtRuntimeException; 209 http2ClientConnectionUpdateSettings(long connectionBinding, AsyncCallback completedCallback, long[] marshalledSettings)210 private static native void http2ClientConnectionUpdateSettings(long connectionBinding, 211 AsyncCallback completedCallback, long[] marshalledSettings) throws CrtRuntimeException; 212 http2ClientConnectionSendPing(long connectionBinding, AsyncCallback completedCallback, byte[] pingData)213 private static native void http2ClientConnectionSendPing(long connectionBinding, AsyncCallback completedCallback, 214 byte[] pingData) throws CrtRuntimeException; 215 http2ClientConnectionSendGoAway(long connectionBinding, long h2ErrorCode, boolean allowMoreStreams, byte[] debugData)216 private static native void http2ClientConnectionSendGoAway(long connectionBinding, long h2ErrorCode, 217 boolean allowMoreStreams, byte[] debugData) throws CrtRuntimeException; 218 http2ClientConnectionUpdateConnectionWindow(long connectionBinding, long incrementSize)219 private static native void http2ClientConnectionUpdateConnectionWindow(long connectionBinding, long incrementSize) 220 throws CrtRuntimeException; 221 } 222