25
25
import org .reactivestreams .Publisher ;
26
26
import org .reactivestreams .Subscriber ;
27
27
import software .amazon .awssdk .annotations .SdkInternalApi ;
28
+ import software .amazon .awssdk .http .auth .aws .internal .signer .io .ContentLengthAwareSubscriber ;
28
29
import software .amazon .awssdk .utils .Pair ;
29
30
import software .amazon .awssdk .utils .async .AddingTrailingDataSubscriber ;
30
31
import software .amazon .awssdk .utils .async .DelegatingSubscriber ;
33
34
34
35
/**
35
36
* An implementation of chunk-transfer encoding, but by wrapping a {@link Publisher} of {@link ByteBuffer}. This implementation
36
- * supports chunk-headers, chunk-extensions.
37
+ * supports chunk-headers, chunk-extensions, and trailer-part .
37
38
* <p>
38
39
* Per <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-4.1">RFC-7230</a>, a chunk-transfer encoded message is
39
40
* defined as:
@@ -66,6 +67,7 @@ public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
66
67
private static final byte COMMA = ',' ;
67
68
68
69
private final Publisher <ByteBuffer > wrapped ;
70
+ private final long contentLength ;
69
71
private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
70
72
private final List <TrailerProvider > trailers = new ArrayList <>();
71
73
private final int chunkSize ;
@@ -74,6 +76,7 @@ public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
74
76
75
77
public ChunkedEncodedPublisher (Builder b ) {
76
78
this .wrapped = b .publisher ;
79
+ this .contentLength = b .contentLength ;
77
80
this .chunkSize = b .chunkSize ;
78
81
this .extensions .addAll (b .extensions );
79
82
this .trailers .addAll (b .trailers );
@@ -82,7 +85,8 @@ public ChunkedEncodedPublisher(Builder b) {
82
85
83
86
@ Override
84
87
public void subscribe (Subscriber <? super ByteBuffer > subscriber ) {
85
- Publisher <Iterable <ByteBuffer >> chunked = chunk (wrapped );
88
+ Publisher <ByteBuffer > lengthEnforced = limitLength (wrapped , contentLength );
89
+ Publisher <Iterable <ByteBuffer >> chunked = chunk (lengthEnforced );
86
90
Publisher <Iterable <ByteBuffer >> trailingAdded = addTrailingChunks (chunked );
87
91
Publisher <ByteBuffer > flattened = flatten (trailingAdded );
88
92
Publisher <ByteBuffer > encoded = map (flattened , this ::encodeChunk );
@@ -111,6 +115,10 @@ private Iterable<Iterable<ByteBuffer>> getTrailingChunks() {
111
115
return Collections .singletonList (trailing );
112
116
}
113
117
118
+ private Publisher <ByteBuffer > limitLength (Publisher <ByteBuffer > publisher , long length ) {
119
+ return subscriber -> publisher .subscribe (new ContentLengthAwareSubscriber (subscriber , length ));
120
+ }
121
+
114
122
private Publisher <Iterable <ByteBuffer >> chunk (Publisher <ByteBuffer > upstream ) {
115
123
return subscriber -> {
116
124
upstream .subscribe (new ChunkingSubscriber (subscriber ));
@@ -153,8 +161,7 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
153
161
}
154
162
155
163
int trailerLen = trailerData .stream ()
156
- // + 2 for each CRLF that ends the header-field
157
- .mapToInt (t -> t .remaining () + 2 )
164
+ .mapToInt (t -> t .remaining () + CRLF .length )
158
165
.sum ();
159
166
160
167
int encodedLen = chunkSizeHex .length + extensionsLength + CRLF .length + contentLen + trailerLen + CRLF .length ;
@@ -188,11 +195,11 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
188
195
encoded .put (t );
189
196
encoded .put (CRLF );
190
197
});
198
+ // empty line ends the request body
191
199
encoded .put (CRLF );
192
200
}
193
201
194
202
encoded .flip ();
195
-
196
203
return encoded ;
197
204
}
198
205
@@ -294,6 +301,7 @@ public void onNext(ByteBuffer byteBuffer) {
294
301
295
302
public static class Builder {
296
303
private Publisher <ByteBuffer > publisher ;
304
+ private long contentLength ;
297
305
private int chunkSize ;
298
306
private boolean addEmptyTrailingChunk ;
299
307
private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
@@ -304,6 +312,15 @@ public Builder publisher(Publisher<ByteBuffer> publisher) {
304
312
return this ;
305
313
}
306
314
315
+ public Publisher <ByteBuffer > publisher () {
316
+ return publisher ;
317
+ }
318
+
319
+ public Builder contentLength (long contentLength ) {
320
+ this .contentLength = contentLength ;
321
+ return this ;
322
+ }
323
+
307
324
public Builder chunkSize (int chunkSize ) {
308
325
this .chunkSize = chunkSize ;
309
326
return this ;
@@ -324,6 +341,10 @@ public Builder addTrailer(TrailerProvider trailerProvider) {
324
341
return this ;
325
342
}
326
343
344
+ public List <TrailerProvider > trailers () {
345
+ return trailers ;
346
+ }
347
+
327
348
public ChunkedEncodedPublisher build () {
328
349
return new ChunkedEncodedPublisher (this );
329
350
}
0 commit comments