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