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 by one or more
25   * delimiters.  It is particularly useful for decoding the frames which ends
26   * with a delimiter such as {@link Delimiters#nulDelimiter() NUL} or
27   * {@linkplain Delimiters#lineDelimiter() newline characters}.
28   *
29   * <h3>Predefined delimiters</h3>
30   * <p>
31   * {@link Delimiters} defines frequently used delimiters for convenience' sake.
32   *
33   * <h3>Specifying more than one delimiter</h3>
34   * <p>
35   * {@link DelimiterBasedFrameDecoder} allows you to specify more than one
36   * delimiter.  If more than one delimiter is found in the buffer, it chooses
37   * the delimiter which produces the shortest frame.  For example, if you have
38   * the following data in the buffer:
39   * <pre>
40   * +--------------+
41   * | ABC\nDEF\r\n |
42   * +--------------+
43   * </pre>
44   * a {@link DelimiterBasedFrameDecoder}{@code (}{@link Delimiters#lineDelimiter() Delimiters.lineDelimiter()}{@code )}
45   * will choose {@code '\n'} as the first delimiter and produce two frames:
46   * <pre>
47   * +-----+-----+
48   * | ABC | DEF |
49   * +-----+-----+
50   * </pre>
51   * rather than incorrectly choosing {@code '\r\n'} as the first delimiter:
52   * <pre>
53   * +----------+
54   * | ABC\nDEF |
55   * +----------+
56   * </pre>
57   *
58   * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
59   */
60  public class DelimiterBasedFrameDecoder extends FrameDecoder {
61  
62      private final ChannelBuffer[] delimiters;
63      private final int maxFrameLength;
64      private final boolean stripDelimiter;
65      private final boolean failFast;
66      private boolean discardingTooLongFrame;
67      private int tooLongFrameLength;
68  
69      /**
70       * Creates a new instance.
71       *
72       * @param maxFrameLength  the maximum length of the decoded frame.
73       *                        A {@link TooLongFrameException} is thrown if
74       *                        the length of the frame exceeds this value.
75       * @param delimiter  the delimiter
76       */
77      public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer delimiter) {
78          this(maxFrameLength, true, delimiter);
79      }
80  
81      /**
82       * Creates a new instance.
83       *
84       * @param maxFrameLength  the maximum length of the decoded frame.
85       *                        A {@link TooLongFrameException} is thrown if
86       *                        the length of the frame exceeds this value.
87       * @param stripDelimiter  whether the decoded frame should strip out the
88       *                        delimiter or not
89       * @param delimiter  the delimiter
90       */
91      public DelimiterBasedFrameDecoder(
92              int maxFrameLength, boolean stripDelimiter, ChannelBuffer delimiter) {
93          this(maxFrameLength, stripDelimiter, false, delimiter);
94      }
95  
96      /**
97       * Creates a new instance.
98       *
99       * @param maxFrameLength  the maximum length of the decoded frame.
100      *                        A {@link TooLongFrameException} is thrown if
101      *                        the length of the frame exceeds this value.
102      * @param stripDelimiter  whether the decoded frame should strip out the
103      *                        delimiter or not
104      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
105      *                  thrown as soon as the decoder notices the length of the
106      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
107      *                  whether the entire frame has been read.
108      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
109      *                  thrown after the entire frame that exceeds
110      *                  <tt>maxFrameLength</tt> has been read.
111      * @param delimiter  the delimiter
112      */
113     public DelimiterBasedFrameDecoder(
114             int maxFrameLength, boolean stripDelimiter, boolean failFast,
115             ChannelBuffer delimiter) {
116         validateMaxFrameLength(maxFrameLength);
117         validateDelimiter(delimiter);
118         delimiters = new ChannelBuffer[] {
119                 delimiter.slice(
120                         delimiter.readerIndex(), delimiter.readableBytes())
121         };
122         this.maxFrameLength = maxFrameLength;
123         this.stripDelimiter = stripDelimiter;
124         this.failFast = failFast;
125     }
126 
127     /**
128      * Creates a new instance.
129      *
130      * @param maxFrameLength  the maximum length of the decoded frame.
131      *                        A {@link TooLongFrameException} is thrown if
132      *                        the length of the frame exceeds this value.
133      * @param delimiters  the delimiters
134      */
135     public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) {
136         this(maxFrameLength, true, delimiters);
137     }
138 
139     /**
140      * Creates a new instance.
141      *
142      * @param maxFrameLength  the maximum length of the decoded frame.
143      *                        A {@link TooLongFrameException} is thrown if
144      *                        the length of the frame exceeds this value.
145      * @param stripDelimiter  whether the decoded frame should strip out the
146      *                        delimiter or not
147      * @param delimiters  the delimiters
148      */
149     public DelimiterBasedFrameDecoder(
150             int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) {
151         this(maxFrameLength, stripDelimiter, false, delimiters);
152     }
153 
154     /**
155      * Creates a new instance.
156      *
157      * @param maxFrameLength  the maximum length of the decoded frame.
158      *                        A {@link TooLongFrameException} is thrown if
159      *                        the length of the frame exceeds this value.
160      * @param stripDelimiter  whether the decoded frame should strip out the
161      *                        delimiter or not
162      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
163      *                  thrown as soon as the decoder notices the length of the
164      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
165      *                  whether the entire frame has been read.
166      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
167      *                  thrown after the entire frame that exceeds
168      *                  <tt>maxFrameLength</tt> has been read.
169      * @param delimiters  the delimiters
170      */
171     public DelimiterBasedFrameDecoder(
172             int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {
173         validateMaxFrameLength(maxFrameLength);
174         if (delimiters == null) {
175             throw new NullPointerException("delimiters");
176         }
177         if (delimiters.length == 0) {
178             throw new IllegalArgumentException("empty delimiters");
179         }
180         this.delimiters = new ChannelBuffer[delimiters.length];
181         for (int i = 0; i < delimiters.length; i ++) {
182             ChannelBuffer d = delimiters[i];
183             validateDelimiter(d);
184             this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
185         }
186         this.maxFrameLength = maxFrameLength;
187         this.stripDelimiter = stripDelimiter;
188         this.failFast = failFast;
189     }
190 
191     @Override
192     protected Object decode(
193             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
194         // Try all delimiters and choose the delimiter which yields the shortest frame.
195         int minFrameLength = Integer.MAX_VALUE;
196         ChannelBuffer minDelim = null;
197         for (ChannelBuffer delim: delimiters) {
198             int frameLength = indexOf(buffer, delim);
199             if (frameLength >= 0 && frameLength < minFrameLength) {
200                 minFrameLength = frameLength;
201                 minDelim = delim;
202             }
203         }
204 
205         if (minDelim != null) {
206             int minDelimLength = minDelim.capacity();
207             ChannelBuffer frame;
208 
209             if (discardingTooLongFrame) {
210                 // We've just finished discarding a very large frame.
211                 // Go back to the initial state.
212                 discardingTooLongFrame = false;
213                 buffer.skipBytes(minFrameLength + minDelimLength);
214 
215                 int tooLongFrameLength = this.tooLongFrameLength;
216                 this.tooLongFrameLength = 0;
217                 if (!failFast) {
218                     fail(ctx, tooLongFrameLength);
219                 }
220                 return null;
221             }
222 
223             if (minFrameLength > maxFrameLength) {
224                 // Discard read frame.
225                 buffer.skipBytes(minFrameLength + minDelimLength);
226                 fail(ctx, minFrameLength);
227                 return null;
228             }
229 
230             if (stripDelimiter) {
231                 frame = buffer.readBytes(minFrameLength);
232                 buffer.skipBytes(minDelimLength);
233             } else {
234                 frame = buffer.readBytes(minFrameLength + minDelimLength);
235             }
236 
237             return frame;
238         } else {
239             if (!discardingTooLongFrame) {
240                 if (buffer.readableBytes() > maxFrameLength) {
241                     // Discard the content of the buffer until a delimiter is found.
242                     tooLongFrameLength = buffer.readableBytes();
243                     buffer.skipBytes(buffer.readableBytes());
244                     discardingTooLongFrame = true;
245                     if (failFast) {
246                         fail(ctx, tooLongFrameLength);
247                     }
248                 }
249             } else {
250                 // Still discarding the buffer since a delimiter is not found.
251                 tooLongFrameLength += buffer.readableBytes();
252                 buffer.skipBytes(buffer.readableBytes());
253             }
254             return null;
255         }
256     }
257 
258     private void fail(ChannelHandlerContext ctx, long frameLength) {
259         if (frameLength > 0) {
260             Channels.fireExceptionCaught(
261                     ctx.getChannel(),
262                     new TooLongFrameException(
263                             "frame length exceeds " + maxFrameLength +
264                             ": " + frameLength + " - discarded"));
265         } else {
266             Channels.fireExceptionCaught(
267                     ctx.getChannel(),
268                     new TooLongFrameException(
269                             "frame length exceeds " + maxFrameLength +
270                             " - discarding"));
271         }
272     }
273 
274     /**
275      * Returns the number of bytes between the readerIndex of the haystack and
276      * the first needle found in the haystack.  -1 is returned if no needle is
277      * found in the haystack.
278      */
279     private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
280         for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
281             int haystackIndex = i;
282             int needleIndex;
283             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
284                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
285                     break;
286                 } else {
287                     haystackIndex ++;
288                     if (haystackIndex == haystack.writerIndex() &&
289                         needleIndex != needle.capacity() - 1) {
290                         return -1;
291                     }
292                 }
293             }
294 
295             if (needleIndex == needle.capacity()) {
296                 // Found the needle from the haystack!
297                 return i - haystack.readerIndex();
298             }
299         }
300         return -1;
301     }
302 
303     private static void validateDelimiter(ChannelBuffer delimiter) {
304         if (delimiter == null) {
305             throw new NullPointerException("delimiter");
306         }
307         if (!delimiter.readable()) {
308             throw new IllegalArgumentException("empty delimiter");
309         }
310     }
311 
312     private static void validateMaxFrameLength(int maxFrameLength) {
313         if (maxFrameLength <= 0) {
314             throw new IllegalArgumentException(
315                     "maxFrameLength must be a positive integer: " +
316                     maxFrameLength);
317         }
318     }
319 }