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