Skip to content

Commit d4489b4

Browse files
authored
Bind out the write data API (#981)
1 parent 3f949b3 commit d4489b4

9 files changed

Lines changed: 622 additions & 11 deletions

File tree

src/main/java/software/amazon/awssdk/crt/http/Http2ClientConnection.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,49 @@ public void updateConnectionWindow(long incrementSize) {
187187
@Override
188188
public Http2Stream makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler)
189189
throws CrtRuntimeException {
190+
return makeRequest(request, streamHandler, false);
191+
}
192+
193+
/**
194+
* Schedules an HttpRequest on the Native EventLoop for this
195+
* HttpClientConnection. The HTTP/1.1 request will be transformed to HTTP/2
196+
* request under the hood.
197+
*
198+
* @param request The Request to make to the Server.
199+
* @param streamHandler The Stream Handler to be called from the Native
200+
* EventLoop
201+
* @param useManualDataWrites When {@code true}, request body data is provided via
202+
* {@link HttpStreamBase#writeData} instead of from the request's
203+
* {@link HttpRequestBodyStream}.
204+
*
205+
* <p>By design, CRT supports setting both a body stream and enabling manual
206+
* writes for HTTP/2, but this is not recommended. Body streams are intended
207+
* for requests whose payload is available in full at the time of sending. If
208+
* the stream does not signal end-of-stream promptly, the event loop will
209+
* busy-wait (hot-loop) for more data, wasting CPU time. Manual writes avoid
210+
* this by letting the caller control when data is sent; the event loop only
211+
* processes the request when {@link HttpStreamBase#writeData} is called and is
212+
* free to service other requests in the meantime.
213+
*
214+
* <p>When both a body stream and manual writes are enabled, the body stream is
215+
* sent as the first DATA frame and the connection then waits asynchronously for
216+
* subsequent {@code writeData()} calls. However, if the body stream has not
217+
* signalled end-of-stream, the event loop will keep getting scheduled for
218+
* requesting more data until it completes.
219+
* @throws CrtRuntimeException if stream creation fails
220+
* @return The Http2Stream that represents this Request/Response Pair. It can be
221+
* closed at any time during the request/response, but must be closed by
222+
* the user thread making this request when it's done.
223+
*/
224+
public Http2Stream makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler,
225+
boolean useManualDataWrites) throws CrtRuntimeException {
190226
if (isNull()) {
191227
throw new IllegalStateException("Http2ClientConnection has been closed, can't make requests on it.");
192228
}
193229

194230
Http2Stream stream = http2ClientConnectionMakeRequest(getNativeHandle(), request.marshalForJni(),
195-
request.getBodyStream(), new HttpStreamResponseHandlerNativeAdapter(streamHandler));
231+
request.getBodyStream(), new HttpStreamResponseHandlerNativeAdapter(streamHandler),
232+
useManualDataWrites);
196233
return stream;
197234
}
198235

@@ -204,7 +241,8 @@ public Http2Stream makeRequest(HttpRequestBase request, HttpStreamBaseResponseHa
204241
******************************************************************************/
205242

206243
private static native Http2Stream http2ClientConnectionMakeRequest(long connectionBinding, byte[] marshalledRequest,
207-
HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler)
244+
HttpRequestBodyStream bodyStream, HttpStreamResponseHandlerNativeAdapter responseHandler,
245+
boolean useManualDataWrites)
208246
throws CrtRuntimeException;
209247

210248
private static native void http2ClientConnectionUpdateSettings(long connectionBinding,

src/main/java/software/amazon/awssdk/crt/http/HttpClientConnection.java

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,58 @@ protected HttpClientConnection(long connectionBinding) {
4141
*/
4242
public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler streamHandler)
4343
throws CrtRuntimeException {
44+
return makeRequest(request, streamHandler, false);
45+
}
46+
47+
/**
48+
* Schedules an HttpRequest on the Native EventLoop for this HttpClientConnection specific to HTTP/1.1 connection.
49+
*
50+
* @param request The Request to make to the Server.
51+
* @param streamHandler The Stream Handler to be called from the Native EventLoop
52+
* @param useManualDataWrites When {@code true}, request body data is provided via
53+
* {@link HttpStreamBase#writeData} instead of from the request's
54+
* {@link HttpRequestBodyStream}.
55+
*
56+
* <p>By design, CRT does not support setting both a body stream and enabling
57+
* manual writes for HTTP/1.1. Body streams are intended for requests whose
58+
* payload is available in full at the time of sending. If the stream does not
59+
* signal end-of-stream promptly, the event loop will busy-wait (hot-loop) for
60+
* more data, wasting CPU time. Manual writes avoid this by letting the caller
61+
* control when data is sent; the event loop only processes the request when
62+
* {@link HttpStreamBase#writeData} is called and is free to service other
63+
* requests in the meantime.
64+
*
65+
* <p>If the request was created with an {@link HttpRequestBodyStream} and this
66+
* parameter is {@code true}, an {@link IllegalStateException} is thrown
67+
* immediately.
68+
* @throws CrtRuntimeException if stream creation fails
69+
* @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the
70+
* request/response, but must be closed by the user thread making this request when it's done.
71+
*/
72+
public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler streamHandler,
73+
boolean useManualDataWrites) throws CrtRuntimeException, IllegalStateException {
4474
if (isNull()) {
4575
throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it.");
4676
}
4777
if (getVersion() == HttpVersion.HTTP_2) {
4878
throw new IllegalArgumentException("HTTP/1 only method called on an HTTP/2 connection.");
4979
}
80+
if (useManualDataWrites && request.getBodyStream() != null) {
81+
throw new IllegalStateException(
82+
"Cannot use manual data writes with a body stream on an HTTP/1.1 request. "
83+
+ "Either remove the body stream or set useManualDataWrites to false.");
84+
}
5085
HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(),
5186
request.marshalForJni(),
5287
request.getBodyStream(),
53-
new HttpStreamResponseHandlerNativeAdapter(streamHandler));
88+
new HttpStreamResponseHandlerNativeAdapter(streamHandler),
89+
useManualDataWrites);
5490

