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.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   * Encodes an {@link HttpMessage} or an {@link HttpContent} into
43   * a {@link ByteBuf}.
44   *
45   * <h3>Extensibility</h3>
46   *
47   * Please note that this encoder is designed to be extended to implement
48   * a protocol derived from HTTP, such as
49   * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
50   * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
51   * To implement the encoder of such a derived protocol, extend this class and
52   * implement all abstract methods properly.
53   */
54  public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {
55  
56      // this is a constant to decide when it is appropriate to copy the data content into the header buffer
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       * Used to calculate an exponential moving average of the encoded size of the initial line and the headers for
80       * a guess for future buffer allocations.
81       */
82      private float headersEncodedSizeAccumulator = 256;
83  
84      /**
85       * Used to calculate an exponential moving average of the encoded size of the trailers for
86       * a guess for future buffer allocations.
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                 // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
124                 // See https://github.com/netty/netty/issues/2525
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         // fast-path for common idiom that doesn't require class-checks
155         if (msg == Unpooled.EMPTY_BUFFER) {
156             out.add(Unpooled.EMPTY_BUFFER);
157             return;
158         }
159         // The reason why we perform instanceof checks in this order,
160         // by duplicating some code and without relying on ReferenceCountUtil::release as a generic release
161         // mechanism, is https://bugs.openjdk.org/browse/JDK-8180450.
162         // https://github.com/netty/netty/issues/12708 contains more detail re how the previous version of this
163         // code was interacting with the JIT instanceof optimizations.
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                 // fall-through!
212             case ST_CONTENT_ALWAYS_EMPTY:
213                 // We allocated a buffer so add it now.
214                 out.add(buf);
215                 break;
216             case ST_CONTENT_CHUNK:
217                 // We allocated a buffer so add it now.
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                                         // try embed the content if less or equals than
321                                         // the biggest of ~12.5% of the header estimated size and COPY_DATA_THRESHOLD:
322                                         // it limits a wrong estimation to waste too much memory
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             boolean handedOff = false;
330             try {
331                 encodeInitialLine(buf, m);
332 
333                 sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
334 
335                 encodeHeaders(m.headers(), buf);
336                 ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
337 
338                 // don't consider the copyContent case here: the statistics is just related the headers
339                 headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
340                         HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
341 
342                 handedOff = true;
343                 encodeByteBufHttpContent(state, ctx, buf, content, msg.trailingHeaders(), out);
344             } finally {
345                 if (!handedOff) {
346                     buf.release();
347                 }
348             }
349         } finally {
350             msg.release();
351         }
352     }
353 
354     private static boolean encodeContentNonChunk(List<Object> out, ByteBuf buf, ByteBuf content) {
355         final int contentLength = content.readableBytes();
356         if (contentLength > 0) {
357             if (buf.maxFastWritableBytes() >= contentLength) {
358                 // merge into other buffer for performance reasons
359                 buf.writeBytes(content);
360                 out.add(buf);
361             } else {
362                 out.add(buf);
363                 out.add(content.retain());
364             }
365             return true;
366         }
367         return false;
368     }
369 
370     private static void throwUnexpectedMessageTypeEx(Object msg, int state) {
371         throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg)
372                 + ", state: " + state);
373     }
374 
375     private void encodeFileRegionContent(ChannelHandlerContext ctx, FileRegion msg, List<Object> out) {
376         try {
377             assert state != ST_INIT;
378             switch (state) {
379                 case ST_CONTENT_NON_CHUNK:
380                     if (msg.count() > 0) {
381                         out.add(msg.retain());
382                         break;
383                     }
384 
385                     // fall-through!
386                 case ST_CONTENT_ALWAYS_EMPTY:
387                     // Need to produce some output otherwise an
388                     // IllegalStateException will be thrown as we did not write anything
389                     // Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be
390                     // propagated as the caller of the encode(...) method will release the original
391                     // buffer.
392                     // Writing an empty buffer will not actually write anything on the wire, so if there is a user
393                     // error with msg it will not be visible externally
394                     out.add(Unpooled.EMPTY_BUFFER);
395                     break;
396                 case ST_CONTENT_CHUNK:
397                     encodedChunkedFileRegionContent(ctx, msg, out);
398                     break;
399                 default:
400                     throw new Error();
401             }
402         } finally {
403             msg.release();
404         }
405     }
406 
407     // Bypass the encoder in case of an empty buffer, so that the following idiom works:
408     //
409     //     ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
410     //
411     // See https://github.com/netty/netty/issues/2983 for more information.
412     private static boolean bypassEncoderIfEmpty(ByteBuf msg, List<Object> out) {
413         if (!msg.isReadable()) {
414             out.add(msg.retain());
415             return true;
416         }
417         return false;
418     }
419 
420     private void encodeByteBufContent(ChannelHandlerContext ctx, ByteBuf content, List<Object> out) {
421         try {
422             assert state != ST_INIT;
423             if (bypassEncoderIfEmpty(content, out)) {
424                 return;
425             }
426             encodeByteBufAndTrailers(state, ctx, out, content, null);
427         } finally {
428             content.release();
429         }
430     }
431 
432     private static int encodeEmptyLastHttpContent(int state, List<Object> out) {
433         assert state != ST_INIT;
434 
435         switch (state) {
436             case ST_CONTENT_NON_CHUNK:
437             case ST_CONTENT_ALWAYS_EMPTY:
438                 out.add(Unpooled.EMPTY_BUFFER);
439                 break;
440             case ST_CONTENT_CHUNK:
441                 out.add(ZERO_CRLF_CRLF_BUF.duplicate());
442                 break;
443             default:
444                 throw new Error();
445         }
446         return ST_INIT;
447     }
448 
449     private void encodeLastHttpContent(ChannelHandlerContext ctx, LastHttpContent msg, List<Object> out) {
450         assert state != ST_INIT;
451         assert !(msg instanceof HttpMessage);
452         try {
453             encodeByteBufAndTrailers(state, ctx, out, msg.content(), msg.trailingHeaders());
454             state = ST_INIT;
455         } finally {
456             msg.release();
457         }
458     }
459 
460     private void encodeHttpContent(ChannelHandlerContext ctx, HttpContent msg, List<Object> out) {
461         assert state != ST_INIT;
462         assert !(msg instanceof HttpMessage);
463         assert !(msg instanceof LastHttpContent);
464         try {
465             this.encodeByteBufAndTrailers(state, ctx, out, msg.content(), null);
466         } finally {
467             msg.release();
468         }
469     }
470 
471     private void encodeByteBufAndTrailers(int state, ChannelHandlerContext ctx, List<Object> out, ByteBuf content,
472                                           HttpHeaders trailingHeaders) {
473         switch (state) {
474             case ST_CONTENT_NON_CHUNK:
475                 if (content.isReadable()) {
476                     out.add(content.retain());
477                     break;
478                 }
479                 // fall-through!
480             case ST_CONTENT_ALWAYS_EMPTY:
481                 out.add(Unpooled.EMPTY_BUFFER);
482                 break;
483             case ST_CONTENT_CHUNK:
484                 encodeChunkedHttpContent(ctx, content, trailingHeaders, out);
485                 break;
486             default:
487                 throw new Error();
488         }
489     }
490 
491     private void encodeChunkedHttpContent(ChannelHandlerContext ctx, ByteBuf content, HttpHeaders trailingHeaders,
492                                           List<Object> out) {
493         final int contentLength = content.readableBytes();
494         if (contentLength > 0) {
495             addEncodedLengthHex(ctx, contentLength, out);
496             out.add(content.retain());
497             out.add(CRLF_BUF.duplicate());
498         }
499         if (trailingHeaders != null) {
500             encodeTrailingHeaders(ctx, trailingHeaders, out);
501         } else if (contentLength == 0) {
502             // Need to produce some output otherwise an
503             // IllegalStateException will be thrown
504             out.add(content.retain());
505         }
506     }
507 
508     private void encodeTrailingHeaders(ChannelHandlerContext ctx, HttpHeaders trailingHeaders, List<Object> out) {
509         if (trailingHeaders.isEmpty()) {
510             out.add(ZERO_CRLF_CRLF_BUF.duplicate());
511         } else {
512             ByteBuf buf = ctx.alloc().buffer((int) trailersEncodedSizeAccumulator);
513             ByteBufUtil.writeMediumBE(buf, ZERO_CRLF_MEDIUM);
514             encodeHeaders(trailingHeaders, buf);
515             ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
516             trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
517                     TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;
518             out.add(buf);
519         }
520     }
521 
522     private ByteBuf encodeInitHttpMessage(ChannelHandlerContext ctx, H m) throws Exception {
523         assert state == ST_INIT;
524 
525         ByteBuf buf = ctx.alloc().buffer((int) headersEncodedSizeAccumulator);
526         boolean success = false;
527         try {
528             // Encode the message.
529             encodeInitialLine(buf, m);
530             state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :
531                     HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
532 
533             sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);
534 
535             encodeHeaders(m.headers(), buf);
536             ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
537 
538             headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +
539                     HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;
540             success = true;
541             return buf;
542         } finally {
543             if (!success) {
544                 buf.release();
545             }
546         }
547     }
548 
549     /**
550      * Encode the {@link HttpHeaders} into a {@link ByteBuf}.
551      */
552     protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) {
553         Iterator<Entry<CharSequence, CharSequence>> iter = headers.iteratorCharSequence();
554         while (iter.hasNext()) {
555             Entry<CharSequence, CharSequence> header = iter.next();
556             HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
557         }
558     }
559 
560     private static void encodedChunkedFileRegionContent(ChannelHandlerContext ctx, FileRegion msg, List<Object> out) {
561         final long contentLength = msg.count();
562         if (contentLength > 0) {
563             addEncodedLengthHex(ctx, contentLength, out);
564             out.add(msg.retain());
565             out.add(CRLF_BUF.duplicate());
566         } else if (contentLength == 0) {
567             // Need to produce some output otherwise an
568             // IllegalStateException will be thrown
569             out.add(msg.retain());
570         }
571     }
572 
573     private static void addEncodedLengthHex(ChannelHandlerContext ctx, long contentLength, List<Object> out) {
574         String lengthHex = Long.toHexString(contentLength);
575         ByteBuf buf = ctx.alloc().buffer(lengthHex.length() + 2);
576         buf.writeCharSequence(lengthHex, CharsetUtil.US_ASCII);
577         ByteBufUtil.writeShortBE(buf, CRLF_SHORT);
578         out.add(buf);
579     }
580 
581     /**
582      * Allows to sanitize headers of the message before encoding these.
583      */
584     protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
585         // noop
586     }
587 
588     /**
589      * Determine whether a message has a content or not. Some message may have headers indicating
590      * a content without having an actual content, e.g the response to an HEAD or CONNECT request.
591      *
592      * @param msg the message to test
593      * @return {@code true} to signal the message has no content
594      */
595     protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") H msg) {
596         return false;
597     }
598 
599     @Override
600     @SuppressWarnings("ConditionCoveredByFurtherCondition")
601     public boolean acceptOutboundMessage(Object msg) throws Exception {
602         return msg == Unpooled.EMPTY_BUFFER ||
603                 msg == LastHttpContent.EMPTY_LAST_CONTENT ||
604                 msg instanceof FullHttpMessage ||
605                 msg instanceof HttpMessage ||
606                 msg instanceof LastHttpContent ||
607                 msg instanceof HttpContent ||
608                 msg instanceof ByteBuf || msg instanceof FileRegion;
609     }
610 
611     /**
612      * Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
613      * some memory, rather than under allocate and require a resize/copy.
614      *
615      * @param readableBytes The readable bytes in the buffer.
616      * @return The {@code readableBytes} with some additional padding.
617      */
618     private static int padSizeForAccumulation(int readableBytes) {
619         return (readableBytes << 2) / 3;
620     }
621 
622     @Deprecated
623     protected static void encodeAscii(String s, ByteBuf buf) {
624         buf.writeCharSequence(s, CharsetUtil.US_ASCII);
625     }
626 
627     protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;
628 }