View Javadoc

1   /*
2    * Copyright 2009 Red Hat, Inc.
3    *
4    * Red Hat licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * 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   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
59   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
60   *
61   * @version $Rev:231 $, $Date:2008-06-12 16:44:50 +0900 (목, 12 6월 2008) $
62   *
63   * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
64   */
65  public class DelimiterBasedFrameDecoder extends FrameDecoder {
66  
67      private final ChannelBuffer[] delimiters;
68      private final int maxFrameLength;
69      private final boolean stripDelimiter;
70      private boolean discardingTooLongFrame;
71      private int tooLongFrameLength;
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, ChannelBuffer 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, ChannelBuffer delimiter) {
97          validateMaxFrameLength(maxFrameLength);
98          validateDelimiter(delimiter);
99          delimiters = new ChannelBuffer[] {
100                 delimiter.slice(
101                         delimiter.readerIndex(), delimiter.readableBytes())
102         };
103         this.maxFrameLength = maxFrameLength;
104         this.stripDelimiter = stripDelimiter;
105     }
106 
107     /**
108      * Creates a new instance.
109      *
110      * @param maxFrameLength  the maximum length of the decoded frame.
111      *                        A {@link TooLongFrameException} is thrown if
112      *                        the length of the frame exceeds this value.
113      * @param delimiters  the delimiters
114      */
115     public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) {
116         this(maxFrameLength, true, delimiters);
117     }
118 
119     /**
120      * Creates a new instance.
121      *
122      * @param maxFrameLength  the maximum length of the decoded frame.
123      *                        A {@link TooLongFrameException} is thrown if
124      *                        the length of the frame exceeds this value.
125      * @param stripDelimiter  whether the decoded frame should strip out the
126      *                        delimiter or not
127      * @param delimiters  the delimiters
128      */
129     public DelimiterBasedFrameDecoder(
130             int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) {
131         validateMaxFrameLength(maxFrameLength);
132         if (delimiters == null) {
133             throw new NullPointerException("delimiters");
134         }
135         if (delimiters.length == 0) {
136             throw new IllegalArgumentException("empty delimiters");
137         }
138         this.delimiters = new ChannelBuffer[delimiters.length];
139         for (int i = 0; i < delimiters.length; i ++) {
140             ChannelBuffer d = delimiters[i];
141             validateDelimiter(d);
142             this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
143         }
144         this.maxFrameLength = maxFrameLength;
145         this.stripDelimiter = stripDelimiter;
146     }
147 
148     @Override
149     protected Object decode(
150             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
151         // Try all delimiters and choose the delimiter which yields the shortest frame.
152         int minFrameLength = Integer.MAX_VALUE;
153         ChannelBuffer minDelim = null;
154         for (ChannelBuffer delim: delimiters) {
155             int frameLength = indexOf(buffer, delim);
156             if (frameLength >= 0 && frameLength < minFrameLength) {
157                 minFrameLength = frameLength;
158                 minDelim = delim;
159             }
160         }
161 
162         if (minDelim != null) {
163             int minDelimLength = minDelim.capacity();
164             ChannelBuffer frame;
165 
166             if (discardingTooLongFrame) {
167                 // We've just finished discarding a very large frame.
168                 // Go back to the initial state.
169                 discardingTooLongFrame = false;
170                 buffer.skipBytes(minFrameLength + minDelimLength);
171 
172                 // TODO Let user choose when the exception should be raised - early or late?
173                 //      If early, fail() should be called when discardingTooLongFrame is set to true.
174                 int tooLongFrameLength = this.tooLongFrameLength;
175                 this.tooLongFrameLength = 0;
176                 fail(ctx, tooLongFrameLength);
177                 return null;
178             }
179 
180             if (minFrameLength > maxFrameLength) {
181                 // Discard read frame.
182                 buffer.skipBytes(minFrameLength + minDelimLength);
183                 fail(ctx, minFrameLength);
184                 return null;
185             }
186 
187             if (stripDelimiter) {
188                 frame = buffer.readBytes(minFrameLength);
189                 buffer.skipBytes(minDelimLength);
190             } else {
191                 frame = buffer.readBytes(minFrameLength + minDelimLength);
192             }
193 
194             return frame;
195         } else {
196             if (!discardingTooLongFrame) {
197                 if (buffer.readableBytes() > maxFrameLength) {
198                     // Discard the content of the buffer until a delimiter is found.
199                     tooLongFrameLength = buffer.readableBytes();
200                     buffer.skipBytes(buffer.readableBytes());
201                     discardingTooLongFrame = true;
202                 }
203             } else {
204                 // Still discarding the buffer since a delimiter is not found.
205                 tooLongFrameLength += buffer.readableBytes();
206                 buffer.skipBytes(buffer.readableBytes());
207             }
208             return null;
209         }
210     }
211 
212     private void fail(ChannelHandlerContext ctx, long frameLength) {
213         if (frameLength > 0) {
214             Channels.fireExceptionCaught(
215                     ctx.getChannel(),
216                     new TooLongFrameException(
217                             "frame length exceeds " + maxFrameLength +
218                             ": " + frameLength + " - discarded"));
219         } else {
220             Channels.fireExceptionCaught(
221                     ctx.getChannel(),
222                     new TooLongFrameException(
223                             "frame length exceeds " + maxFrameLength +
224                             " - discarding"));
225         }
226     }
227 
228     /**
229      * Returns the number of bytes between the readerIndex of the haystack and
230      * the first needle found in the haystack.  -1 is returned if no needle is
231      * found in the haystack.
232      */
233     private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
234         for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
235             int haystackIndex = i;
236             int needleIndex;
237             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
238                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
239                     break;
240                 } else {
241                     haystackIndex ++;
242                     if (haystackIndex == haystack.writerIndex() &&
243                         needleIndex != needle.capacity() - 1) {
244                         return -1;
245                     }
246                 }
247             }
248 
249             if (needleIndex == needle.capacity()) {
250                 // Found the needle from the haystack!
251                 return i - haystack.readerIndex();
252             }
253         }
254         return -1;
255     }
256 
257     private static void validateDelimiter(ChannelBuffer delimiter) {
258         if (delimiter == null) {
259             throw new NullPointerException("delimiter");
260         }
261         if (!delimiter.readable()) {
262             throw new IllegalArgumentException("empty delimiter");
263         }
264     }
265 
266     private static void validateMaxFrameLength(int maxFrameLength) {
267         if (maxFrameLength <= 0) {
268             throw new IllegalArgumentException(
269                     "maxFrameLength must be a positive integer: " +
270                     maxFrameLength);
271         }
272     }
273 }