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   *
59   * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
60   */
61  public class DelimiterBasedFrameDecoder extends FrameDecoder {
62  
63      private final ChannelBuffer[] delimiters;
64      private final int maxFrameLength;
65      private final boolean stripDelimiter;
66      private final boolean failFast;
67      private boolean discardingTooLongFrame;
68      private int tooLongFrameLength;
69      /** Set only when decoding with "\n" and "\r\n" as the delimiter.  */
70      private final LineBasedFrameDecoder lineBasedDecoder;
71  
72      /**
73       * Creates a new instance.
74       *
75       * @param maxFrameLength  the maximum length of the decoded frame.
76       *                        A {@link TooLongFrameException} is thrown if
77       *                        the length of the frame exceeds this value.
78       * @param delimiter  the delimiter
79       */
80      public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer delimiter) {
81          this(maxFrameLength, true, delimiter);
82      }
83  
84      /**
85       * Creates a new instance.
86       *
87       * @param maxFrameLength  the maximum length of the decoded frame.
88       *                        A {@link TooLongFrameException} is thrown if
89       *                        the length of the frame exceeds this value.
90       * @param stripDelimiter  whether the decoded frame should strip out the
91       *                        delimiter or not
92       * @param delimiter  the delimiter
93       */
94      public DelimiterBasedFrameDecoder(
95              int maxFrameLength, boolean stripDelimiter, ChannelBuffer delimiter) {
96          this(maxFrameLength, stripDelimiter, false, delimiter);
97      }
98  
99      /**
100      * Creates a new instance.
101      *
102      * @param maxFrameLength  the maximum length of the decoded frame.
103      *                        A {@link TooLongFrameException} is thrown if
104      *                        the length of the frame exceeds this value.
105      * @param stripDelimiter  whether the decoded frame should strip out the
106      *                        delimiter or not
107      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
108      *                  thrown as soon as the decoder notices the length of the
109      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
110      *                  whether the entire frame has been read.
111      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
112      *                  thrown after the entire frame that exceeds
113      *                  <tt>maxFrameLength</tt> has been read.
114      * @param delimiter  the delimiter
115      */
116     public DelimiterBasedFrameDecoder(
117             int maxFrameLength, boolean stripDelimiter, boolean failFast,
118             ChannelBuffer delimiter) {
119         this(maxFrameLength, stripDelimiter, failFast, new ChannelBuffer[] {
120                 delimiter.slice(
121                         delimiter.readerIndex(), delimiter.readableBytes()) });
122     }
123 
124     /**
125      * Creates a new instance.
126      *
127      * @param maxFrameLength  the maximum length of the decoded frame.
128      *                        A {@link TooLongFrameException} is thrown if
129      *                        the length of the frame exceeds this value.
130      * @param delimiters  the delimiters
131      */
132     public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) {
133         this(maxFrameLength, true, delimiters);
134     }
135 
136     /**
137      * Creates a new instance.
138      *
139      * @param maxFrameLength  the maximum length of the decoded frame.
140      *                        A {@link TooLongFrameException} is thrown if
141      *                        the length of the frame exceeds this value.
142      * @param stripDelimiter  whether the decoded frame should strip out the
143      *                        delimiter or not
144      * @param delimiters  the delimiters
145      */
146     public DelimiterBasedFrameDecoder(
147             int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) {
148         this(maxFrameLength, stripDelimiter, false, delimiters);
149     }
150 
151     /**
152      * Creates a new instance.
153      *
154      * @param maxFrameLength  the maximum length of the decoded frame.
155      *                        A {@link TooLongFrameException} is thrown if
156      *                        the length of the frame exceeds this value.
157      * @param stripDelimiter  whether the decoded frame should strip out the
158      *                        delimiter or not
159      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
160      *                  thrown as soon as the decoder notices the length of the
161      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
162      *                  whether the entire frame has been read.
163      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
164      *                  thrown after the entire frame that exceeds
165      *                  <tt>maxFrameLength</tt> has been read.
166      * @param delimiters  the delimiters
167      */
168     public DelimiterBasedFrameDecoder(
169             int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {
170         validateMaxFrameLength(maxFrameLength);
171         if (delimiters == null) {
172             throw new NullPointerException("delimiters");
173         }
174         if (delimiters.length == 0) {
175             throw new IllegalArgumentException("empty delimiters");
176         }
177 
178         if (isLineBased(delimiters) && !isSubclass()) {
179             lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
180             this.delimiters = null;
181         } else {
182             this.delimiters = new ChannelBuffer[delimiters.length];
183             for (int i = 0; i < delimiters.length; i ++) {
184                 ChannelBuffer d = delimiters[i];
185                 validateDelimiter(d);
186                 this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
187             }
188             lineBasedDecoder = null;
189         }
190         this.maxFrameLength = maxFrameLength;
191         this.stripDelimiter = stripDelimiter;
192         this.failFast = failFast;
193     }
194 
195     /** Returns true if the delimiters are "\n" and "\r\n".  */
196     private static boolean isLineBased(final ChannelBuffer[] delimiters) {
197         if (delimiters.length != 2) {
198             return false;
199         }
200         ChannelBuffer a = delimiters[0];
201         ChannelBuffer b = delimiters[1];
202         if (a.capacity() < b.capacity()) {
203             a = delimiters[1];
204             b = delimiters[0];
205         }
206         return a.capacity() == 2 && b.capacity() == 1
207             && a.getByte(0) == '\r' && a.getByte(1) == '\n'
208             && b.getByte(0) == '\n';
209     }
210 
211     /**
212      * Return {@code true} if the current instance is a subclass of DelimiterBasedFrameDecoder
213      */
214     private boolean isSubclass() {
215         return getClass() != DelimiterBasedFrameDecoder.class;
216     }
217 
218     @Override
219     protected Object decode(
220             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
221         if (lineBasedDecoder != null) {
222             return lineBasedDecoder.decode(ctx, channel, buffer);
223         }
224         // Try all delimiters and choose the delimiter which yields the shortest frame.
225         int minFrameLength = Integer.MAX_VALUE;
226         ChannelBuffer minDelim = null;
227         for (ChannelBuffer delim: delimiters) {
228             int frameLength = indexOf(buffer, delim);
229             if (frameLength >= 0 && frameLength < minFrameLength) {
230                 minFrameLength = frameLength;
231                 minDelim = delim;
232             }
233         }
234 
235         if (minDelim != null) {
236             int minDelimLength = minDelim.capacity();
237             ChannelBuffer frame;
238 
239             if (discardingTooLongFrame) {
240                 // We've just finished discarding a very large frame.
241                 // Go back to the initial state.
242                 discardingTooLongFrame = false;
243                 buffer.skipBytes(minFrameLength + minDelimLength);
244 
245                 int tooLongFrameLength = this.tooLongFrameLength;
246                 this.tooLongFrameLength = 0;
247                 if (!failFast) {
248                     fail(ctx, tooLongFrameLength);
249                 }
250                 return null;
251             }
252 
253             if (minFrameLength > maxFrameLength) {
254                 // Discard read frame.
255                 buffer.skipBytes(minFrameLength + minDelimLength);
256                 fail(ctx, minFrameLength);
257                 return null;
258             }
259 
260             if (stripDelimiter) {
261                 frame = extractFrame(buffer, buffer.readerIndex(), minFrameLength);
262             } else {
263                 frame = extractFrame(buffer, buffer.readerIndex(), minFrameLength + minDelimLength);
264             }
265             buffer.skipBytes(minFrameLength + minDelimLength);
266 
267             return frame;
268         } else {
269             if (!discardingTooLongFrame) {
270                 if (buffer.readableBytes() > maxFrameLength) {
271                     // Discard the content of the buffer until a delimiter is found.
272                     tooLongFrameLength = buffer.readableBytes();
273                     buffer.skipBytes(buffer.readableBytes());
274                     discardingTooLongFrame = true;
275                     if (failFast) {
276                         fail(ctx, tooLongFrameLength);
277                     }
278                 }
279             } else {
280                 // Still discarding the buffer since a delimiter is not found.
281                 tooLongFrameLength += buffer.readableBytes();
282                 buffer.skipBytes(buffer.readableBytes());
283             }
284             return null;
285         }
286     }
287 
288     private void fail(ChannelHandlerContext ctx, long frameLength) {
289         if (frameLength > 0) {
290             Channels.fireExceptionCaught(
291                     ctx.getChannel(),
292                     new TooLongFrameException(
293                             "frame length exceeds " + maxFrameLength +
294                             ": " + frameLength + " - discarded"));
295         } else {
296             Channels.fireExceptionCaught(
297                     ctx.getChannel(),
298                     new TooLongFrameException(
299                             "frame length exceeds " + maxFrameLength +
300                             " - discarding"));
301         }
302     }
303 
304     /**
305      * Returns the number of bytes between the readerIndex of the haystack and
306      * the first needle found in the haystack.  -1 is returned if no needle is
307      * found in the haystack.
308      */
309     private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
310         for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
311             int haystackIndex = i;
312             int needleIndex;
313             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
314                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
315                     break;
316                 } else {
317                     haystackIndex ++;
318                     if (haystackIndex == haystack.writerIndex() &&
319                         needleIndex != needle.capacity() - 1) {
320                         return -1;
321                     }
322                 }
323             }
324 
325             if (needleIndex == needle.capacity()) {
326                 // Found the needle from the haystack!
327                 return i - haystack.readerIndex();
328             }
329         }
330         return -1;
331     }
332 
333     private static void validateDelimiter(ChannelBuffer delimiter) {
334         if (delimiter == null) {
335             throw new NullPointerException("delimiter");
336         }
337         if (!delimiter.readable()) {
338             throw new IllegalArgumentException("empty delimiter");
339         }
340     }
341 
342     private static void validateMaxFrameLength(int maxFrameLength) {
343         if (maxFrameLength <= 0) {
344             throw new IllegalArgumentException(
345                     "maxFrameLength must be a positive integer: " +
346                     maxFrameLength);
347         }
348     }
349 }