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