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.Channels;
21  import org.jboss.netty.channel.ChannelHandlerContext;
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  
40      /**
41       * Creates a new decoder.
42       * @param maxLength  the maximum length of the decoded frame.
43       *                   A {@link TooLongFrameException} is thrown if
44       *                   the length of the frame exceeds this value.
45       */
46      public LineBasedFrameDecoder(final int maxLength) {
47          this(maxLength, true, false);
48      }
49  
50      /**
51       * Creates a new decoder.
52       * @param maxLength  the maximum length of the decoded frame.
53       *                   A {@link TooLongFrameException} is thrown if
54       *                   the length of the frame exceeds this value.
55       * @param stripDelimiter  whether the decoded frame should strip out the
56       *                        delimiter or not
57       * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
58       *                  thrown as soon as the decoder notices the length of the
59       *                  frame will exceed <tt>maxFrameLength</tt> regardless of
60       *                  whether the entire frame has been read.
61       *                  If <tt>false</tt>, a {@link TooLongFrameException} is
62       *                  thrown after the entire frame that exceeds
63       *                  <tt>maxFrameLength</tt> has been read.
64       */
65      public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
66          this.maxLength = maxLength;
67          this.failFast = failFast;
68          this.stripDelimiter = stripDelimiter;
69      }
70  
71      @Override
72      protected Object decode(final ChannelHandlerContext ctx,
73                              final Channel channel,
74                              final ChannelBuffer buffer) throws Exception {
75          final int eol = findEndOfLine(buffer);
76          if (eol != -1) {
77              final ChannelBuffer frame;
78              final int length = eol - buffer.readerIndex();
79              assert length >= 0: "Invalid length=" + length;
80              if (discarding) {
81                  frame = null;
82                  buffer.skipBytes(length);
83                  if (!failFast) {
84                      fail(ctx, "over " + (maxLength + length) + " bytes");
85                  }
86              } else {
87                  int delimLength;
88                  final byte delim = buffer.getByte(buffer.readerIndex() + length);
89                  if (delim == '\r') {
90                      delimLength = 2;  // Skip the \r\n.
91                  } else {
92                      delimLength = 1;
93                  }
94                  if (stripDelimiter) {
95                      frame = extractFrame(buffer, buffer.readerIndex(), length);
96                  } else {
97                      frame = extractFrame(buffer, buffer.readerIndex(), length + delimLength);
98                  }
99                  buffer.skipBytes(length + delimLength);
100             }
101             return frame;
102         }
103 
104         final int buffered = buffer.readableBytes();
105         if (!discarding && buffered > maxLength) {
106             discarding = true;
107             if (failFast) {
108                 fail(ctx, buffered + " bytes buffered already");
109             }
110         }
111         if (discarding) {
112             buffer.skipBytes(buffer.readableBytes());
113         }
114         return null;
115     }
116 
117     private void fail(final ChannelHandlerContext ctx, final String msg) {
118         Channels.fireExceptionCaught(ctx.getChannel(),
119             new TooLongFrameException("Frame length exceeds " + maxLength + " ("
120                                       + msg + ')'));
121     }
122 
123     /**
124      * Returns the index in the buffer of the end of line found.
125      * Returns -1 if no end of line was found in the buffer.
126      */
127     private static int findEndOfLine(final ChannelBuffer buffer) {
128         final int n = buffer.writerIndex();
129         for (int i = buffer.readerIndex(); i < n; i ++) {
130             final byte b = buffer.getByte(i);
131             if (b == '\n') {
132                 return i;
133             } else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
134                 return i;  // \r\n
135             }
136         }
137         return -1;  // Not found.
138     }
139 
140 }