51
51
* chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
52
52
* chunk-ext-name = token
53
53
* chunk-ext-val = token / quoted-string
54
+ *
55
+ * trailer-part = *( header-field CRLF )
54
56
* </pre>
55
57
*
56
58
* @see ChunkedEncodedInputStream
@@ -60,9 +62,12 @@ public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
60
62
private static final byte [] CRLF = {'\r' , '\n' };
61
63
private static final byte SEMICOLON = ';' ;
62
64
private static final byte EQUALS = '=' ;
65
+ private static final byte COLON = ':' ;
66
+ private static final byte COMMA = ',' ;
63
67
64
68
private final Publisher <ByteBuffer > wrapped ;
65
69
private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
70
+ private final List <TrailerProvider > trailers = new ArrayList <>();
66
71
private final int chunkSize ;
67
72
private ByteBuffer chunkBuffer ;
68
73
private final boolean addEmptyTrailingChunk ;
@@ -71,6 +76,7 @@ public ChunkedEncodedPublisher(Builder b) {
71
76
this .wrapped = b .publisher ;
72
77
this .chunkSize = b .chunkSize ;
73
78
this .extensions .addAll (b .extensions );
79
+ this .trailers .addAll (b .trailers );
74
80
this .addEmptyTrailingChunk = b .addEmptyTrailingChunk ;
75
81
}
76
82
@@ -125,7 +131,6 @@ public Publisher<ByteBuffer> map(Publisher<ByteBuffer> upstream, Function<? supe
125
131
return subscriber -> upstream .subscribe (MappingSubscriber .create (subscriber , mapper ));
126
132
}
127
133
128
- // TODO: Trailing checksum
129
134
private ByteBuffer encodeChunk (ByteBuffer byteBuffer ) {
130
135
int contentLen = byteBuffer .remaining ();
131
136
byte [] chunkSizeHex = Integer .toHexString (contentLen ).getBytes (StandardCharsets .UTF_8 );
@@ -138,24 +143,46 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
138
143
139
144
int extensionsLength = calculateExtensionsLength (chunkExtensions );
140
145
141
- int encodedLen = chunkSizeHex .length + extensionsLength + CRLF .length + contentLen + CRLF .length ;
146
+ boolean isTrailerChunk = contentLen == 0 ;
147
+
148
+ List <ByteBuffer > trailerData ;
149
+ if (isTrailerChunk ) {
150
+ trailerData = getTrailerData ();
151
+ } else {
152
+ trailerData = Collections .emptyList ();
153
+ }
154
+
155
+ int trailerLen = trailerData .stream ().mapToInt (t -> t .remaining () + 2 ).sum ();
156
+
157
+ int encodedLen = chunkSizeHex .length + extensionsLength + CRLF .length + contentLen + trailerLen + CRLF .length ;
142
158
143
159
ByteBuffer encoded = ByteBuffer .allocate (encodedLen );
144
- encoded .put (chunkSizeHex );
145
160
146
- chunkExtensions .forEach (p -> {
161
+ encoded .put (chunkSizeHex ); // chunk-size
162
+ chunkExtensions .forEach (p -> { // chunk-ext
147
163
encoded .put (SEMICOLON );
148
164
encoded .put (p .left ());
149
165
if (p .right () != null && p .right ().length > 0 ) {
150
166
encoded .put (EQUALS );
151
167
encoded .put (p .right ());
152
168
}
153
169
});
154
-
155
- encoded .put (CRLF );
156
- encoded .put (byteBuffer );
157
170
encoded .put (CRLF );
158
171
172
+ // chunk-data
173
+ if (byteBuffer .hasRemaining ()) {
174
+ encoded .put (byteBuffer );
175
+ encoded .put (CRLF );
176
+ }
177
+
178
+ if (isTrailerChunk ) {
179
+ // trailer-part
180
+ trailerData .forEach (t -> {
181
+ encoded .put (t );
182
+ encoded .put (CRLF );
183
+ });
184
+ }
185
+
159
186
encoded .flip ();
160
187
161
188
return encoded ;
@@ -174,6 +201,41 @@ private int calculateExtensionsLength(List<Pair<byte[], byte[]>> chunkExtensions
174
201
}).sum ();
175
202
}
176
203
204
+ private List <ByteBuffer > getTrailerData () {
205
+ List <ByteBuffer > data = new ArrayList <>();
206
+
207
+ for (TrailerProvider provider : trailers ) {
208
+ Pair <String , List <String >> trailer = provider .get ();
209
+
210
+ byte [] key = trailer .left ().getBytes (StandardCharsets .UTF_8 );
211
+ List <byte []> values = trailer .right ()
212
+ .stream ().map (v -> v .getBytes (StandardCharsets .UTF_8 ))
213
+ .collect (Collectors .toList ());
214
+ int valuesLen = values .stream ().mapToInt (v -> v .length ).sum ();
215
+ // name:value1,value2,..
216
+ int size = key .length
217
+ + 1 // colon
218
+ + valuesLen
219
+ + values .size () - 1 ; // commas
220
+
221
+ ByteBuffer trailerData = ByteBuffer .allocate (size );
222
+
223
+ trailerData .put (key );
224
+ trailerData .put (COLON );
225
+
226
+ for (int i = 0 ; i < values .size (); ++i ) {
227
+ trailerData .put (values .get (i ));
228
+ if (i + 1 != values .size ()) {
229
+ trailerData .put (COMMA );
230
+ }
231
+ }
232
+
233
+ trailerData .flip ();
234
+ data .add (trailerData );
235
+ }
236
+ return data ;
237
+ }
238
+
177
239
private class ChunkingSubscriber extends DelegatingSubscriber <ByteBuffer , Iterable <ByteBuffer >> {
178
240
protected ChunkingSubscriber (Subscriber <? super Iterable <ByteBuffer >> subscriber ) {
179
241
super (subscriber );
@@ -222,6 +284,7 @@ public static class Builder {
222
284
private int chunkSize ;
223
285
private boolean addEmptyTrailingChunk ;
224
286
private final List <ChunkExtensionProvider > extensions = new ArrayList <>();
287
+ private final List <TrailerProvider > trailers = new ArrayList <>();
225
288
226
289
public Builder publisher (Publisher <ByteBuffer > publisher ) {
227
290
this .publisher = publisher ;
@@ -243,6 +306,11 @@ public Builder addExtension(ChunkExtensionProvider extension) {
243
306
return this ;
244
307
}
245
308
309
+ public Builder addTrailer (TrailerProvider trailerProvider ) {
310
+ this .trailers .add (trailerProvider );
311
+ return this ;
312
+ }
313
+
246
314
public ChunkedEncodedPublisher build () {
247
315
return new ChunkedEncodedPublisher (this );
248
316
}
0 commit comments