View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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   * Encodes an {@link HttpMessage} or an {@link HttpContent} into
44   * a {@link ByteBuf}.
45   *
46   * <h3>Extensibility</h3>
47   *
48   * Please note that this encoder is designed to be extended to implement
49   * a protocol derived from HTTP, such as
50   * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
51   * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
52   * To implement the encoder of such a derived protocol, extend this class and
53   * implement all abstract methods properly.
54   */
55  public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
56  
57      // this is a constant to decide when it is appropriate to copy the data content into the header buffer
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       * Used to calculate an exponential moving average of the encoded size of the initial line and the headers for
81       * a guess for future buffer allocations.
82       */
83      private float headersEncodedSizeAccumulator = 256;
84  
85      /**
86       * Used to calculate an exponential moving average of the encoded size of the trailers for
87       * a guess for future buffer allocations.
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                 // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
129                 // See https://github.com/netty/netty/issues/2525
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         // fast-path for common idiom that doesn't require class-checks
160         if (msg == Unpooled.EMPTY_BUFFER) {
161             out.add(Unpooled.EMPTY_BUFFER);
162             return;
163         }
164         // The reason why we perform instanceof checks in this order,
165         // by duplicating some code and without relying on ReferenceCountUtil::release as a generic release
166         // mechanism, is https://bugs.openjdk.org/browse/JDK-8180450.
167         // https://github.com/netty/netty/issues/12708 contains more detail re how the previous version of this
168         // code was interacting with the JIT instanceof optimizations.
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                 // fall-through!
217             case ST_CONTENT_ALWAYS_EMPTY:
218                 // We allocated a buffer so add it now.
219                 out.add(buf);
220                 break;
221             case ST_CONTENT_CHUNK:
222                 // We allocated a buffer so add it now.
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                                         // try embed the content if less or equals than
326                                         // the biggest of ~12.5% of the header estimated size and COPY_DATA_THRESHOLD:
327                                         // it limits a wrong estimation to waste too much memory
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                 // don't consider the copyContent case here: the statistics is just related the headers
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                 // merge into other buffer for performance reasons
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                     // fall-through!
391                 case ST_CONTENT_ALWAYS_EMPTY:
392                     // Need to produce some output otherwise an
393                     // IllegalStateException will be thrown as we did not write anything
394                     // Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be
395                     // propagated as the caller of the encode(...) method will release the original
396                     // buffer.
397                     // Writing an empty buffer will not actually write anything on the wire, so if there is a user
398                     // error with msg it will not be visible externally
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     // Bypass the encoder in case of an empty buffer, so that the following idiom works:
413     //
414     //     ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
415     //
416     // See https://github.com/netty/netty/issues/2983 for more information.
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                 // fall-through!
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             // Need to produce some output otherwise an
508             // IllegalStateException will be thrown
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             // Encode the message.
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      * Encode the {@link HttpHeaders} into a {@link ByteBuf}.
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             // Need to produce some output otherwise an
573             // IllegalStateException will be thrown
574             out.add(msg.retain());
575         }
576     }
577 
578     private static void addEncodedLengthHex(ChannelHandlerContext ctx, long contentLength, List<Object> out) {
579         // logic is from Long.toHexString() but we want to avoid creating a String
580         int hexLen = contentLength == 0 ? 1 : (Long.SIZE - Long.numberOfLeadingZeros(contentLength) + 3) >>> 2;
581         ByteBuf buf = ctx.alloc().buffer(hexLen + 2); // +2 for CRLF
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      * Allows to sanitize headers of the message before encoding these.
599      */
600     protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
601         // noop
602     }
603 
604     /**
605      * Determine whether a message has a content or not. Some message may have headers indicating
606      * a content without having an actual content, e.g the response to an HEAD or CONNECT request.
607      *
608      * @param msg the message to test
609      * @return {@code true} to signal the message has no content
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      * Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
629      * some memory, rather than under allocate and require a resize/copy.
630      *
631      * @param readableBytes The readable bytes in the buffer.
632      * @return The {@code readableBytes} with some additional padding.
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 }