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 }