1616 */
1717public 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