Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions samples/KestrelHttp2DetachDemo/Http2Frame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Buffers;
using System.Text;

namespace KestrelHttp2DetachDemo;

internal enum Http2FrameType : byte
{
Data = 0x0,
Settings = 0x4
}

internal enum Http2FrameFlags : byte
{
None = 0x0,
Ack = 0x1,
EndStream = 0x1
}

internal sealed class Http2Frame
{
public const int HeaderLength = 9;

public Http2Frame(int length, Http2FrameType type, byte flags, int streamId, byte[] payload)
{
Length = length;
Type = type;
Flags = flags;
StreamId = streamId;
Payload = payload;
}

public int Length { get; }

public Http2FrameType Type { get; }

public byte Flags { get; }

public int StreamId { get; }

public byte[] Payload { get; }

public bool IsSettingsAck => Type == Http2FrameType.Settings && (Flags & (byte)Http2FrameFlags.Ack) != 0;

public string PayloadText => Encoding.UTF8.GetString(Payload);

public static Http2Frame Read(ReadOnlySequence<byte> buffer)
{
Span<byte> header = stackalloc byte[HeaderLength];
buffer.Slice(0, HeaderLength).CopyTo(header);

var length = ReadUInt24(header);
var type = (Http2FrameType)header[3];
var flags = header[4];
var streamId = ReadInt31(header.Slice(5, 4));
var payload = buffer.Slice(HeaderLength, length).ToArray();

return new Http2Frame(length, type, flags, streamId, payload);
}

public static byte[] Write(Http2FrameType type, byte flags, int streamId, ReadOnlySpan<byte> payload)
{
var frame = new byte[HeaderLength + payload.Length];
WriteUInt24(frame, payload.Length);
frame[3] = (byte)type;
frame[4] = flags;
WriteInt31(frame.AsSpan(5, 4), streamId);
payload.CopyTo(frame.AsSpan(HeaderLength));
return frame;
}

private static int ReadUInt24(ReadOnlySpan<byte> source)
{
return (source[0] << 16) | (source[1] << 8) | source[2];
}

private static void WriteUInt24(Span<byte> destination, int value)
{
destination[0] = (byte)((value >> 16) & 0xff);
destination[1] = (byte)((value >> 8) & 0xff);
destination[2] = (byte)(value & 0xff);
}

private static int ReadInt31(ReadOnlySpan<byte> source)
{
return ((source[0] & 0x7f) << 24) | (source[1] << 16) | (source[2] << 8) | source[3];
}

private static void WriteInt31(Span<byte> destination, int value)
{
destination[0] = (byte)((value >> 24) & 0x7f);
destination[1] = (byte)((value >> 16) & 0xff);
destination[2] = (byte)((value >> 8) & 0xff);
destination[3] = (byte)(value & 0xff);
}
}
78 changes: 78 additions & 0 deletions samples/KestrelHttp2DetachDemo/Http2PipelineFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Buffers;
using System.Text;
using SuperSocket.ProtoBase;

namespace KestrelHttp2DetachDemo;

internal sealed class Http2PipelineFilter : PipelineFilterBase<Http2Frame>
{
private static readonly byte[] ClientPreface = Encoding.ASCII.GetBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");

private readonly bool _expectClientPreface;
private bool _clientPrefaceRead;
private bool _foundHeader;
private int _totalSize;

public Http2PipelineFilter(bool expectClientPreface = true)
{
_expectClientPreface = expectClientPreface;
}

public override Http2Frame Filter(ref SequenceReader<byte> reader)
{
if (_expectClientPreface && !_clientPrefaceRead)
{
if (reader.Remaining < ClientPreface.Length)
return null!;

Span<byte> preface = stackalloc byte[24];
reader.Sequence.Slice(reader.Position, ClientPreface.Length).CopyTo(preface);

if (!preface.SequenceEqual(ClientPreface))
throw new ProtocolException("The client did not send a valid HTTP/2 connection preface.");

reader.Advance(ClientPreface.Length);
_clientPrefaceRead = true;
}

if (!_foundHeader)
{
if (reader.Remaining < Http2Frame.HeaderLength)
return null!;

var header = reader.Sequence.Slice(reader.Position, Http2Frame.HeaderLength);
var bodyLength = GetBodyLengthFromHeader(header);

if (bodyLength < 0)
throw new ProtocolException("Failed to get body length from the HTTP/2 frame header.");

_foundHeader = true;
_totalSize = Http2Frame.HeaderLength + bodyLength;
}

if (reader.Remaining < _totalSize)
return null!;

var package = reader.Sequence.Slice(reader.Position, _totalSize);
var frame = Http2Frame.Read(package);
reader.Advance(_totalSize);
_foundHeader = false;
_totalSize = 0;

return frame;
}

public override void Reset()
{
base.Reset();
_foundHeader = false;
_totalSize = 0;
}

private static int GetBodyLengthFromHeader(ReadOnlySequence<byte> headerBuffer)
{
Span<byte> header = stackalloc byte[3];
headerBuffer.Slice(0, 3).CopyTo(header);
return (header[0] << 16) | (header[1] << 8) | header[2];
}
}
18 changes: 18 additions & 0 deletions samples/KestrelHttp2DetachDemo/KestrelHttp2DetachDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(SamplesTargetFramework)</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Description>Demonstrates a minimal HTTP/2 preface and SETTINGS handshake over SuperSocket.Kestrel, then DetachAsync and exact raw data transfer over the same Kestrel transport.</Description>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="../../src/SuperSocket.Connection/SuperSocket.Connection.csproj" />
<ProjectReference Include="../../src/SuperSocket.Kestrel/SuperSocket.Kestrel.csproj" />
<ProjectReference Include="../../src/SuperSocket.ProtoBase/SuperSocket.ProtoBase.csproj" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
</Project>
Loading
Loading