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    *   http://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;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelHandlerContext;
20  
21  import java.util.List;
22  
23  /**
24   * A decoder that splits the received {@link ByteBuf}s on line endings.
25   * <p>
26   * Both {@code "\n"} and {@code "\r\n"} are handled.
27   * For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}.
28   */
29  public class LineBasedFrameDecoder extends ByteToMessageDecoder {
30  
31      /** Maximum length of a frame we're willing to decode.  */
32      private final int maxLength;
33      /** Whether or not to throw an exception as soon as we exceed maxLength. */
34      private final boolean failFast;
35      private final boolean stripDelimiter;
36  
37      /** True if we're discarding input because we're already over maxLength.  */
38      private boolean discarding;
39      private int discardedBytes;
40  
41      /**
42       * Creates a new decoder.
43       * @param maxLength  the maximum length of the decoded frame.
44       *                   A {@link TooLongFrameException} is thrown if
45       *                   the length of the frame exceeds this value.
46       */
47      public LineBasedFrameDecoder(final int maxLength) {
48          this(maxLength, true, false);
49      }
50  
51      /**
52       * Creates a new decoder.
53       * @param maxLength  the maximum length of the decoded frame.
54       *                   A {@link TooLongFrameException} is thrown if
55       *                   the length of the frame exceeds this value.
56       * @param stripDelimiter  whether the decoded frame should strip out the
57       *                        delimiter or not
58       * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
59       *                  thrown as soon as the decoder notices the length of the
60       *                  frame will exceed <tt>maxFrameLength</tt> regardless of
61       *                  whether the entire frame has been read.
62       *                  If <tt>false</tt>, a {@link TooLongFrameException} is
63       *                  thrown after the entire frame that exceeds
64       *                  <tt>maxFrameLength</tt> has been read.
65       */
66      public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
67          this.maxLength = maxLength;
68          this.failFast = failFast;
69          this.stripDelimiter = stripDelimiter;
70      }
71  
72      @Override
73      protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
74          Object decoded = decode(ctx, in);
75          if (decoded != null) {
76              out.add(decoded);
77          }
78      }
79  
80      /**
81       * Create a frame out of the {@link ByteBuf} and return it.
82       *
83       * @param   ctx             the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
84       * @param   buffer          the {@link ByteBuf} from which to read data
85       * @return  frame           the {@link ByteBuf} which represent the frame or {@code null} if no frame could
86       *                          be created.
87       */
88      protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
89          final int eol = findEndOfLine(buffer);
90          if (!discarding) {
91              if (eol >= 0) {
92                  final ByteBuf frame;
93                  final int length = eol - buffer.readerIndex();
94                  final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
95  
96                  if (length > maxLength) {
97                      buffer.readerIndex(eol + delimLength);
98                      fail(ctx, length);
99                      return null;
100                 }
101 
102                 if (stripDelimiter) {
103                     frame = buffer.readSlice(length);
104                     buffer.skipBytes(delimLength);
105                 } else {
106                     frame = buffer.readSlice(length + delimLength);
107                 }
108 
109                 return frame.retain();
110             } else {
111                 final int length = buffer.readableBytes();
112                 if (length > maxLength) {
113                     discardedBytes = length;
114                     buffer.readerIndex(buffer.writerIndex());
115                     discarding = true;
116                     if (failFast) {
117                         fail(ctx, "over " + discardedBytes);
118                     }
119                 }
120                 return null;
121             }
122         } else {
123             if (eol >= 0) {
124                 final int length = discardedBytes + eol - buffer.readerIndex();
125                 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
126                 buffer.readerIndex(eol + delimLength);
127                 discardedBytes = 0;
128                 discarding = false;
129                 if (!failFast) {
130                     fail(ctx, length);
131                 }
132             } else {
133                 discardedBytes = buffer.readableBytes();
134                 buffer.readerIndex(buffer.writerIndex());
135             }
136             return null;
137         }
138     }
139 
140     private void fail(final ChannelHandlerContext ctx, int length) {
141         fail(ctx, String.valueOf(length));
142     }
143 
144     private void fail(final ChannelHandlerContext ctx, String length) {
145         ctx.fireExceptionCaught(
146                 new TooLongFrameException(
147                         "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
148     }
149 
150     /**
151      * Returns the index in the buffer of the end of line found.
152      * Returns -1 if no end of line was found in the buffer.
153      */
154     private static int findEndOfLine(final ByteBuf buffer) {
155         final int n = buffer.writerIndex();
156         for (int i = buffer.readerIndex(); i < n; i ++) {
157             final byte b = buffer.getByte(i);
158             if (b == '\n') {
159                 return i;
160             } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
161                 return i;  // \r\n
162             }
163         }
164         return -1;  // Not found.
165     }
166 }