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 
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             // don't consider the copyContent case here: the statistics is just related the headers
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                 // merge into other buffer for performance reasons
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                     // fall-through!
379                 case ST_CONTENT_ALWAYS_EMPTY:
380                     // Need to produce some output otherwise an
381                     // IllegalStateException will be thrown as we did not write anything
382                     // Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be
383                     // propagated as the caller of the encode(...) method will release the original
384                     // buffer.
385                     // Writing an empty buffer will not actually write anything on the wire, so if there is a user
386                     // error with msg it will not be visible externally
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     // Bypass the encoder in case of an empty buffer, so that the following idiom works:
401     //
402     //     ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
403     //
404     // See https://github.com/netty/netty/issues/2983 for more information.
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                 // fall-through!
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             // Need to produce some output otherwise an
496             // IllegalStateException will be thrown
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         // Encode the message.
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      * Encode the {@link HttpHeaders} into a {@link ByteBuf}.
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             // Need to produce some output otherwise an
553             // IllegalStateException will be thrown
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      * Allows to sanitize headers of the message before encoding these.
568      */
569     protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {
570         // noop
571     }
572 
573     /**
574      * Determine whether a message has a content or not. Some message may have headers indicating
575      * a content without having an actual content, e.g the response to an HEAD or CONNECT request.
576      *
577      * @param msg the message to test
578      * @return {@code true} to signal the message has no content
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      * Add some additional overhead to the buffer. The rational is that it is better to slightly over allocate and waste
598      * some memory, rather than under allocate and require a resize/copy.
599      *
600      * @param readableBytes The readable bytes in the buffer.
601      * @return The {@code readableBytes} with some additional padding.
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 }