/// <summary>
        /// Attempts to parse a single non-empty line of SSE content that was read as UTF-8 bytes. Empty lines
        /// should not be not passed to this method.
        /// </summary>
        /// <param name="line">a line that was read from the stream, not including any trailing CR/LF</param>
        /// <returns>a <see cref="Result"/> containing the parsed field or comment; <c>ValueBytes</c>
        /// will be set rather than <c>ValueString</c></returns>
        public static Result ParseLineUtf8Bytes(Utf8ByteSpan line)
        {
            if (line.Length > 0 && line.Data[line.Offset] == ':') // comment
            {
                return(new Result {
                    ValueBytes = line
                });
            }
            int colonPos = 0;

            for (; colonPos < line.Length && line.Data[line.Offset + colonPos] != ':'; colonPos++)
            {
            }
            string fieldName = Encoding.UTF8.GetString(line.Data, line.Offset, colonPos);

            if (colonPos == line.Length) // field name without a value - assume empty value
            {
                return(new Result {
                    FieldName = fieldName,
                    ValueBytes = new Utf8ByteSpan()
                });
            }
            int valuePos = colonPos + 1;

            if (valuePos < line.Length && line.Data[line.Offset + valuePos] == ' ')
            {
                valuePos++; // trim a single leading space from the value, if present
            }
            return(new Result
            {
                FieldName = fieldName,
                ValueBytes = new Utf8ByteSpan(line.Data, line.Offset + valuePos, line.Length - valuePos)
            });
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="MessageEvent"/> class,
 /// providing the data as a UTF-8 byte span.
 /// </summary>
 /// <param name="name">the event name</param>
 /// <param name="dataUtf8Bytes">the data received in the server-sent event;
 ///   the <c>MessageEvent</c> will store a reference to the byte array, rather than
 ///   copying it, so it should not be modified afterward by the caller
 /// </param>
 /// <param name="lastEventId">the last event identifier, or null</param>
 /// <param name="origin">the origin URI of the stream</param>
 public MessageEvent(string name, Utf8ByteSpan dataUtf8Bytes, string lastEventId, Uri origin)
 {
     _name          = name;
     _dataString    = null;
     _dataUtf8Bytes = dataUtf8Bytes;
     _lastEventId   = lastEventId;
     _origin        = origin;
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="MessageEvent"/> class.
 /// </summary>
 /// <param name="name">the event name</param>
 /// <param name="data">the data received in the server-sent event</param>
 /// <param name="lastEventId">the last event identifier, or null</param>
 /// <param name="origin">the origin URI of the stream</param>
 public MessageEvent(string name, string data, string lastEventId, Uri origin)
 {
     _name          = name;
     _dataString    = data;
     _dataUtf8Bytes = new Utf8ByteSpan();
     _lastEventId   = lastEventId;
     _origin        = origin;
 }
示例#4
0
 private void ProcessResponseLineUtf8(Utf8ByteSpan content)
 {
     if (content.Length == 0)
     {
         DispatchEvent();
     }
     else
     {
         HandleParsedLine(EventParser.ParseLineUtf8Bytes(content));
     }
 }
        /// <summary>
        /// Tests whether the bytes in this span are the same as another span.
        /// </summary>
        /// <param name="other">Another <c>Utf8ByteSpan</c>.</param>
        /// <returns>True if the two spans have the same length and the same
        /// data, starting from each one's <c>Offset</c>.</returns>
        public bool Equals(Utf8ByteSpan other)
        {
            var len = Length;

            if (len != other.Length)
            {
                return(false);
            }
            int offset = Offset, otherOffset = other.Offset;

            byte[] data = Data, otherData = other.Data;
            for (int i = 0; i < len; i++)
            {
                if (data[i + offset] != otherData[i + otherOffset])
                {
                    return(false);
                }
            }
            return(true);
        }
示例#6
0
        private void DispatchEvent()
        {
            var name = _eventName ?? Constants.MessageField;

            _eventName = null;
            MessageEvent message;

            if (_eventDataStringBuffer != null)
            {
                if (_eventDataStringBuffer.Count == 0)
                {
                    return;
                }
                // remove last item which is always a trailing newline
                _eventDataStringBuffer.RemoveAt(_eventDataStringBuffer.Count - 1);
                var dataString = string.Concat(_eventDataStringBuffer);
                message = new MessageEvent(name, dataString, _lastEventId, _configuration.Uri);

                _eventDataStringBuffer.Clear();
            }
            else
            {
                if (_eventDataUtf8ByteBuffer is null || _eventDataUtf8ByteBuffer.Length == 0)
                {
                    return;
                }
                var dataSpan = new Utf8ByteSpan(_eventDataUtf8ByteBuffer.GetBuffer(), 0,
                                                (int)_eventDataUtf8ByteBuffer.Length - 1); // remove trailing newline
                message = new MessageEvent(name, dataSpan, _lastEventId, _configuration.Uri);

                // We've now taken ownership of the original buffer; null out the previous
                // reference to it so a new one will be created next time
                _eventDataUtf8ByteBuffer = null;
            }

            _logger.Debug("Received event \"{0}\"", name);
            OnMessageReceived(new MessageReceivedEventArgs(message));
        }
        /// <summary>
        /// Searches for the next line ending and, if successful, provides the line data.
        /// </summary>
        /// <param name="lineOut">if successful, this is set to point to the bytes for the line
        /// <i>not</i> including any CR/LF; whenever possible this is a reference to the underlying
        /// buffer, not a copy, so the caller should read/copy it before doing anything else to the
        /// buffer</param>
        /// <returns>true if a full line was read, false if we need more data first</returns>
        public bool ScanToEndOfLine(out Utf8ByteSpan lineOut)
        {
            if (_startPos == _count)
            {
                _startPos = _count = 0;
                lineOut   = new Utf8ByteSpan();
                return(false);
            }

            if (_startPos == 0 && _partialLine != null && _partialLine.Position > 0 &&
                _partialLine.GetBuffer()[_partialLine.Position - 1] == '\r')
            {
                // This is an edge case where the very last byte we previously saw was a CR, and we didn't know
                // whether the next byte would be LF or not, but we had to dump the buffer into _partialLine
                // because it was completely full. So, now we can return the line that's already in _partialLine,
                // but if the first byte in the buffer is LF we should skip past it.
                if (_buffer[_startPos] == '\n')
                {
                    _startPos++;
                }
                lineOut = new Utf8ByteSpan(_partialLine.GetBuffer(), 0,
                                           (int)_partialLine.Position - 1); // don't include the CR
                _partialLine = null;
                return(true);
            }

            int startedAt = _startPos, pos = _startPos;

            while (pos < _count)
            {
                var b = _buffer[pos];
                if (b == '\n')           // LF by itself terminates a line
                {
                    _startPos = pos + 1; // next line will start after the LF
                    break;
                }
                if (b == '\r')
                {
                    if (pos < (_count - 1))
                    {
                        _startPos = pos + 1;          // next line will start after the CR--
                        if (_buffer[pos + 1] == '\n') // --unless there was an LF right after that
                        {
                            _startPos++;
                        }
                        break;
                    }
                    else
                    {
                        // CR by itself and CR+LF are both valid line endings in SSE, so if the very
                        // last character we saw was a CR, we can't know when the line is fully read
                        // until we've gotten more data. So we'll need to treat this as an incomplete
                        // line.
                        pos++;
                        break;
                    }
                }
                pos++;
            }

            if (pos == _count) // we didn't find a line terminator
            {
                lineOut = new Utf8ByteSpan();
                if (_count < _capacity)
                {
                    // There's still room in the buffer, so we'll re-scan the line once they add more bytes
                    return(false);
                }
                // We need to dump the incomplete line into _partialLine so we can make room in the buffer
                var partialCount = pos - _startPos;
                if (_partialLine is null)
                {
                    _partialLine = new MemoryStream(partialCount);
                }
                _partialLine.Write(_buffer, _startPos, partialCount);

                // Clear the main buffer
                _startPos = _count = 0;

                return(false);
            }

            if (_partialLine != null && _partialLine.Position > 0)
            {
                _partialLine.Write(_buffer, startedAt, pos - startedAt);
                lineOut      = new Utf8ByteSpan(_partialLine.GetBuffer(), 0, (int)_partialLine.Position);
                _partialLine = null;

                // If there are still bytes in the main buffer, move them over to make more room. It's
                // safe for us to do this before the caller has looked at lineOut, because lineOut is now
                // a reference to the separate _partialLine buffer, not to the main buffer.
                if (_startPos < _count)
                {
                    System.Buffer.BlockCopy(_buffer, _startPos, _buffer, 0, _count - _startPos);
                }
                _count   -= _startPos;
                _startPos = 0;
            }
            else
            {
                lineOut = new Utf8ByteSpan(_buffer, startedAt, pos - startedAt);
                if (_startPos == _count)
                {
                    // If we've scanned all the data in the buffer, reset _startPos and _count to indicate
                    // that the entire buffer is available for the next read. It's safe for us to do this
                    // before the caller has looked at lineOut, because we're not actually modifying any
                    // bytes in the buffer. It's the caller's responsibility not to modify the buffer
                    // until it has already done whatever needs to be done with the lineOut data.
                    _startPos = _count = 0;
                }
            }
            return(true);
        }