5591
return (HttpStream)stream;
5692
}
5793

5894
/**
59-
* Schedules an HttpRequestBase on the Native EventLoop for this HttpClientConnection applies to both HTTP/2 and HTTP/1.1 connection.
95+
* Schedules an HttpRequestBase on the Native EventLoop for this HttpClientConnection. Applies to both HTTP/2 and HTTP/1.1 connections.
6096
*
6197
* @param request The Request to make to the Server.
6298
* @param streamHandler The Stream Handler to be called from the Native EventLoop
@@ -65,13 +101,30 @@ public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler str
65101
* request/response, but must be closed by the user thread making this request when it's done.
66102
*/
67103
public HttpStreamBase makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler) throws CrtRuntimeException {
104+
return makeRequest(request, streamHandler, false);
105+
}
106+
107+
/**
108+
* Schedules an HttpRequestBase on the Native EventLoop for this HttpClientConnection. Applies to both HTTP/2 and HTTP/1.1 connections.
109+
*
110+
* @param request The Request to make to the Server.
111+
* @param streamHandler The Stream Handler to be called from the Native EventLoop
112+
* @param useManualDataWrites When true, request body data will be provided via
113+
* {@link HttpStreamBase#writeData} instead of from the request's body stream.
114+
* @throws CrtRuntimeException if stream creation fails
115+
* @return The HttpStream that represents this Request/Response Pair. It can be closed at any time during the
116+
* request/response, but must be closed by the user thread making this request when it's done.
117+
*/
118+
public HttpStreamBase makeRequest(HttpRequestBase request, HttpStreamBaseResponseHandler streamHandler,
119+
boolean useManualDataWrites) throws CrtRuntimeException {
68120
if (isNull()) {
69121
throw new IllegalStateException("HttpClientConnection has been closed, can't make requests on it.");
70122
}
71123
HttpStreamBase stream = httpClientConnectionMakeRequest(getNativeHandle(),
72124
request.marshalForJni(),
73125
request.getBodyStream(),
74-
new HttpStreamResponseHandlerNativeAdapter(streamHandler));
126+
new HttpStreamResponseHandlerNativeAdapter(streamHandler),
127+
useManualDataWrites);
75128

76129
return stream;
77130
}
@@ -172,7 +225,8 @@ public static boolean isErrorRetryable(HttpException exception) {
172225
private static native HttpStreamBase httpClientConnectionMakeRequest(long connectionBinding,
173226
byte[] marshalledRequest,
174227
HttpRequestBodyStream bodyStream,
175-
HttpStreamResponseHandlerNativeAdapter responseHandler) throws CrtRuntimeException;
228+
HttpStreamResponseHandlerNativeAdapter responseHandler,
229+
boolean useManualDataWrites) throws CrtRuntimeException;
176230

177231
private static native void httpClientConnectionShutdown(long connectionBinding) throws CrtRuntimeException;
178232
private static native boolean httpClientConnectionIsOpen(long connectionBinding) throws CrtRuntimeException;

