Skip to content

Commit f4bf53d

Browse files
committed
deprecate write chunk and add preemptive check
1 parent e72343a commit f4bf53d

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ public HttpStream makeRequest(HttpRequest request, HttpStreamResponseHandler str
6969
new HttpStreamResponseHandlerNativeAdapter(streamHandler),
7070
useManualDataWrites);
7171

72-
return (HttpStream)stream;
72+
HttpStream h1Stream = (HttpStream)stream;
73+
h1Stream.setHasBodyStream(request.getBodyStream() != null);
74+
return h1Stream;
7375
}
7476

7577
/**

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
*/
1717
public class HttpStream extends HttpStreamBase {
1818

19+
/**
20+
* Tracks whether this stream was created with a body stream on the request.
21+
* Used to guard against calling writeData() on a stream that already has a body
22+
* stream — HTTP/1.1 requires exactly one body framing mechanism per message
23+
* (RFC 9112, Section 6), so a body stream and manual writes cannot coexist.
24+
*/
25+
private boolean hasBodyStream = false;
26+
1927
/*
2028
* Native code will call this constructor during
2129
* HttpClientConnection.makeRequest()
@@ -24,6 +32,52 @@ protected HttpStream(long ptr) {
2432
super(ptr);
2533
}
2634

35+
/**
36+
* Package-private. Called by HttpClientConnection.makeRequest() to record
37+
* whether the originating request had a body stream attached.
38+
*/
39+
void setHasBodyStream(boolean hasBodyStream) {
40+
this.hasBodyStream = hasBodyStream;
41+
}
42+
43+
/**
44+
* {@inheritDoc}
45+
* <p>
46+
* <b>HTTP/1.1 restriction:</b> An HTTP/1.1 request message body is framed by
47+
* exactly one mechanism: either a {@code Content-Length} header declaring the
48+
* body size upfront, or {@code Transfer-Encoding: chunked} for streaming
49+
* (RFC 9112, Section 6). A sender MUST NOT combine both framing mechanisms
50+
* (RFC 9112, Section 6.2: "A sender MUST NOT send a Content-Length header field
51+
* in any message that contains a Transfer-Encoding header field").
52+
* <p>
53+
* Because the framing is committed at the start of the message, a body stream
54+
* and manual {@code writeData()} calls cannot coexist on the same HTTP/1.1
55+
* stream — doing so would either violate the declared {@code Content-Length} or
56+
* require switching transfer-encoding mid-message, which the protocol does not
57+
* permit. If the request was created with an {@link HttpRequestBodyStream},
58+
* calling this method will throw {@link IllegalStateException}.
59+
* <p>
60+
* HTTP/2 does not have this restriction. HTTP/2 uses its own DATA frame
61+
* framing (RFC 9113, Section 8.1), where the body stream and manual writes
62+
* both append DATA frames to the same outgoing queue.
63+
* <p>
64+
* <b>Migration from writeChunk:</b> This method supersedes the deprecated
65+
* {@link #writeChunk} methods. Use {@code writeData} for all manual body data
66+
* writes on both HTTP/1.1 and HTTP/2 streams.
67+
*/
68+
@Override
69+
public void writeData(final byte[] data, boolean endStream,
70+
final HttpStreamWriteDataCompletionCallback completionCallback) {
71+
if (hasBodyStream) {
72+
throw new IllegalStateException(
73+
"Cannot call writeData() on an HTTP/1.1 stream that was created with a body stream. "
74+
+ "HTTP/1.1 requires exactly one body framing mechanism per message (RFC 9112, Section 6). "
75+
+ "A body stream and manual writeData() calls cannot coexist. "
76+
+ "Create the stream with useManualDataWrites=true and no body stream to use writeData().");
77+
}
78+
super.writeData(data, endStream, completionCallback);
79+
}
80+
2781
/**
2882
* Completion interface for writing chunks to an http stream
2983
*/
@@ -40,7 +94,10 @@ public interface HttpStreamWriteChunkCompletionCallback {
4094
* request stream.
4195
* @param chunkCompletionCallback Invoked upon the data being flushed to the
4296
* wire or an error occurring.
97+
* @deprecated Use {@link HttpStreamBase#writeData(byte[], boolean, HttpStreamWriteDataCompletionCallback)} instead.
98+
* writeData() works for both HTTP/1.1 and HTTP/2, whereas writeChunk() is HTTP/1.1 only.
4399
*/
100+
@Deprecated
44101
public void writeChunk(final byte[] chunkData, boolean isFinalChunk,
45102
final HttpStreamWriteChunkCompletionCallback chunkCompletionCallback) {
46103
if (isNull()) {
@@ -71,7 +128,10 @@ public void writeChunk(final byte[] chunkData, boolean isFinalChunk,
71128
* @param isFinalChunk if set to true, this will terminate the request stream.
72129
* @return completable future which will complete upon the data being flushed to
73130
* the wire or an error occurring.
131+
* @deprecated Use {@link HttpStreamBase#writeData(byte[], boolean)} instead.
132+
* writeData() works for both HTTP/1.1 and HTTP/2, whereas writeChunk() is HTTP/1.1 only.
74133
*/
134+
@Deprecated
75135
public CompletableFuture<Void> writeChunk(final byte[] chunkData, boolean isFinalChunk) {
76136
CompletableFuture<Void> completionFuture = new CompletableFuture<>();
77137

0 commit comments

Comments
 (0)