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 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   * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
    59   */
    60  public class DelimiterBasedFrameDecoder extends FrameDecoder {
    61
    62      private final ChannelBuffer[] delimiters;
    63      private final int maxFrameLength;
    64      private final boolean stripDelimiter;
    65      private final boolean failFast;
    66      private boolean discardingTooLongFrame;
    67      private int tooLongFrameLength;
    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, ChannelBuffer 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, ChannelBuffer delimiter) {
    93          this(maxFrameLength, stripDelimiter, false, 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             ChannelBuffer delimiter) {
    116         validateMaxFrameLength(maxFrameLength);
    117         validateDelimiter(delimiter);
    118         delimiters = new ChannelBuffer[] {
    119                 delimiter.slice(
    120                         delimiter.readerIndex(), delimiter.readableBytes())
    121         };
    122         this.maxFrameLength = maxFrameLength;
    123         this.stripDelimiter = stripDelimiter;
    124         this.failFast = failFast;
    125     }
    126
    127     /**
    128      * Creates a new instance.
    129      *
    130      * @param maxFrameLength  the maximum length of the decoded frame.
    131      *                        A {@link TooLongFrameException} is thrown if
    132      *                        the length of the frame exceeds this value.
    133      * @param delimiters  the delimiters
    134      */
    135     public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) {
    136         this(maxFrameLength, true, delimiters);
    137     }
    138
    139     /**
    140      * Creates a new instance.
    141      *
    142      * @param maxFrameLength  the maximum length of the decoded frame.
    143      *                        A {@link TooLongFrameException} is thrown if
    144      *                        the length of the frame exceeds this value.
    145      * @param stripDelimiter  whether the decoded frame should strip out the
    146      *                        delimiter or not
    147      * @param delimiters  the delimiters
    148      */
    149     public DelimiterBasedFrameDecoder(
    150             int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) {
    151         this(maxFrameLength, stripDelimiter, false, delimiters);
    152     }
    153
    154     /**
    155      * Creates a new instance.
    156      *
    157      * @param maxFrameLength  the maximum length of the decoded frame.
    158      *                        A {@link TooLongFrameException} is thrown if
    159      *                        the length of the frame exceeds this value.
    160      * @param stripDelimiter  whether the decoded frame should strip out the
    161      *                        delimiter or not
    162      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
    163      *                  thrown as soon as the decoder notices the length of the
    164      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
    165      *                  whether the entire frame has been read.
    166      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
    167      *                  thrown after the entire frame that exceeds
    168      *                  <tt>maxFrameLength</tt> has been read.
    169      * @param delimiters  the delimiters
    170      */
    171     public DelimiterBasedFrameDecoder(
    172             int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {
    173         validateMaxFrameLength(maxFrameLength);
    174         if (delimiters == null) {
    175             throw new NullPointerException("delimiters");
    176         }
    177         if (delimiters.length == 0) {
    178             throw new IllegalArgumentException("empty delimiters");
    179         }
    180         this.delimiters = new ChannelBuffer[delimiters.length];
    181         for (int i = 0; i < delimiters.length; i ++) {
    182             ChannelBuffer d = delimiters[i];
    183             validateDelimiter(d);
    184             this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
    185         }
    186         this.maxFrameLength = maxFrameLength;
    187         this.stripDelimiter = stripDelimiter;
    188         this.failFast = failFast;
    189     }
    190
    191     @Override
    192     protected Object decode(
    193             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
    194         // Try all delimiters and choose the delimiter which yields the shortest frame.
    195         int minFrameLength = Integer.MAX_VALUE;
    196         ChannelBuffer minDelim = null;
    197         for (ChannelBuffer delim: delimiters) {
    198             int frameLength = indexOf(buffer, delim);
    199             if (frameLength >= 0 && frameLength < minFrameLength) {
    200                 minFrameLength = frameLength;
    201                 minDelim = delim;
    202             }
    203         }
    204
    205         if (minDelim != null) {
    206             int minDelimLength = minDelim.capacity();
    207             ChannelBuffer frame;
    208
    209             if (discardingTooLongFrame) {
    210                 // We've just finished discarding a very large frame.
    211                 // Go back to the initial state.
    212                 discardingTooLongFrame = false;
    213                 buffer.skipBytes(minFrameLength + minDelimLength);
    214
    215                 int tooLongFrameLength = this.tooLongFrameLength;
    216                 this.tooLongFrameLength = 0;
    217                 if (!failFast) {
    218                     fail(ctx, tooLongFrameLength);
    219                 }
    220                 return null;
    221             }
    222
    223             if (minFrameLength > maxFrameLength) {
    224                 // Discard read frame.
    225                 buffer.skipBytes(minFrameLength + minDelimLength);
    226                 fail(ctx, minFrameLength);
    227                 return null;
    228             }
    229
    230             if (stripDelimiter) {
    231                 frame = buffer.readBytes(minFrameLength);
    232                 buffer.skipBytes(minDelimLength);
    233             } else {
    234                 frame = buffer.readBytes(minFrameLength + minDelimLength);
    235             }
    236
    237             return frame;
    238         } else {
    239             if (!discardingTooLongFrame) {
    240                 if (buffer.readableBytes() > maxFrameLength) {
    241                     // Discard the content of the buffer until a delimiter is found.
    242                     tooLongFrameLength = buffer.readableBytes();
    243                     buffer.skipBytes(buffer.readableBytes());
    244                     discardingTooLongFrame = true;
    245                     if (failFast) {
    246                         fail(ctx, tooLongFrameLength);
    247                     }
    248                 }
    249             } else {
    250                 // Still discarding the buffer since a delimiter is not found.
    251                 tooLongFrameLength += buffer.readableBytes();
    252                 buffer.skipBytes(buffer.readableBytes());
    253             }
    254             return null;
    255         }
    256     }
    257
    258     private void fail(ChannelHandlerContext ctx, long frameLength) {
    259         if (frameLength > 0) {
    260             Channels.fireExceptionCaught(
    261                     ctx.getChannel(),
    262                     new TooLongFrameException(
    263                             "frame length exceeds " + maxFrameLength +
    264                             ": " + frameLength + " - discarded"));
    265         } else {
    266             Channels.fireExceptionCaught(
    267                     ctx.getChannel(),
    268                     new TooLongFrameException(
    269                             "frame length exceeds " + maxFrameLength +
    270                             " - discarding"));
    271         }
    272     }
    273
    274     /**
    275      * Returns the number of bytes between the readerIndex of the haystack and
    276      * the first needle found in the haystack.  -1 is returned if no needle is
    277      * found in the haystack.
    278      */
    279     private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
    280         for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
    281             int haystackIndex = i;
    282             int needleIndex;
    283             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
    284                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
    285                     break;
    286                 } else {
    287                     haystackIndex ++;
    288                     if (haystackIndex == haystack.writerIndex() &&
    289                         needleIndex != needle.capacity() - 1) {
    290                         return -1;
    291                     }
    292                 }
    293             }
    294
    295             if (needleIndex == needle.capacity()) {
    296                 // Found the needle from the haystack!
    297                 return i - haystack.readerIndex();
    298             }
    299         }
    300         return -1;
    301     }
    302
    303     private static void validateDelimiter(ChannelBuffer delimiter) {
    304         if (delimiter == null) {
    305             throw new NullPointerException("delimiter");
    306         }
    307         if (!delimiter.readable()) {
    308             throw new IllegalArgumentException("empty delimiter");
    309         }
    310     }
    311
    312     private static void validateMaxFrameLength(int maxFrameLength) {
    313         if (maxFrameLength <= 0) {
    314             throw new IllegalArgumentException(
    315                     "maxFrameLength must be a positive integer: " +
    316                     maxFrameLength);
    317         }
    318     }
    319 }