File: ServerSentEventsMessageFormatter.cs
Web Access
Project: ..\..\..\src\aspnetcore\src\SignalR\common\Http.Connections\src\Microsoft.AspNetCore.Http.Connections.csproj (Microsoft.AspNetCore.Http.Connections)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Runtime.CompilerServices;
 
namespace Microsoft.AspNetCore.Http.Connections;
 
internal static class ServerSentEventsMessageFormatter
{
    private static readonly ReadOnlyMemory<byte> DataPrefix = new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)':', (byte)' ' };
    private static readonly ReadOnlyMemory<byte> Newline = new[] { (byte)'\r', (byte)'\n' };
 
    private const byte LineFeed = (byte)'\n';
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Task WriteMessageAsync(ReadOnlySequence<byte> payload, Stream output, CancellationToken token)
    {
        // Payload does not contain a line feed so write it directly to output
        return payload.PositionOf(LineFeed) is null
            ? WriteSingleLineAsync(payload, output, token)
            : WriteMultilineAsync(payload, output, token);
    }
 
    private static async Task WriteSingleLineAsync(ReadOnlySequence<byte> payload, Stream output, CancellationToken token)
    {
        if (payload.Length > 0)
        {
            await output.WriteAsync(DataPrefix, token);
            await output.WriteAsync(payload, token);
            await output.WriteAsync(Newline, token);
        }
 
        await output.WriteAsync(Newline, token);
    }
 
    private static async Task WriteMultilineAsync(ReadOnlySequence<byte> payload, Stream output, CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        var maximumLength = GetFormattedLengthUpperBound(payload);
        var buffer = ArrayPool<byte>.Shared.Rent(maximumLength);
        try
        {
            // Keep the output coalesced, but do not write unused space from trimmed carriage returns.
            var length = WriteMessageToMemory(buffer.AsSpan(0, maximumLength), payload);
            token.ThrowIfCancellationRequested();
            await output.WriteAsync(buffer.AsMemory(0, length), token);
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
 
    private static int GetFormattedLengthUpperBound(ReadOnlySequence<byte> payload)
    {
        var length = checked(payload.Length + DataPrefix.Length + (2 * Newline.Length));
        foreach (var segment in payload)
        {
            // Each LF is replaced by CRLF and a data prefix. Trimming CR can only reduce the length.
            length = checked(length + ((long)segment.Span.Count(LineFeed) * (DataPrefix.Length + Newline.Length - 1)));
        }
 
        return checked((int)length);
    }
 
    /// <summary>
    /// Gets the last memory segment in a sequence.
    /// </summary>
    /// <param name="source">Source sequence.</param>
    /// <param name="offset">The offset the segment starts at.</param>
    /// <returns>The last memory segment in a sequence.</returns>
    private static ReadOnlyMemory<byte> GetLastSegment(in ReadOnlySequence<byte> source, out long offset)
    {
        offset = 0;
 
        var totalLength = source.Length;
        var position = source.Start;
        while (source.TryGet(ref position, out ReadOnlyMemory<byte> memory))
        {
            // Last segment
            if (offset + memory.Length >= totalLength)
            {
                return memory;
            }
 
            offset += memory.Length;
        }
 
        throw new InvalidOperationException("Could not get last segment from sequence.");
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static ReadOnlySequence<byte> ReadLine(ref ReadOnlySequence<byte> payload, out bool hasMore)
    {
        var sliceEnd = payload.PositionOf(LineFeed);
        if (sliceEnd is null)
        {
            var remaining = payload;
            payload = ReadOnlySequence<byte>.Empty;
            hasMore = false;
            return remaining;
        }
 
        var line = payload.Slice(payload.Start, sliceEnd.Value);
        if (line.Length > 1)
        {
            // Preserve the existing CRLF trimming behavior, including across segment boundaries.
            var memory = GetLastSegment(line, out var offset);
            if (memory.Span[memory.Length - 1] == '\r')
            {
                line = line.Slice(line.Start, offset + memory.Length - 1);
            }
        }
 
        payload = payload.Slice(payload.GetPosition(1, sliceEnd.Value));
        hasMore = true;
        return line;
    }
 
    private static int WriteMessageToMemory(Span<byte> output, ReadOnlySequence<byte> payload)
    {
        var initialLength = output.Length;
        bool hasMore;
        do
        {
            var line = ReadLine(ref payload, out hasMore);
            DataPrefix.Span.CopyTo(output);
            output = output.Slice(DataPrefix.Length);
            line.CopyTo(output);
            output = output.Slice((int)line.Length);
            Newline.Span.CopyTo(output);
            output = output.Slice(Newline.Length);
        }
        while (hasMore);
 
        Newline.Span.CopyTo(output);
 
        return initialLength - output.Length + Newline.Length;
    }
}