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      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                 // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
128                 // See https://github.com/netty/netty/issues/2525
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         // fast-path for common idiom that doesn't require class-checks
159         if (msg == Unpooled.EMPTY_BUFFER) {
160             out.add(Unpooled.EMPTY_BUFFER);
161             return;
162         }
163         // The reason why we perform instanceof checks in this order,
164         // by duplicating some code and without relying on ReferenceCountUtil::release as a generic release
165         // mechanism, is https://bugs.openjdk.org/browse/JDK-8180450.
166         // https://github.com/netty/netty/issues/12708 contains more detail re how the previous version of this
167         // code was interacting with the JIT instanceof optimizations.
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                 // fall-through!
216             case ST_CONTENT_ALWAYS_EMPTY:
217                 // We allocated a buffer so add it now.
218                 out.add(buf);
219                 break;
220             case ST_CONTENT_CHUNK:
221                 // We allocated a buffer so add it now.
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                                         // try embed the content if less or equals than
325                                         // the biggest of ~12.5% of the header estimated size and COPY_DATA_THRESHOLD:
326                                         // it limits a wrong estimation to waste too much memory
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             // don't consider the copyContent case here: the statistics is just related the headers
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                 // merge into other buffer for performance reasons
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                     // fall-through!
383                 case ST_CONTENT_ALWAYS_EMPTY:
384                     // Need to produce some output otherwise an
385                     // IllegalStateException will be thrown as we did not write anything
386                     // Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be
387                     // propagated as the caller of the encode(...) method will release the original
388                     // buffer.
389                     // Writing an empty buffer will not actually write anything on the wire, so if there is a user
390                     // error with msg it will not be visible externally
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     // Bypass the encoder in case of an empty buffer, so that the following idiom works:
405     //
406     //     ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
407     //
408     // See https://github.com/netty/netty/issues/2983 for more information.
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                 // fall-through!
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             // Need to produce some output otherwise an
500             // IllegalStateException will be thrown
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         // Encode the message.
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      * Encode the {@link HttpHeaders} into a {@link ByteBuf}.
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             // Need to produce some output otherwise an
557             // IllegalStateException will be thrown
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      * Allows to sanitize headers of the message before encoding these.
572      */
573     protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
574         // noop
575     }
576 
577     /**
578      * Determine whether a message has a content or not. Some message may have headers indicating
579      * a content without having an actual content, e.g the response to an HEAD or CONNECT request.
580      *
581      * @param msg the message to test
582      * @return {@code true} to signal the message has no content
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      * Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
602      * some memory, rather than under allocate and require a resize/copy.
603      *
604      * @param readableBytes The readable bytes in the buffer.
605      * @return The {@code readableBytes} with some additional padding.
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 }