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