Merge pull request #105 from jeoffman/dev

Porting in DelimiterBasedFrameDecoder from Java (redo)
This commit is contained in:
Max Gortman 2016-05-06 10:13:02 -07:00
Родитель 45c8c70649 7bbfe1bddf
Коммит 49308fb644
7 изменённых файлов: 330 добавлений и 5 удалений

Просмотреть файл

@ -137,7 +137,7 @@ namespace DotNetty.Codecs
}
}
protected abstract void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output);
protected internal abstract void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output);
static IByteBuffer ExpandCumulation(IByteBufferAllocator allocator, IByteBuffer cumulation, int readable)
{

Просмотреть файл

@ -0,0 +1,291 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
namespace DotNetty.Codecs
{
/// <summary>
/// A decoder that splits the received <see cref="DotNetty.Buffers.IByteBuffer"/> by one or more
/// delimiters.It is particularly useful for decoding the frames which ends
/// with a delimiter such as <see cref = "DotNetty.Codecs.Delimiters.NullDelimiter" /> or
/// <see cref = "DotNetty.Codecs.Delimiters.LineDelimiter" />
/// --- Specifying more than one delimiter </ h3 >
/// <see cref = "DotNetty.Codecs.Delimiters.NullDelimiter" /> allows you to specify more than one
/// delimiter. If more than one delimiter is found in the buffer, it chooses
/// the delimiter which produces the shortest frame. For example, if you have
/// the following data in the buffer:
/// +--------------+
/// | ABC\nDEF\r\n |
/// +--------------+
/// a <see cref = "DotNetty.Codecs.Delimiters.LineDelimiter" /> will choose '\n' as the first delimiter and produce two frames:
/// +-----+-----+
/// | ABC | DEF |
/// +-----+-----+
/// rather than incorrectly choosing '\r\n' as the first delimiter:
/// +----------+
/// | ABC\nDEF |
/// +----------+
/// </summary>
public class DelimiterBasedFrameDecoder : ByteToMessageDecoder
{
IByteBuffer[] delimiters;
int maxFrameLength;
bool stripDelimiter;
bool failFast;
bool discardingTooLongFrame;
int tooLongFrameLength;
LineBasedFrameDecoder lineBasedDecoder; // Set only when decoding with "\n" and "\r\n" as the delimiter.
/// <summary>Common constructor</summary>
/// <param name="maxFrameLength">The maximum length of the decoded frame
/// NOTE: A see <cref="DotNetty.Codecs.TooLongFrameException" /> is thrown if the length of the frame exceeds this value.</param>
/// <param name="stripDelimiter">whether the decoded frame should strip out the delimiter or not</param>
/// <param name="failFast">If true, a <cref="DotNetty.Codecs.TooLongFrameException" /> is
/// thrown as soon as the decoder notices the length of the
/// frame will exceed<tt>maxFrameLength</tt> regardless of
/// whether the entire frame has been read.
/// If false, a <cref="DotNetty.Codecs.TooLongFrameException" /> is
/// thrown after the entire frame that exceeds maxFrameLength has been read.</param>
/// <param name="delimiter">the delimiter</param>
public DelimiterBasedFrameDecoder(int maxFrameLength, bool stripDelimiter, bool failFast, params IByteBuffer[] delimiters)
{
ValidateMaxFrameLength(maxFrameLength);
if(delimiters == null)
throw new NullReferenceException("delimiters");
if(delimiters.Length == 0)
throw new ArgumentException("empty delimiters");
if(IsLineBased(delimiters) && !IsSubclass())
{
lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
this.delimiters = null;
}
else
{
this.delimiters = new IByteBuffer[delimiters.Length];
for(int i = 0; i < delimiters.Length; i++)
{
IByteBuffer d = delimiters[i];
ValidateDelimiter(d);
this.delimiters[i] = d.Slice(d.ReaderIndex, d.ReadableBytes);
}
lineBasedDecoder = null;
}
this.maxFrameLength = maxFrameLength;
this.stripDelimiter = stripDelimiter;
this.failFast = failFast;
}
public DelimiterBasedFrameDecoder(int maxFrameLength, IByteBuffer delimiter)
: this(maxFrameLength, true, true, new IByteBuffer[] { delimiter })
{
}
public DelimiterBasedFrameDecoder(int maxFrameLength, bool stripDelimiter, IByteBuffer delimiter)
: this(maxFrameLength, stripDelimiter, true, new IByteBuffer[] { delimiter })
{
}
public DelimiterBasedFrameDecoder(int maxFrameLength, bool stripDelimiter, bool failFast, IByteBuffer delimiter)
: this(maxFrameLength, true, failFast, new IByteBuffer[] { delimiter })
{
}
public DelimiterBasedFrameDecoder(int maxFrameLength, params IByteBuffer[] delimiters)
: this(maxFrameLength, true, true, delimiters )
{
}
public DelimiterBasedFrameDecoder(int maxFrameLength, bool stripDelimiter, params IByteBuffer[] delimiters)
: this(maxFrameLength, stripDelimiter, true, delimiters)
{
}
/// <summary>Returns true if the delimiters are "\n" and "\r\n"</summary>
static bool IsLineBased(IByteBuffer[] delimiters)
{
if (delimiters.Length != 2)
{
return false;
}
IByteBuffer a = delimiters[0];
IByteBuffer b = delimiters[1];
if (a.Capacity < b.Capacity)
{
a = delimiters[1];
b = delimiters[0];
}
return a.Capacity == 2 && b.Capacity == 1 && a.GetByte(0) == '\r' && a.GetByte(1) == '\n' && b.GetByte(0) == '\n';
}
/// <summary>ReturnsReturn true if the current instance is a subclass of DelimiterBasedFrameDecoder</summary>
bool IsSubclass()
{
return (this is DelimiterBasedFrameDecoder);
}
override protected internal void Decode(IChannelHandlerContext ctx, IByteBuffer input, List<object> output)
{
var decoded = Decode(ctx, input);
if (decoded != null)
output.Add(decoded);
}
/// <summary>Create a frame out of the <see cref="DotNetty.Buffers.IByteBuffer"/> and return it</summary>
/// <param name="ctx">the <see cref="DotNetty.Transport.Channels.IChannelHandlerContext" /> which this <see cref="DotNetty.Codecs.ByteToMessageDecoder"/> belongs to</param>
/// <param name="buffer">the <see cref="DotNetty.Buffers.IByteBuffer" /> from which to read data</param>
/// <returns>the <see cref="DotNetty.Buffers.IByteBuffer" /> which represent the frame or null if no frame could be created.</returns>
protected object Decode(IChannelHandlerContext ctx, IByteBuffer buffer)
{
if (lineBasedDecoder != null)
{
return lineBasedDecoder.Decode(ctx, buffer);
}
// Try all delimiters and choose the delimiter which yields the shortest frame.
int minFrameLength = int.MaxValue;
IByteBuffer minDelim = null;
foreach (IByteBuffer delim in delimiters)
{
int frameLength = IndexOf(buffer, delim);
if (frameLength >= 0 && frameLength < minFrameLength)
{
minFrameLength = frameLength;
minDelim = delim;
}
}
if (minDelim != null)
{
int minDelimLength = minDelim.Capacity;
IByteBuffer frame;
if (discardingTooLongFrame)
{
// We've just finished discarding a very large frame.
// Go back to the initial state.
discardingTooLongFrame = false;
buffer.SkipBytes(minFrameLength + minDelimLength);
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast)
{
Fail(tooLongFrameLength);
}
return null;
}
if (minFrameLength > this.maxFrameLength)
{
// Discard read frame.
buffer.SkipBytes(minFrameLength + minDelimLength);
Fail(minFrameLength);
return null;
}
if (stripDelimiter)
{
frame = buffer.ReadSlice(minFrameLength);
buffer.SkipBytes(minDelimLength);
}
else
{
frame = buffer.ReadSlice(minFrameLength + minDelimLength);
}
return frame.Retain();
}
else
{
if (!discardingTooLongFrame)
{
if (buffer.ReadableBytes > maxFrameLength)
{
// Discard the content of the buffer until a delimiter is found.
tooLongFrameLength = buffer.ReadableBytes;
buffer.SkipBytes(buffer.ReadableBytes);
discardingTooLongFrame = true;
if (failFast)
{
Fail(tooLongFrameLength);
}
}
}
else
{
// Still discarding the buffer since a delimiter is not found.
tooLongFrameLength += buffer.ReadableBytes;
buffer.SkipBytes(buffer.ReadableBytes);
}
return null;
}
}
void Fail(long frameLength)
{
if (frameLength > 0)
throw new TooLongFrameException("frame length exceeds " + maxFrameLength +": " + frameLength + " - discarded");
else
throw new TooLongFrameException("frame length exceeds " + maxFrameLength +" - discarding");
}
/**
* Returns the number of bytes between the readerIndex of the haystack and
* the first needle found in the haystack. -1 is returned if no needle is
* found in the haystack.
*/
static int IndexOf(IByteBuffer haystack, IByteBuffer needle)
{
for (int i = haystack.ReaderIndex; i < haystack.WriterIndex; i++)
{
int haystackIndex = i;
int needleIndex;
for (needleIndex = 0; needleIndex < needle.Capacity; needleIndex++)
{
if (haystack.GetByte(haystackIndex) != needle.GetByte(needleIndex))
{
break;
}
else
{
haystackIndex++;
if (haystackIndex == haystack.WriterIndex && needleIndex != needle.Capacity - 1)
{
return -1;
}
}
}
if (needleIndex == needle.Capacity)
{
// Found the needle from the haystack!
return i - haystack.ReaderIndex;
}
}
return -1;
}
static void ValidateDelimiter(IByteBuffer delimiter)
{
if (delimiter == null)
throw new NullReferenceException("delimiter");
if (!delimiter.IsReadable())
throw new ArgumentException("empty delimiter");
}
static void ValidateMaxFrameLength(int maxFrameLength)
{
if (maxFrameLength <= 0)
throw new ArgumentException("maxFrameLength must be a positive integer: " + maxFrameLength);
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using DotNetty.Buffers;
namespace DotNetty.Codecs
{
public class Delimiters
{
/// <summary>Returns a null (0x00) delimiter, which could be used for Flash XML socket or any similar protocols</summary>
public static IByteBuffer[] NullDelimiter()
{
return new IByteBuffer[] { Unpooled.WrappedBuffer(new byte[] { 0 }) };
}
/// <summary>Returns {@code CR ('\r')} and {@code LF ('\n')} delimiters, which could
/// be used for text-based line protocols.</summary>
public static IByteBuffer[] LineDelimiter()
{
return new IByteBuffer[]
{
Unpooled.WrappedBuffer(new byte[] { (byte)'\r', (byte)'\n' }),
Unpooled.WrappedBuffer(new byte[] { (byte)'\n' }),
};
}
private Delimiters()
{
// Unused
}
}
}

Просмотреть файл

@ -46,6 +46,8 @@
<Compile Include="CodecException.cs" />
<Compile Include="CorruptedFrameException.cs" />
<Compile Include="DecoderException.cs" />
<Compile Include="DelimiterBasedFrameDecoder.cs" />
<Compile Include="Delimiters.cs" />
<Compile Include="EncoderException.cs" />
<Compile Include="Json\JsonObjectDecoder.cs" />
<Compile Include="LengthFieldBasedFrameDecoder.cs" />

Просмотреть файл

@ -57,7 +57,7 @@ namespace DotNetty.Codecs.Json
this.streamArrayElements = streamArrayElements;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
protected internal override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
if (this.state == StCorrupted)
{

Просмотреть файл

@ -287,7 +287,7 @@ namespace DotNetty.Codecs
this.failFast = failFast;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
protected internal override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
object decoded = this.Decode(context, input);
if (decoded != null)

Просмотреть файл

@ -66,7 +66,7 @@ namespace DotNetty.Codecs
this.stripDelimiter = stripDelimiter;
}
protected sealed override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
protected internal sealed override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
object decode = this.Decode(context, input);
if (decode != null)
@ -80,7 +80,7 @@ namespace DotNetty.Codecs
/// </summary>
/// <param name="ctx">the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to</param>
/// <param name="buffer">the {@link ByteBuf} from which to read data</param>
protected object Decode(IChannelHandlerContext ctx, IByteBuffer buffer)
protected internal object Decode(IChannelHandlerContext ctx, IByteBuffer buffer)
{
int eol = this.FindEndOfLine(buffer);
if (!this.discarding)