src/main/java/software/amazon/awssdk/crt/http/HttpStream.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ public interface HttpStreamWriteChunkCompletionCallback {
4040
* request stream.
4141
* @param chunkCompletionCallback Invoked upon the data being flushed to the
4242
* wire or an error occurring.
43+
* @deprecated Use {@link HttpStreamBase#writeData(byte[], boolean, HttpStreamWriteDataCompletionCallback)} instead.
44+
* writeData() works for both HTTP/1.1 and HTTP/2, whereas writeChunk() is HTTP/1.1 only.
4345
*/
46+
@Deprecated
4447
public void writeChunk(final byte[] chunkData, boolean isFinalChunk,
4548
final HttpStreamWriteChunkCompletionCallback chunkCompletionCallback) {
4649
if (isNull()) {
@@ -71,7 +74,10 @@ public void writeChunk(final byte[] chunkData, boolean isFinalChunk,
7174
* @param isFinalChunk if set to true, this will terminate the request stream.
7275
* @return completable future which will complete upon the data being flushed to
7376
* the wire or an error occurring.
77+
* @deprecated Use {@link HttpStreamBase#writeData(byte[], boolean)} instead.
78+
* writeData() works for both HTTP/1.1 and HTTP/2, whereas writeChunk() is HTTP/1.1 only.
7479
*/
80+
@Deprecated
7581
public CompletableFuture<Void> writeChunk(final byte[] chunkData, boolean isFinalChunk) {
7682
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
7783

src/main/java/software/amazon/awssdk/crt/http/HttpStreamBase.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
package software.amazon.awssdk.crt.http;
77

8+
import software.amazon.awssdk.crt.CRT;
89
import software.amazon.awssdk.crt.CrtResource;
10+
import software.amazon.awssdk.crt.CrtRuntimeException;
11+
12+
import java.util.concurrent.CompletableFuture;
913

1014
/**
1115
* An base class represents a single Http Request/Response for both HTTP/1.1 and
@@ -112,6 +116,63 @@ public void cancel() {
112116
}
113117
}
114118

119+
/**
120+
* Completion interface for writing data to an http stream.
121+
*/
122+
public interface HttpStreamWriteDataCompletionCallback {
123+
void onWriteDataCompleted(int errorCode);
124+
}
125+
126+
/**
127+
* Write data to an HTTP stream. Works for both HTTP/1.1 and HTTP/2.
128+
* The stream must have been created with {@code useManualDataWrites = true}.
129+
* You must call activate() before using this function.
130+
*
131+
* @param data data to send, or null to write zero bytes. Pass null with
132+
* endStream=true to signal end-of-body without sending additional data.
133+
* @param endStream if true, this is the last data to be sent on this stream.
134+
* @param completionCallback invoked when the data has been flushed or an error occurs.
135+
*/
136+
public void writeData(final byte[] data, boolean endStream,
137+
final HttpStreamWriteDataCompletionCallback completionCallback) {
138+
if (isNull()) {
139+
throw new IllegalStateException("HttpStream has been closed.");
140+
}
141+
if (completionCallback == null) {
142+
throw new IllegalArgumentException("You must supply a completionCallback");
143+
}
144+
145+
int error = httpStreamBaseWriteData(getNativeHandle(), data, endStream, completionCallback);
146+
if (error != 0) {
147+
int lastError = CRT.awsLastError();
148+
throw new CrtRuntimeException(lastError);
149+
}
150+
}
151+
152+
/**
153+
* Write data to an HTTP stream. Works for both HTTP/1.1 and HTTP/2.
154+
* The stream must have been created with {@code useManualDataWrites = true}.
155+
* You must call activate() before using this function.
156+
*
157+
* @param data data to send, or null to write zero bytes. Pass null with
158+
* endStream=true to signal end-of-body without sending additional data.
159+
* @param endStream if true, this is the last data to be sent on this stream.
160+
* @return completable future which completes when data is flushed or an error occurs.
161+
*/
162+
public CompletableFuture<Void> writeData(final byte[] data, boolean endStream) {
163+
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
164+
165+
writeData(data, endStream, (errorCode) -> {
166+
if (errorCode == 0) {
167+
completionFuture.complete(null);
168+
} else {
169+
completionFuture.completeExceptionally(new CrtRuntimeException(errorCode));
170+
}
171+
});
172+
173+
return completionFuture;
174+
}
175+
115176
/*******************************************************************************
116177
* Native methods
117178
******************************************************************************/
@@ -125,4 +186,7 @@ public void cancel() {
125186
private static native int httpStreamBaseGetResponseStatusCode(long http_stream);
126187

127188
private static native void httpStreamBaseCancelDefaultError(long http_stream);
189+
190+
private static native int httpStreamBaseWriteData(long http_stream, byte[] data, boolean endStream,
191+
HttpStreamWriteDataCompletionCallback completionCallback);
128192
}

src/main/resources/META-INF/native-image/software.amazon.awssdk/crt/aws-crt/jni-config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,17 @@
761761
}
762762
]
763763
},
764+
{
765+
"name": "software.amazon.awssdk.crt.http.HttpStreamBase$HttpStreamWriteDataCompletionCallback",
766+
"methods": [
767+
{
768+
"name": "onWriteDataCompleted",
769+
"parameterTypes": [
770+
"int"
771+
]
772+
}
773+
]
774+
},
764775
{
765776
"name": "software.amazon.awssdk.crt.http.HttpStreamMetrics",
766777
"methods": [

0 commit comments

Comments
 (0)