1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufUtil;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.channel.ChannelPromise;
23 import io.netty.channel.FileRegion;
24 import io.netty.handler.codec.EncoderException;
25 import io.netty.handler.codec.MessageToMessageEncoder;
26 import io.netty.util.CharsetUtil;
27 import io.netty.util.ReferenceCountUtil;
28 import io.netty.util.concurrent.PromiseCombiner;
29 import io.netty.util.internal.StringUtil;
30
31 import java.util.ArrayList;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map.Entry;
35
36 import static io.netty.buffer.Unpooled.directBuffer;
37 import static io.netty.buffer.Unpooled.unreleasableBuffer;
38 import static io.netty.handler.codec.http.HttpConstants.CR;
39 import static io.netty.handler.codec.http.HttpConstants.LF;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
55
56
57 private static final int COPY_CONTENT_THRESHOLD = 128;
58 static final int CRLF_SHORT = (CR << 8) | LF;
59 private static final int ZERO_CRLF_MEDIUM = ('0' << 16) | CRLF_SHORT;
60 private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };
61 private static final ByteBuf CRLF_BUF = unreleasableBuffer(
62 directBuffer(2).writeByte(CR).writeByte(LF)).asReadOnly();
63 private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(
64 directBuffer(ZERO_CRLF_CRLF.length).writeBytes(ZERO_CRLF_CRLF)).asReadOnly();
65 private static final float HEADERS_WEIGHT_NEW = 1 / 5f;
66 private static final float HEADERS_WEIGHT_HISTORICAL = 1 - HEADERS_WEIGHT_NEW;
67 private static final float TRAILERS_WEIGHT_NEW = HEADERS_WEIGHT_NEW;
68 private static final float TRAILERS_WEIGHT_HISTORICAL = HEADERS_WEIGHT_HISTORICAL;
69
70 private static final int ST_INIT = 0;
71 private static final int ST_CONTENT_NON_CHUNK = 1;
72 private static final int ST_CONTENT_CHUNK = 2;
73 private static final int ST_CONTENT_ALWAYS_EMPTY = 3;
74
75 @SuppressWarnings("RedundantFieldInitialization")
76 private int state = ST_INIT;
77
78
79
80
81
82 private float headersEncodedSizeAccumulator = 256;
83
84
85
86
87
88 private float trailersEncodedSizeAccumulator = 256;
89
90 private final List<Object> out = new ArrayList<Object>();
91
92 private static boolean checkContentState(int state) {
93 return state == ST_CONTENT_CHUNK || state == ST_CONTENT_NON_CHUNK || state == ST_CONTENT_ALWAYS_EMPTY;
94 }
95
96 public HttpObjectEncoder() {
97 super(Object.class);
98 }
99
100 @Override
101 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
102 try {
103 if (acceptOutboundMessage(msg)) {
104 encode(ctx, msg, out);
105 if (out.isEmpty()) {
106 throw new EncoderException(
107 StringUtil.simpleClassName(this) + " must produce at least one message.");
108 }
109 } else {
110 ctx.write(msg, promise);
111 }
112 } catch (EncoderException e) {
113 throw e;
114 } catch (Throwable t) {
115 throw new EncoderException(t);
116 } finally {
117 writeOutList(ctx, out, promise);
118 }
119 }
120
121 private static void writeOutList(ChannelHandlerContext ctx, List<Object> out, ChannelPromise promise) {
122 final int size = out.size();
123 try {
124 if (size == 1) {
125 ctx.write(out.get(0), promise);
126 } else if (size > 1) {
127
128
129 if (promise == ctx.voidPromise()) {
130 writeVoidPromise(ctx, out);
131 } else {
132 writePromiseCombiner(ctx, out, promise);
133 }
134 }
135 } finally {
136 out.clear();
137 }
138 }
139
140 private static void writeVoidPromise(ChannelHandlerContext ctx, List<Object> out) {
141 final ChannelPromise voidPromise = ctx.voidPromise();
142 for (int i = 0; i < out.size(); i++) {
143 ctx.write(out.get(i), voidPromise);
144 }
145 }
146
147 private static void writePromiseCombiner(ChannelHandlerContext ctx, List<Object> out, ChannelPromise promise) {
148 final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
149 for (int i = 0; i < out.size(); i++) {
150 combiner.add(ctx.write(out.get(i)));
151 }
152 combiner.finish(promise);
153 }
154
155 @Override
156 @SuppressWarnings("ConditionCoveredByFurtherCondition")
157 protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
158
159 if (msg == Unpooled.EMPTY_BUFFER) {
160 out.add(Unpooled.EMPTY_BUFFER);
161 return;
162 }
163
164
165
166
167
168 if (msg instanceof FullHttpMessage) {
169 encodeFullHttpMessage(ctx, msg, out);
170 return;
171 }
172 if (msg instanceof HttpMessage) {
173 final H m;
174 try {
175 m = (H) msg;
176 } catch (Exception rethrow) {
177 ReferenceCountUtil.release(msg);
178 throw rethrow;
179 }
180 if (m instanceof LastHttpContent) {
181 encodeHttpMessageLastContent(ctx, m, out);
182 } else if (m instanceof HttpContent) {
183 encodeHttpMessageNotLastContent(ctx, m, out);
184 } else {
185 encodeJustHttpMessage(ctx, m, out);
186 }
187 } else {
188 encodeNotHttpMessageContentTypes(ctx, msg, out);
189 }
190 }
191
192 private void encodeJustHttpMessage(ChannelHandlerContext ctx, H m, List<Object> out) throws Exception {
193 assert !(m instanceof HttpContent);
194 try {
195 if (state != ST_INIT) {
196 throwUnexpectedMessageTypeEx(m, state);
197 }
198 final ByteBuf buf = encodeInitHttpMessage(ctx, m);
199
200 assert checkContentState(state);
201
202 out.add(buf);
203 } finally {
204 ReferenceCountUtil.release(m);
205 }
206 }
207
208 private void encodeByteBufHttpContent(int state, ChannelHandlerContext ctx, ByteBuf buf, ByteBuf content,
209 HttpHeaders trailingHeaders, List<Object> out) {
210 switch (state) {
211 case ST_CONTENT_NON_CHUNK:
212 if (encodeContentNonChunk(out, buf, content)) {
213 break;
214 }
215
216 case ST_CONTENT_ALWAYS_EMPTY:
217
218 out.add(buf);
219 break;
220 case ST_CONTENT_CHUNK:
221
222 out.add(buf);
223 encodeChunkedHttpContent(ctx, content, trailingHeaders, out);
224 break;
225 default:
226 throw new Error();
227 }
228 }
229
230 private void encodeHttpMessageNotLastContent(ChannelHandlerContext ctx, H m, List<Object> out) throws Exception {
231 assert m instanceof HttpContent;
232 assert !(m instanceof LastHttpContent);
233 final HttpContent httpContent = (HttpContent) m;
234 try {
235 if (state != ST_INIT) {
236 throwUnexpectedMessageTypeEx(m, state);
237 }
238 final ByteBuf buf = encodeInitHttpMessage(ctx, m);
239
240 assert checkContentState(state);
241
242 encodeByteBufHttpContent(state, ctx, buf, httpContent.content(), null, out);
243 } finally {
244 httpContent.release();
245 }
246 }
247
248 private void encodeHttpMessageLastContent(ChannelHandlerContext ctx, H m, List<Object> out) throws Exception {
249 assert m instanceof LastHttpContent;
250 final LastHttpContent httpContent = (LastHttpContent) m;
251 try {
252 if (state != ST_INIT) {
253 throwUnexpectedMessageTypeEx(m, state);
254 }
255 final ByteBuf buf = encodeInitHttpMessage(ctx, m);
256
257 assert checkContentState(state);
258
259 encodeByteBufHttpContent(state, ctx, buf, httpContent.content(), httpContent.trailingHeaders(), out);
260
261 state = ST_INIT;
262 } finally {
263 httpContent.release();
264 }
265 }
266 @SuppressWarnings("ConditionCoveredByFurtherCondition")
267 private void encodeNotHttpMessageContentTypes(ChannelHandlerContext ctx, Object msg, List<Object> out) {
268 assert !(msg instanceof HttpMessage);
269 if (state == ST_INIT) {
270 try {
271 if (msg instanceof ByteBuf && bypassEncoderIfEmpty((ByteBuf) msg, out)) {
272 return;
273 }
274 throwUnexpectedMessageTypeEx(msg, ST_INIT);
275 } finally {
276 ReferenceCountUtil.release(msg);
277 }
278 }
279 if (msg == LastHttpContent.EMPTY_LAST_CONTENT) {
280 state = encodeEmptyLastHttpContent(state, out);
281 return;
282 }
283 if (msg instanceof LastHttpContent) {
284 encodeLastHttpContent(ctx, (LastHttpContent) msg, out);
285 return;
286 }
287 if (msg instanceof HttpContent) {
288 encodeHttpContent(ctx, (HttpContent) msg, out);
289 return;
290 }
291 if (msg instanceof ByteBuf) {
292 encodeByteBufContent(ctx, (ByteBuf) msg, out);
293 return;
294 }
295 if (msg instanceof FileRegion) {
296 encodeFileRegionContent(ctx, (FileRegion) msg, out);
297 return;
298 }
299 try {
300 throwUnexpectedMessageTypeEx(msg, state);
301 } finally {
302 ReferenceCountUtil.release(msg);
303 }
304 }
305
306 private void encodeFullHttpMessage(ChannelHandlerContext ctx, Object o, List<Object> out)
307 throws Exception {
308 assert o instanceof FullHttpMessage;
309 final FullHttpMessage msg = (FullHttpMessage) o;
310 try {
311 if (state != ST_INIT) {
312 throwUnexpectedMessageTypeEx(o, state);
313 }
314
315 final H m = (H) o;
316
317 final int state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
318 HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
319
320 ByteBuf content = msg.content();
321
322 final boolean accountForContentSize = content.readableBytes() > 0 &&
323 state == ST_CONTENT_NON_CHUNK &&
324
325
326
327 content.readableBytes() <=
328 Math.max(COPY_CONTENT_THRESHOLD, ((int) headersEncodedSizeAccumulator) / 8);
329
330 final int headersAndContentSize = (int) headersEncodedSizeAccumulator +
331 (accountForContentSize? content.readableBytes() : 0);
332 final ByteBuf buf = ctx.alloc().buffer(headersAndContentSize);
333
334 encodeInitialLine(buf, m);
335
336 sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
337
338 encodeHeaders(m.headers(), buf);
339 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
340
341
342 headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
343 HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
344
345 encodeByteBufHttpContent(state, ctx, buf, content, msg.trailingHeaders(), out);
346 } finally {
347 msg.release();
348 }
349 }
350
351 private static boolean encodeContentNonChunk(List<Object> out, ByteBuf buf, ByteBuf content) {
352 final int contentLength = content.readableBytes();
353 if (contentLength > 0) {
354 if (buf.maxFastWritableBytes() >= contentLength) {
355
356 buf.writeBytes(content);
357 out.add(buf);
358 } else {
359 out.add(buf);
360 out.add(content.retain());
361 }
362 return true;
363 }
364 return false;
365 }
366
367 private static void throwUnexpectedMessageTypeEx(Object msg, int state) {
368 throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg)
369 + ", state: " + state);
370 }
371
372 private void encodeFileRegionContent(ChannelHandlerContext ctx, FileRegion msg, List<Object> out) {
373 try {
374 assert state != ST_INIT;
375 switch (state) {
376 case ST_CONTENT_NON_CHUNK:
377 if (msg.count() > 0) {
378 out.add(msg.retain());
379 break;
380 }
381
382
383 case ST_CONTENT_ALWAYS_EMPTY:
384
385
386
387
388
389
390
391 out.add(Unpooled.EMPTY_BUFFER);
392 break;
393 case ST_CONTENT_CHUNK:
394 encodedChunkedFileRegionContent(ctx, msg, out);
395 break;
396 default:
397 throw new Error();
398 }
399 } finally {
400 msg.release();
401 }
402 }
403
404
405
406
407
408
409 private static boolean bypassEncoderIfEmpty(ByteBuf msg, List<Object> out) {
410 if (!msg.isReadable()) {
411 out.add(msg.retain());
412 return true;
413 }
414 return false;
415 }
416
417 private void encodeByteBufContent(ChannelHandlerContext ctx, ByteBuf content, List<Object> out) {
418 try {
419 assert state != ST_INIT;
420 if (bypassEncoderIfEmpty(content, out)) {
421 return;
422 }
423 encodeByteBufAndTrailers(state, ctx, out, content, null);
424 } finally {
425 content.release();
426 }
427 }
428
429 private static int encodeEmptyLastHttpContent(int state, List<Object> out) {
430 assert state != ST_INIT;
431
432 switch (state) {
433 case ST_CONTENT_NON_CHUNK:
434 case ST_CONTENT_ALWAYS_EMPTY:
435 out.add(Unpooled.EMPTY_BUFFER);
436 break;
437 case ST_CONTENT_CHUNK:
438 out.add(ZERO_CRLF_CRLF_BUF.duplicate());
439 break;
440 default:
441 throw new Error();
442 }
443 return ST_INIT;
444 }
445
446 private void encodeLastHttpContent(ChannelHandlerContext ctx, LastHttpContent msg, List<Object> out) {
447 assert state != ST_INIT;
448 assert !(msg instanceof HttpMessage);
449 try {
450 encodeByteBufAndTrailers(state, ctx, out, msg.content(), msg.trailingHeaders());
451 state = ST_INIT;
452 } finally {
453 msg.release();
454 }
455 }
456
457 private void encodeHttpContent(ChannelHandlerContext ctx, HttpContent msg, List<Object> out) {
458 assert state != ST_INIT;
459 assert !(msg instanceof HttpMessage);
460 assert !(msg instanceof LastHttpContent);
461 try {
462 this.encodeByteBufAndTrailers(state, ctx, out, msg.content(), null);
463 } finally {
464 msg.release();
465 }
466 }
467
468 private void encodeByteBufAndTrailers(int state, ChannelHandlerContext ctx, List<Object> out, ByteBuf content,
469 HttpHeaders trailingHeaders) {
470 switch (state) {
471 case ST_CONTENT_NON_CHUNK:
472 if (content.isReadable()) {
473 out.add(content.retain());
474 break;
475 }
476
477 case ST_CONTENT_ALWAYS_EMPTY:
478 out.add(Unpooled.EMPTY_BUFFER);
479 break;
480 case ST_CONTENT_CHUNK:
481 encodeChunkedHttpContent(ctx, content, trailingHeaders, out);
482 break;
483 default:
484 throw new Error();
485 }
486 }
487
488 private void encodeChunkedHttpContent(ChannelHandlerContext ctx, ByteBuf content, HttpHeaders trailingHeaders,
489 List<Object> out) {
490 final int contentLength = content.readableBytes();
491 if (contentLength > 0) {
492 addEncodedLengthHex(ctx, contentLength, out);
493 out.add(content.retain());
494 out.add(CRLF_BUF.duplicate());
495 }
496 if (trailingHeaders != null) {
497 encodeTrailingHeaders(ctx, trailingHeaders, out);
498 } else if (contentLength == 0) {
499
500
501 out.add(content.retain());
502 }
503 }
504
505 private void encodeTrailingHeaders(ChannelHandlerContext ctx, HttpHeaders trailingHeaders, List<Object> out) {
506 if (trailingHeaders.isEmpty()) {
507 out.add(ZERO_CRLF_CRLF_BUF.duplicate());
508 } else {
509 ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
510 ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
511 encodeHeaders(trailingHeaders, buf);
512 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
513 trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
514 TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
515 out.add(buf);
516 }
517 }
518
519 private ByteBuf encodeInitHttpMessage(ChannelHandlerContext ctx, H m) throws Exception {
520 assert state == ST_INIT;
521
522 ByteBuf buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
523
524 encodeInitialLine(buf, m);
525 state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
526 HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
527
528 sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
529
530 encodeHeaders(m.headers(), buf);
531 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
532
533 headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
534 HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
535 return buf;
536 }
537
538
539
540
541 protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) {
542 Iterator<Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
543 while (iter.hasNext()) {
544 Entry<CharSequence, CharSequence> header = iter.next();
545 HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
546 }
547 }
548
549 private static void encodedChunkedFileRegionContent(ChannelHandlerContext ctx, FileRegion msg, List<Object> out) {
550 final long contentLength = msg.count();
551 if (contentLength > 0) {
552 addEncodedLengthHex(ctx, contentLength, out);
553 out.add(msg.retain());
554 out.add(CRLF_BUF.duplicate());
555 } else if (contentLength == 0) {
556
557
558 out.add(msg.retain());
559 }
560 }
561
562 private static void addEncodedLengthHex(ChannelHandlerContext ctx, long contentLength, List<Object> out) {
563 String lengthHex = Long.toHexString(contentLength);
564 ByteBuf buf = ctx.alloc().buffer(lengthHex.length() + 2);
565 buf.writeCharSequence(lengthHex, CharsetUtil.US_ASCII);
566 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
567 out.add(buf);
568 }
569
570
571
572
573 protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
574
575 }
576
577
578
579
580
581
582
583
584 protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") H msg) {
585 return false;
586 }
587
588 @Override
589 @SuppressWarnings("ConditionCoveredByFurtherCondition")
590 public boolean acceptOutboundMessage(Object msg) throws Exception {
591 return msg == Unpooled.EMPTY_BUFFER ||
592 msg == LastHttpContent.EMPTY_LAST_CONTENT ||
593 msg instanceof FullHttpMessage ||
594 msg instanceof HttpMessage ||
595 msg instanceof LastHttpContent ||
596 msg instanceof HttpContent ||
597 msg instanceof ByteBuf || msg instanceof FileRegion;
598 }
599
600
601
602
603
604
605
606
607 private static int padSizeForAccumulation(int readableBytes) {
608 return (readableBytes << 2) / 3;
609 }
610
611 @Deprecated
612 protected static void encodeAscii(String s, ByteBuf buf) {
613 buf.writeCharSequence(s, CharsetUtil.US_ASCII);
614 }
615
616 protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
617 }