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