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 org.jboss.netty.handler.codec.frame;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.channel.Channel;
20  import org.jboss.netty.channel.ChannelHandlerContext;
21  import org.jboss.netty.channel.Channels;
22  
23  /**
24   * A decoder that splits the received {@link ChannelBuffer}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 FrameDecoder {
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 Object decode(final ChannelHandlerContext ctx,
74                              final Channel channel,
75                              final ChannelBuffer buffer) throws Exception {
76          final int eol = findEndOfLine(buffer);
77          if (!discarding) {
78              if (eol >= 0) {
79                  final ChannelBuffer frame;
80                  final int length = eol - buffer.readerIndex();
81                  final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
82  
83                  if (length > maxLength) {
84                      buffer.readerIndex(eol + delimLength);
85                      fail(ctx, length);
86                      return null;
87                  }
88  
89                  try {
90                      if (stripDelimiter) {
91                          frame = extractFrame(buffer, buffer.readerIndex(), length);
92                      } else {
93                          frame = extractFrame(buffer, buffer.readerIndex(), length + delimLength);
94                      }
95                  } finally {
96                      buffer.skipBytes(length + delimLength);
97                  }
98                  return frame;
99              } else {
100                 final int length = buffer.readableBytes();
101                 if (length > maxLength) {
102                     discardedBytes = length;
103                     buffer.readerIndex(buffer.writerIndex());
104                     discarding = true;
105                     if (failFast) {
106                         fail(ctx, "over " + discardedBytes);
107                     }
108                 }
109                 return null;
110             }
111         } else {
112             if (eol >= 0) {
113                 final int length = discardedBytes + eol - buffer.readerIndex();
114                 final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
115                 buffer.readerIndex(eol + delimLength);
116                 discardedBytes = 0;
117                 discarding = false;
118                 if (!failFast) {
119                     fail(ctx, length);
120                 }
121             } else {
122                 discardedBytes = buffer.readableBytes();
123                 buffer.readerIndex(buffer.writerIndex());
124             }
125             return null;
126         }
127     }
128 
129     private void fail(final ChannelHandlerContext ctx, int length) {
130         fail(ctx, String.valueOf(length));
131     }
132 
133     private void fail(final ChannelHandlerContext ctx, String length) {
134         Channels.fireExceptionCaught(
135                 ctx.getChannel(),
136                 new TooLongFrameException(
137                         "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
138     }
139 
140     /**
141      * Returns the index in the buffer of the end of line found.
142      * Returns -1 if no end of line was found in the buffer.
143      */
144     private static int findEndOfLine(final ChannelBuffer buffer) {
145         final int n = buffer.writerIndex();
146         for (int i = buffer.readerIndex(); i < n; i ++) {
147             final byte b = buffer.getByte(i);
148             if (b == '\n') {
149                 return i;
150             } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
151                 return i;  // \r\n
152             }
153         }
154         return -1;  // Not found.
155     }
156 }