question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

New protocol APIs proposal

See original GitHub issue

Hello, first of all thank you very much for this library, I learned a lot reading the wonderful code that it contains.

I have a proposal to make for a new protocol abstraction that is currently published on this repository. I believe it can make implementation of any kind of length prefixed protocol less painful and easier in a different way than with the IMessageReader and IMessageWriter protocol abstractions of @davidfowl.

The logic is contained within the Andromeda.Framing library which provide read/write mechanism to handle any kind of length prefixed protocol.

Both mechanism works around the Frame and Frame<TMetadata> readonly structs. Here is a less verbose version of the frames (without docs) :

public readonly struct Frame
{
    public static readonly Frame Empty = new(ReadOnlySequence<byte>.Empty, default!);

    public Frame(ReadOnlyMemory<byte> payload, IFrameMetadata metadata) =>
        (Payload, Metadata) = (new ReadOnlySequence<byte>(payload), metadata);

    public Frame(ReadOnlySequence<byte> payload, IFrameMetadata metadata) =>
        (Payload, Metadata) = (payload, metadata);

    public ReadOnlySequence<byte> Payload { get; }
    public IFrameMetadata Metadata { get; }

    public bool IsPayloadEmpty() =>  Metadata.Length == 0 && Payload.IsEmpty;
    public bool IsEmptyFrame() => Metadata == default! && Payload.IsEmpty;
}

public readonly struct Frame<TMetadata> where TMetadata : class, IFrameMetadata
{
    public static readonly Frame<TMetadata> Empty = new(ReadOnlySequence<byte>.Empty, default!);

    public Frame(ReadOnlyMemory<byte> payload, TMetadata metadata) =>
        (Payload, Metadata) = (new ReadOnlySequence<byte>(payload), metadata);

    public Frame(ReadOnlySequence<byte> payload, TMetadata metadata) =>
        (Payload, Metadata) = (payload, metadata);

    public ReadOnlySequence<byte> Payload { get; }
    public TMetadata Metadata { get; }

    public bool IsPayloadEmpty() => Metadata.Length == 0 && Payload.IsEmpty;
    public bool IsEmptyFrame() => Metadata == default! && Payload.IsEmpty;
}

How it works

The library provides abstractions that must be implemented for a any kind of protocol such as IFrameMetadata, IMetadataEncoder, IMetadataDecoder, and an IMetadataParser which inherit from both two previous interfaces.

Here is the MetadataParser<TMetadata> base abstraction to implement :

public abstract class MetadataParser<TMetadata> : IMetadataParser where TMetadata : class, IFrameMetadata
{
    public bool TryParse(ref SequenceReader<byte> input, out IFrameMetadata? metadata)
    {
        if (!TryParse(ref input, out TMetadata? meta))
        {
             metadata = default;
            return false;
        }

        metadata = meta;
        return true;
    }


    public void Write(ref Span<byte> span, IFrameMetadata metadata) => Write(ref span, (TMetadata)metadata);
    public int GetLength(IFrameMetadata metadata) => GetLength((TMetadata)metadata);
    public int GetMetadataLength(IFrameMetadata metadata) => GetLength(metadata);
        
    protected abstract bool TryParse(ref SequenceReader<byte> input, out TMetadata? metadata);
    protected abstract void Write(ref Span<byte> span, TMetadata metadata);
    protected abstract int GetLength(TMetadata metadata);
}

Once you’ve a protocol-specific implementation of an IMetadataParser you can use the main mechanism provided by the IFrameEncoder and the IFrameDecoder interfaces.

The first mechanism is implemented by the PipeFrameEncoder class which can be thread synchronizeded (or not) to write frames in a PipeWriter or Stream. A typed implementation also exists to handle typed Frame<TMetadata>.

The second one is implemented by the PipeFrameDecoder class which provides methods to read single frames or consume them via an IAsyncEnumerable<Frame>. No thread synchronization is provided since read are mostly done with loops. A typed implementation also exists to handle typed Frame<TMetadata>.

Here is a pseudo-code sample use using these APIs with untyped decoder/encoder :

public class SomeProtocolHandler : ConnectionHandler
{
    public SomeProtocolHandler(IMetadataParser parser) => _someProtocolParser = parser;
    private readonly IMetadataParser _someProtocolParser;

    public async Task OnConnectedAsync(ConnectionContext connection)
    {
        await using var encoder = connection.Transport.Output.AsFrameEncoder(_someProtocolParser);
        await using var decoder = connection.Transport.Input.AsFrameDecoder(_someProtocolParser);
        
        try
        {
            await foreach(var frame in decoder.ReadFramesAsync(connection.ConnectionClosed))
            {
                var metadata = frame.Metadata as MyProtocolHeader ?? throw new InvalidOperationException("Invalid frame metadata !");
                var response = metadata.MessageId switch {
                    1 => encoder.WriteAsync(in someResponseFrame),
                    2 => encoder.WriteAsync(in anotherResponseFrame),
                    _ => throw new InvalidOperationException($"Message with Id={metadata.MessageId} is not handled !");
                }

                if(response.IsCompletedSuccessfully) continue;
                await response.ConfigureAwait(false);
            }
        }
        catch (ObjectDisposedException) { /* if the encoder throw this it means the connection closed, don't let this out */ }
    }
}

I unit tested and documented most of the code, all the tests can be found on the repository.

Please let me know what you think of my protocols APIs I would really appreciate any kind of review. Of course i’m still learning so it might contains some bad code, and I didn’t benched nor profiled the performance of the whole library so it’s still a todo.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
thenameless314159commented, May 2, 2021

@shaggygi This approach is not about converting objects to payload, I made a serialization library for this purpose.

Here you only have frame metadata parsing logic, the payload to object convert logic is up to you, whether it’s with an implementation of my library or something else (json serialization, payload-less frames with metadata that provides infos to find the relevant message from a different channel…).

I think that I already answered all your questions on my previous comment so you may have to re-read them. I didn’t look your protocol spec in depth but it seems that it’s a length prefixed protocol so my framing lib or the message reader/writer of @davidfowl would be a perfect match to implement it. There are already some implementations of the message reader/writer on this repository to help you build your own. Here is the rabbitMQ protocol implementation : https://github.com/davidfowl/BedrockFramework/tree/master/src/Bedrock.Framework.Experimental/Protocols/RabbitMQ

1reaction
davidfowlcommented, Mar 9, 2021

I took a quick look and it does look quite promising. I’ll take a deeper look and provide more detailed feedback.

Read more comments on GitHub >

github_iconTop Results From Across the Web

APIs and Protocols: What you need to know
An Application Programming Interface (API) is a set of definitions and protocols for building and integrating application software.
Read more >
ECMAScript First-Class Protocols Proposal
a proposal to bring protocol-based interfaces to ECMAScript users ... As of ES2015, new ECMAScript standard library APIs have used a protocol-based design, ......
Read more >
Using Proposed API
Proposed APIs are a set of unstable APIs that are implemented in VS Code but not exposed to the public as stable APIs...
Read more >
API documentation proposal · WebPlatform Docs
This proposal outlines a strategy for building out the API documentation, considering the following: The current state of the “apis” namespace and extant ......
Read more >
Web API design best practices - Azure Architecture Center
This guidance describes issues that you should consider when designing a web API. What is REST? In 2000, Roy Fielding proposed Representational ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found