/// <summary> /// Initializes a new instance. /// </summary> public StompErrorFrameException(StompFrame frame) : base(frame, GetErrorMessage(frame)) { }
/// <summary> /// Extracts the message from the error frame. /// </summary> /// <param name="frame"></param> /// <returns></returns> static string GetErrorMessage(StompFrame frame) { return(frame.GetHeaderValue("message") ?? GetErrorBody(frame)); }
/// <summary> /// Sends a single STOMP frame to the transport. /// </summary> /// <param name="frame"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public abstract ValueTask SendAsync(StompFrame frame, CancellationToken cancellationToken);
/// <summary> /// Attempts to parse a single frame from the given sequence. /// </summary> /// <param name="sequence"></param> /// <param name="frame"></param> /// <returns></returns> public bool TryReadFrame(ref SequenceReader <byte> sequence, out StompFrame frame) { frame = new StompFrame(StompCommand.Unknown, null, null); StompCommand command; List <KeyValuePair <string, string> > headers = new List <KeyValuePair <string, string> >(); Memory <byte> body = null; // attempt to read initial command line if (TryReadCommand(ref sequence, out command) == false) { return(false); } // read headers while (sequence.End == false) { // read the current header if (TryReadHeader(ref sequence, out var header, command != StompCommand.Connect && command != StompCommand.Connected) == false) { return(false); } // null key indicates end of headers if (header.Key == null) { break; } headers.Add(header); } // content length header can help us decide how far to read var contenLengthHeader = headers.FirstOrDefault(i => i.Key == "content-length"); if (contenLengthHeader.Value != null) { if (int.TryParse(contenLengthHeader.Value, out var contentLength) == false) { throw new StompProtocolException("Invalid 'content-length'. Not an integer."); } // allocate new buffer and copy body contents body = new byte[contentLength]; if (sequence.TryCopyTo(body.Span) == false) { return(false); } sequence.Advance(contentLength); // body should be terminated by a null character if (sequence.TryRead(out var term) == false) { return(false); } if (term != 0x00) { throw new StompProtocolException("Body not terminated by null."); } } else { if (sequence.TryReadTo(out ReadOnlySpan <byte> buffer, (byte)0x00) == false) { return(false); } // allocate new buffer and copy body contents body = new byte[buffer.Length]; if (buffer.TryCopyTo(body.Span) == false) { return(false); } } frame = new StompFrame(command, headers, body); return(true); }
/// <summary> /// Writes the specified frame to the writer. /// </summary> /// <param name="writer"></param> /// <param name="frame"></param> public void Write(ArrayBufferWriter <byte> writer, StompFrame frame) { // write command switch (frame.Command) { case StompCommand.Connect: writer.Write(COMMAND_CONNECT); break; case StompCommand.Stomp: writer.Write(COMMAND_STOMP); break; case StompCommand.Connected: writer.Write(COMMAND_CONNECTED); break; case StompCommand.Send: writer.Write(COMMAND_SEND); break; case StompCommand.Subscribe: writer.Write(COMMAND_SUBSCRIBE); break; case StompCommand.Unsubscribe: writer.Write(COMMAND_UNSUBSCRIBE); break; case StompCommand.Ack: writer.Write(COMMAND_ACK); break; case StompCommand.Nack: writer.Write(COMMAND_NACK); break; case StompCommand.Begin: writer.Write(COMMAND_BEGIN); break; case StompCommand.Disconnect: writer.Write(COMMAND_DISCONNECT); break; case StompCommand.Message: writer.Write(COMMAND_MESSAGE); break; case StompCommand.Receipt: writer.Write(COMMAND_RECEIPT); break; case StompCommand.Error: writer.Write(COMMAND_ERROR); break; default: throw new StompProtocolException("Unknown command."); } // write headers foreach (var header in frame.Headers) { WriteEscapedHeaderValue(writer, header.Key); writer.Write(BYTE_COLON); WriteEscapedHeaderValue(writer, header.Value); writer.Write(BYTE_LF); } // headers separated from body by a new line writer.Write(BYTE_LF); // body followed by NULL writer.Write(frame.Body.Span); writer.Write(BYTE_NULL); }
/// <summary> /// Handles outgoing messages to the transport. /// </summary> /// <param name="frame"></param> /// <param name="cancellationToken"></param> /// <returns></returns> async ValueTask OnSendAsync(StompFrame frame, CancellationToken cancellationToken) { logger.LogDebug("Sending STOMP frame: {Command}", frame.Command); await transport.SendAsync(frame, cancellationToken); }