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