in src/Utility/System.Net.ServerSentEvents.cs [396:519]
private bool ProcessLine(out SseItem<T> sseItem, out int advance)
{
ReadOnlySpan<byte> line = _lineBuffer.AsSpan(_lineOffset, _newlineIndex - _lineOffset);
// Spec: "If the line is empty (a blank line) Dispatch the event"
if (line.IsEmpty)
{
advance = GetNewLineLength();
if (_dataAppended)
{
sseItem = new SseItem<T>(_itemParser(_eventType, _dataBuffer.AsSpan(0, _dataLength)), _eventType);
_eventType = SseParser.EventTypeDefault;
_dataLength = 0;
_dataAppended = false;
return true;
}
sseItem = default;
return false;
}
// Find the colon separating the field name and value.
int colonPos = line.IndexOf((byte)':');
ReadOnlySpan<byte> fieldName;
ReadOnlySpan<byte> fieldValue;
if (colonPos >= 0)
{
// Spec: "Collect the characters on the line before the first U+003A COLON character (:), and let field be that string."
fieldName = line.Slice(0, colonPos);
// Spec: "Collect the characters on the line after the first U+003A COLON character (:), and let value be that string.
// If value starts with a U+0020 SPACE character, remove it from value."
fieldValue = line.Slice(colonPos + 1);
if (!fieldValue.IsEmpty && fieldValue[0] == (byte)' ')
{
fieldValue = fieldValue.Slice(1);
}
}
else
{
// Spec: "using the whole line as the field name, and the empty string as the field value."
fieldName = line;
fieldValue = [];
}
if (fieldName.SequenceEqual("data"u8))
{
// Spec: "Append the field value to the data buffer, then append a single U+000A LINE FEED (LF) character to the data buffer."
// Spec: "If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer."
// If there's nothing currently in the data buffer and we can easily detect that this line is immediately followed by
// an empty line, we can optimize it to just handle the data directly from the line buffer, rather than first copying
// into the data buffer and dispatching from there.
if (!_dataAppended)
{
int newlineLength = GetNewLineLength();
ReadOnlySpan<byte> remainder = _lineBuffer.AsSpan(_newlineIndex + newlineLength, _lineLength - line.Length - newlineLength);
if (!remainder.IsEmpty &&
(remainder[0] is LF || (remainder[0] is CR && remainder.Length > 1)))
{
advance = line.Length + newlineLength + (remainder.StartsWith(CRLF) ? 2 : 1);
sseItem = new SseItem<T>(_itemParser(_eventType, fieldValue), _eventType);
_eventType = SseParser.EventTypeDefault;
return true;
}
}
// We need to copy the data from the data buffer to the line buffer. Make sure there's enough room.
if (_dataBuffer is null || _dataLength + _lineLength + 1 > _dataBuffer.Length)
{
GrowBuffer(ref _dataBuffer, _dataLength + _lineLength + 1);
}
// Append a newline if there's already content in the buffer.
// Then copy the field value to the data buffer
if (_dataAppended)
{
_dataBuffer![_dataLength++] = LF;
}
fieldValue.CopyTo(_dataBuffer.AsSpan(_dataLength));
_dataLength += fieldValue.Length;
_dataAppended = true;
}
else if (fieldName.SequenceEqual("event"u8))
{
// Spec: "Set the event type buffer to field value."
_eventType = SseParser.Utf8GetString(fieldValue);
}
else if (fieldName.SequenceEqual("id"u8))
{
// Spec: "If the field value does not contain U+0000 NULL, then set the last event ID buffer to the field value. Otherwise, ignore the field."
if (fieldValue.IndexOf((byte)'\0') < 0)
{
// Note that fieldValue might be empty, in which case LastEventId will naturally be reset to the empty string. This is per spec.
LastEventId = SseParser.Utf8GetString(fieldValue);
}
}
else if (fieldName.SequenceEqual("retry"u8))
{
// Spec: "If the field value consists of only ASCII digits, then interpret the field value as an integer in base ten,
// and set the event stream's reconnection time to that integer. Otherwise, ignore the field."
if (long.TryParse(
#if NET7_0_OR_GREATER
fieldValue,
#else
SseParser.Utf8GetString(fieldValue),
#endif
NumberStyles.None, CultureInfo.InvariantCulture, out long milliseconds))
{
ReconnectionInterval = TimeSpan.FromMilliseconds(milliseconds);
}
}
else
{
// We'll end up here if the line starts with a colon, producing an empty field name, or if the field name is otherwise unrecognized.
// Spec: "If the line starts with a U+003A COLON character (:) Ignore the line."
// Spec: "Otherwise, The field is ignored"
}
advance = line.Length + GetNewLineLength();
sseItem = default;
return false;
}