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