├── StompNet ├── Helpers │ ├── MediaTypeNames.cs │ ├── ISequenceNumberGenerator.cs │ ├── SequenceNumberGenerator.cs │ ├── ActionDisposable.cs │ ├── RandomSequenceNumberGenerator.cs │ ├── AsyncActionDisposable.cs │ ├── ObservableExtensions.cs │ ├── ITaskExecuter.cs │ ├── ActionObserver.cs │ ├── EnumerableExtensions.cs │ └── SerialTaskExecuter.cs ├── StompNet.csproj ├── Exceptions │ └── ErrorFrameException.cs ├── Models │ ├── Frames │ │ ├── ReceiptFrame.cs │ │ ├── Heartbeat.cs │ │ ├── ErrorFrame.cs │ │ ├── ConnectedFrame.cs │ │ ├── MessageFrame.cs │ │ └── Frame.cs │ ├── StompAckValues.cs │ ├── StompHeaders.cs │ ├── StompCommands.cs │ ├── StompInterpreter.cs │ └── StompOctets.cs ├── IO │ ├── IStompClient.cs │ ├── IStompFrameObservable.cs │ ├── IStompFrameReader.cs │ ├── IStompFrameWriter.cs │ ├── StompSerialFrameWriter.cs │ ├── StompSerialFrameReader.cs │ ├── Stomp12Client.cs │ ├── StompFrameObservable.cs │ ├── StompClient.cs │ ├── Stomp12FrameWriter.cs │ ├── StompFrameWriterWithConfirmation.cs │ ├── AsyncStreamWriter.cs │ └── AsyncStreamReader.cs ├── IStompConnector.cs ├── IAsyncDisposable.cs ├── StompTransaction.cs ├── StompConnection.cs ├── IStompConnection.cs ├── IStompTransaction.cs ├── StompMessage.cs ├── IStompMessage.cs ├── StompSubscription.cs ├── StompConnectionBase.cs ├── Stomp12Connector.cs └── IStompConnectionBase.cs ├── StompNet.Tests ├── Helpers │ ├── MemoryStreamExtensions.cs │ └── NoEndChunkedMemoryStream.cs ├── Properties │ └── AssemblyInfo.cs └── StompNet.Tests.csproj ├── StompNet.Examples ├── Properties │ └── AssemblyInfo.cs ├── StompNet.Examples.csproj ├── 0.ReadmeExample.cs ├── 3.ExampleConnectorConcurrent.cs ├── Program.cs ├── 5.ExampleClient.cs ├── 2.ExampleConnectorAnother.cs └── 6.ExampleWriterAndReader.cs ├── StompNet.sln ├── .gitattributes ├── .gitignore ├── README.md └── LICENSE.txt /StompNet/Helpers/MediaTypeNames.cs: -------------------------------------------------------------------------------- 1 | namespace StompNet.Helpers 2 | { 3 | internal static class MediaTypeNames 4 | { 5 | public const string ApplicationOctet = "application/octet-stream"; 6 | public const string TextPlain = "text/plain"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /StompNet.Tests/Helpers/MemoryStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace StompNet.Tests.Helpers 5 | { 6 | internal static class MemoryStreamExtensions 7 | { 8 | public static void Write(this MemoryStream stream, string str) 9 | { 10 | foreach (byte b in Encoding.UTF8.GetBytes(str)) 11 | { 12 | stream.WriteByte(b); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /StompNet/StompNet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net45;netstandard15 5 | 6 | 7 | 8 | 1.0.1.0 9 | Asynchronous STOMP 1.2 client library for .NET. 10 | Carlos Campo 11 | Copyright © Carlos Campo 2020 12 | 13 | 14 | Carlos Campo 15 | stomp;nms;activemq;message broker client 16 | https://github.com/krlito/StompNet 17 | https://github.com/krlito/StompNet 18 | LICENSE.txt 19 | true 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /StompNet/Helpers/ISequenceNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | namespace StompNet.Helpers 20 | { 21 | /// 22 | /// Interface for sequence number generator. 23 | /// 24 | internal interface ISequenceNumberGenerator 25 | { 26 | /// 27 | /// Returns the next number in the sequence. 28 | /// 29 | /// The next number in the sequence 30 | int Next(); 31 | } 32 | } -------------------------------------------------------------------------------- /StompNet/Helpers/SequenceNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | 22 | namespace StompNet.Helpers 23 | { 24 | /// 25 | /// Sequence number generator starting at 1. 26 | /// 27 | /// This class is thread-safe. 28 | /// 29 | internal class SequenceNumberGenerator : ISequenceNumberGenerator 30 | { 31 | private int _count = 0; 32 | 33 | public int Next() 34 | { 35 | return Interlocked.Increment(ref _count); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /StompNet.Tests/Helpers/NoEndChunkedMemoryStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace StompNet.Tests.Helpers 7 | { 8 | /// 9 | /// MemoryStream that simulates a 'live' stream (e.g. NetworkStream) which does not signal end of stream. When 10 | /// the end of stream has been reached and ReadAsync(byte[], int, int, CancellationToken) is called, it waits for 11 | /// the cancellation token to be canceled. 12 | /// 13 | /// It also supports providing a chunk size (in constructor) to simulate reading in chunks when using 14 | /// ReadAsync(byte[], int, int, CancellationToken). 15 | /// 16 | internal class NoEndChunkedMemoryStream : MemoryStream 17 | { 18 | private readonly int _chunkSize; 19 | 20 | public NoEndChunkedMemoryStream(int chunkSize = 0) 21 | { 22 | _chunkSize = chunkSize; 23 | } 24 | 25 | public override async Task ReadAsync(byte[] buffer, int offset, int count, 26 | CancellationToken cancellationToken) 27 | { 28 | int chunkSize = _chunkSize > 0 ? Math.Min(_chunkSize, count) : count; 29 | int bytesRead = await base.ReadAsync(buffer, offset, chunkSize, cancellationToken); 30 | if (bytesRead == 0) 31 | { 32 | cancellationToken.WaitHandle.WaitOne(); 33 | cancellationToken.ThrowIfCancellationRequested(); 34 | } 35 | return bytesRead; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /StompNet.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StompNet.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StompNet.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("546ec5fe-779d-4cfa-9679-091162fe0c6f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /StompNet/Exceptions/ErrorFrameException.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using StompNet.Models.Frames; 21 | 22 | namespace StompNet.Exceptions 23 | { 24 | /// 25 | /// Exception to be used when an STOMP ERROR frame is received. 26 | /// 27 | public class ErrorFrameException : Exception 28 | { 29 | public ErrorFrame ErrorFrame { get; private set; } 30 | 31 | public ErrorFrameException(ErrorFrame errorFrame) 32 | : base(errorFrame != null ? errorFrame.Message : null) 33 | { 34 | if(errorFrame == null) 35 | throw new ArgumentNullException("errorFrame"); 36 | ErrorFrame = errorFrame; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /StompNet.Examples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StompNet.Examples")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StompNet.Examples")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7e9fb3b1-c612-4bb0-b5d8-db06dad87e0f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /StompNet/Helpers/ActionDisposable.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | 21 | namespace StompNet.Helpers 22 | { 23 | /// 24 | /// Disposable class that calls a custom action when it is disposed. 25 | /// 26 | internal class ActionDisposable : IDisposable 27 | { 28 | private readonly Action _action; 29 | 30 | /// 31 | /// Constructor. 32 | /// 33 | /// Action to be invoked when this instance is disposed. 34 | public ActionDisposable(Action action) 35 | { 36 | _action = action; 37 | } 38 | 39 | public void Dispose() 40 | { 41 | _action(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/ReceiptFrame.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | 22 | namespace StompNet.Models.Frames 23 | { 24 | /// 25 | /// Class representing a STOMP RECEIPT frame. 26 | /// 27 | public class ReceiptFrame : Frame 28 | { 29 | public string ReceiptId { get; private set; } 30 | 31 | internal ReceiptFrame(IEnumerable> headers) 32 | : base (StompCommands.Receipt, headers) 33 | { 34 | ReceiptId = Headers.FirstOrDefault(header => header.Key == StompHeaders.ReceiptId).Value; 35 | 36 | if (string.IsNullOrEmpty(ReceiptId)) 37 | ThrowMandatoryHeaderException(StompHeaders.ReceiptId); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StompNet/IO/IStompClient.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | 21 | namespace StompNet.IO 22 | { 23 | /// 24 | /// Stomp client interface. 25 | /// 26 | public interface IStompClient : IStompFrameObservable, IStompFrameWriter 27 | { 28 | /// 29 | /// Get next receipt id. 30 | /// 31 | /// The next receipt id. 32 | string GetNextReceiptId(); 33 | 34 | /// 35 | /// Get next subscription id. 36 | /// 37 | /// The next subscription id. 38 | string GetNextSubscriptionId(); 39 | 40 | /// 41 | /// Get next transaction id. 42 | /// 43 | /// The next transaction id. 44 | string GetNextTransactionId(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /StompNet/IStompConnector.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | 24 | namespace StompNet 25 | { 26 | /// 27 | /// Stomp connector interface. 28 | /// 29 | public interface IStompConnector : IDisposable 30 | { 31 | 32 | /// 33 | /// Connect to the STOMP service. 34 | /// 35 | /// Non-standard headers to include in the connect request frame. 36 | /// The token to monitor for cancellation requests. 37 | /// A task representing the connect operation. 38 | Task ConnectAsync( 39 | IEnumerable> extraHeaders = null, 40 | CancellationToken? cancellationToken = null); 41 | } 42 | } -------------------------------------------------------------------------------- /StompNet/IAsyncDisposable.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | 23 | namespace StompNet 24 | { 25 | /// 26 | /// It's like an IDisposable, but with an async dispose method. 27 | /// 28 | public interface IAsyncDisposable : IDisposable 29 | { 30 | Task DisposeAsync(CancellationToken cancellationToken); 31 | } 32 | 33 | /// 34 | /// IAsyncDisposable Extensions. 35 | /// 36 | public static class AsyncDisposableExtensions 37 | { 38 | /// 39 | /// Dispose asynchronously with no cancellation token. 40 | /// 41 | /// 42 | /// A Task representing the dispose operation. 43 | public static Task DisposeAsync(this IAsyncDisposable disposable) 44 | { 45 | return disposable.DisposeAsync(CancellationToken.None); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /StompNet/Helpers/RandomSequenceNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | 22 | namespace StompNet.Helpers 23 | { 24 | /// 25 | /// Sequence number generator based on System.Random. 26 | /// 27 | /// This class is thread-safe. 28 | /// 29 | internal class RandomSequenceNumberGenerator : ISequenceNumberGenerator 30 | { 31 | private readonly Random _random; 32 | private SpinLock _spinLock; 33 | 34 | public RandomSequenceNumberGenerator() 35 | { 36 | _random = new Random(); 37 | _spinLock = new SpinLock(false); 38 | } 39 | 40 | public int Next() 41 | { 42 | bool lockTaken = false; 43 | try 44 | { 45 | _spinLock.Enter(ref lockTaken); 46 | return _random.Next(); 47 | } 48 | finally 49 | { 50 | if(lockTaken) _spinLock.Exit(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /StompNet/Helpers/AsyncActionDisposable.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | 23 | namespace StompNet.Helpers 24 | { 25 | /// 26 | /// IAsyncDisposable implementation that calls a custom action when it is disposed. 27 | /// 28 | internal class AsyncActionDisposable : IAsyncDisposable 29 | { 30 | private readonly Func _action; 31 | 32 | /// 33 | /// Constructor. 34 | /// 35 | /// Action to be invoked when this instance is disposed. 36 | public AsyncActionDisposable(Func action) 37 | { 38 | _action = action; 39 | } 40 | 41 | public void Dispose() 42 | { 43 | DisposeAsync(CancellationToken.None).Wait(); 44 | } 45 | 46 | public Task DisposeAsync(CancellationToken cancellationToken) 47 | { 48 | return Task.Run(() => _action(cancellationToken), cancellationToken); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /StompNet/Helpers/ObservableExtensions.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | 21 | namespace StompNet.Helpers 22 | { 23 | /// 24 | /// Some IObservable extensions. 25 | /// 26 | public static class ObservableExtensions 27 | { 28 | /// 29 | /// Allows to subscribe to an observable using delegates. 30 | /// 31 | /// Observable to be subscribed to. 32 | /// Action to be invoked when the observable calls OnNext. 33 | /// Action to be invoked when the observable calls OnError. 34 | /// Action to be invoked when the observable calls OnCompleted. 35 | /// An IDisposable which ca be used to unsubscribe. 36 | public static IDisposable SubscribeEx(this IObservable observable, Action onNext = null, Action onError = null, Action onCompleted = null) 37 | { 38 | return observable.Subscribe(new ActionObserver(onNext, onError, onCompleted)); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /StompNet/Models/StompAckValues.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | namespace StompNet.Models 23 | { 24 | /// 25 | /// Ack values as defined by STOMP protocol specification. 26 | /// 27 | public static class StompAckValues 28 | { 29 | public const string AckAutoValue = "auto"; 30 | public const string AckClientValue = "client"; 31 | public const string AckClientIndividualValue = "client-individual"; 32 | 33 | private static readonly ISet _ackValidValues = new HashSet { AckAutoValue, AckClientIndividualValue, AckClientValue }; 34 | 35 | public static bool IsValidAckValue(string ackValue) 36 | { 37 | return _ackValidValues.Contains(ackValue); 38 | } 39 | 40 | public static void ThrowIfInvalidAckValue(string ackValue) 41 | { 42 | if(!_ackValidValues.Contains(ackValue)) 43 | throw new ArgumentException(string.Format("{0} header value MUST be: '{1}', '{2}' or '{3}'", StompHeaders.Ack, AckAutoValue, AckClientIndividualValue, AckClientValue)); 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /StompNet/IO/IStompFrameObservable.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using StompNet.Models.Frames; 22 | 23 | namespace StompNet.IO 24 | { 25 | /// 26 | /// Interface for a frame reader in an 'observer pattern' fashion. 27 | /// 28 | public interface IStompFrameObservable : IObservable 29 | { 30 | bool IsStarted { get; } 31 | string ProtocolVersion { get; } 32 | 33 | /// 34 | /// Start processing incoming frames. 35 | /// 36 | /// Cancellation token that may be used to stop the processing of incoming frames. 37 | void Start(CancellationToken cancellationToken); 38 | } 39 | 40 | /// 41 | /// IStompFrameObservable extensions. 42 | /// 43 | public static class StompFrameObservableExtensions 44 | { 45 | /// 46 | /// Start processing incoming frames. 47 | /// 48 | public static void Start(this IStompFrameObservable stompFrameObservable) 49 | { 50 | stompFrameObservable.Start(CancellationToken.None); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /StompNet/IO/IStompFrameReader.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | using StompNet.Models.Frames; 23 | 24 | namespace StompNet.IO 25 | { 26 | /// 27 | /// Contract of a Frame Reader. 28 | /// 29 | public interface IStompFrameReader : IDisposable 30 | { 31 | /// 32 | /// Version of the STOMP protocol. 33 | /// 34 | string ProtocolVersion { get; } 35 | 36 | /// 37 | /// Read a frame. 38 | /// 39 | /// The token to monitor for cancellation requests. 40 | /// A task representing the read operation. The result of this task is a frame. 41 | Task ReadFrameAsync(CancellationToken cancellationToken); 42 | } 43 | 44 | /// 45 | /// So, you do not like using CancellationTokens :D. Use this extension! 46 | /// 47 | public static class FrameReaderExtensions 48 | { 49 | public static Task ReadFrameAsync(this IStompFrameReader reader) 50 | { 51 | return reader.ReadFrameAsync(CancellationToken.None); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /StompNet/IO/IStompFrameWriter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | using StompNet.Models.Frames; 23 | 24 | namespace StompNet.IO 25 | { 26 | /// 27 | /// Contract of a Frame Writer. 28 | /// 29 | public interface IStompFrameWriter : IDisposable 30 | { 31 | /// 32 | /// Version of the STOMP protocol. 33 | /// 34 | string ProtocolVersion { get; } 35 | 36 | /// 37 | /// Write a frame. 38 | /// 39 | /// Frame to be written. 40 | /// The token to monitor for cancellation requests. 41 | /// A task representing the write operation. 42 | Task WriteAsync(Frame frame, CancellationToken cancellationToken); 43 | } 44 | 45 | /// 46 | /// So, you do not like using CancellationTokens :D. Use this extension! 47 | /// 48 | public static class FrameWriterExtensions 49 | { 50 | public static Task WriteAsync(this IStompFrameWriter writer, Frame frame) 51 | { 52 | return writer.WriteAsync(frame, CancellationToken.None); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /StompNet/Models/StompHeaders.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | namespace StompNet.Models 20 | { 21 | /// 22 | /// Frame headers as defined by STOMP protocol specification. 23 | /// 24 | public static class StompHeaders 25 | { 26 | public const string AcceptVersion = "accept-version"; 27 | public const string Ack = "ack"; 28 | public const string ContentLength = "content-length"; 29 | public const string ContentType = "content-type"; 30 | public const string Destination = "destination"; 31 | public const string Heartbeat = "heart-beat"; 32 | public const string Host = "host"; 33 | public const string Id = "id"; 34 | public const string Login = "login"; 35 | public const string Message = "message"; 36 | public const string MessageId = "message-id"; 37 | public const string Passcode = "passcode"; 38 | public const string Receipt = "receipt"; 39 | public const string ReceiptId = "receipt-id"; 40 | public const string Server = "server"; 41 | public const string Session = "session"; 42 | public const string Subscription = "subscription"; 43 | public const string Transaction = "transaction"; 44 | public const string Version = "version"; 45 | } 46 | } -------------------------------------------------------------------------------- /StompNet/Helpers/ITaskExecuter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | 23 | namespace StompNet.Helpers 24 | { 25 | /// 26 | /// Interface for a task executer/scheduler. 27 | /// 28 | internal interface ITaskExecuter 29 | { 30 | /// 31 | /// Execute a task. 32 | /// 33 | /// A function that returns the task to be executed. 34 | /// The token to monitor for cancellation requests. 35 | /// A task representing the scheduled task operation. 36 | Task Execute(Func task, CancellationToken cancellationToken); 37 | } 38 | 39 | /// 40 | /// Interface for a task executer/scheduler. 41 | /// 42 | internal interface ITaskExecuter : ITaskExecuter 43 | { 44 | /// 45 | /// Execute a task. 46 | /// 47 | /// A function that returns the task to be executed. 48 | /// The token to monitor for cancellation requests. 49 | /// A task representing the scheduled task operation. The returned task result is the same as the original task's result. 50 | Task Execute(Func> task, CancellationToken cancellationToken); 51 | } 52 | } -------------------------------------------------------------------------------- /StompNet.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5E1F295D-C527-42EA-A486-DDBD2A1F9CB7}" 7 | ProjectSection(SolutionItems) = preProject 8 | LICENSE.txt = LICENSE.txt 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StompNet", "StompNet\StompNet.csproj", "{2BE5619B-B63B-4583-B434-371C925FE18C}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StompNet.Examples", "StompNet.Examples\StompNet.Examples.csproj", "{84A7D6AB-D0C5-4A83-A205-35FE232F8547}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StompNet.Tests", "StompNet.Tests\StompNet.Tests.csproj", "{546EC5FE-779D-4CFA-9679-091162FE0C6F}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {2BE5619B-B63B-4583-B434-371C925FE18C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {2BE5619B-B63B-4583-B434-371C925FE18C}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {2BE5619B-B63B-4583-B434-371C925FE18C}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {2BE5619B-B63B-4583-B434-371C925FE18C}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {84A7D6AB-D0C5-4A83-A205-35FE232F8547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {84A7D6AB-D0C5-4A83-A205-35FE232F8547}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {84A7D6AB-D0C5-4A83-A205-35FE232F8547}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {84A7D6AB-D0C5-4A83-A205-35FE232F8547}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {546EC5FE-779D-4CFA-9679-091162FE0C6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {546EC5FE-779D-4CFA-9679-091162FE0C6F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {546EC5FE-779D-4CFA-9679-091162FE0C6F}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {546EC5FE-779D-4CFA-9679-091162FE0C6F}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /StompNet/Helpers/ActionObserver.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | 21 | namespace StompNet.Helpers 22 | { 23 | /// 24 | /// General-purpose IObserver configured using custom actions/delegates. 25 | /// 26 | internal class ActionObserver : IObserver 27 | { 28 | private readonly Action _onNext; 29 | private readonly Action _onError; 30 | private readonly Action _onCompleted; 31 | 32 | /// 33 | /// Constructor. 34 | /// 35 | /// Action to be invoked when the observable calls OnNext. 36 | /// Action to be invoked when the observable calls OnError. 37 | /// Action to be invoked when the observable calls OnCompleted. 38 | public ActionObserver(Action onNext = null, Action onError = null, Action onCompleted = null) 39 | { 40 | _onNext = onNext; 41 | _onError = onError; 42 | _onCompleted = onCompleted; 43 | } 44 | 45 | public void OnNext(T value) 46 | { 47 | if(_onNext != null) _onNext(value); 48 | } 49 | 50 | public void OnError(Exception error) 51 | { 52 | if (_onError != null) _onError(error); 53 | } 54 | 55 | public void OnCompleted() 56 | { 57 | if (_onCompleted != null) _onCompleted(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /StompNet/IO/StompSerialFrameWriter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Threading; 20 | using System.Threading.Tasks; 21 | using StompNet.Helpers; 22 | using StompNet.Models.Frames; 23 | 24 | namespace StompNet.IO 25 | { 26 | /// 27 | /// Wrapper around an IStompFrameWriter for thread-safety. 28 | /// STOMP frames will be written in a sequential/serial FIFO manner. 29 | /// 30 | public class StompSerialFrameWriter : IStompFrameWriter 31 | { 32 | private readonly IStompFrameWriter _writer; 33 | private readonly ITaskExecuter _serialTaskExecuter; 34 | 35 | public string ProtocolVersion 36 | { 37 | get { return _writer.ProtocolVersion; } 38 | } 39 | 40 | /// 41 | /// Constructor. 42 | /// 43 | /// Frame writer to be wrapped. 44 | public StompSerialFrameWriter(IStompFrameWriter writer) 45 | { 46 | _writer = writer; 47 | _serialTaskExecuter = new SerialTaskExecuter(); 48 | } 49 | 50 | public Task WriteAsync(Frame frame, CancellationToken cancellationToken) 51 | { 52 | return _serialTaskExecuter.Execute(() => _writer.WriteAsync(frame, cancellationToken), cancellationToken); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | Dispose(true); 58 | } 59 | 60 | protected virtual void Dispose(bool disposing) 61 | { 62 | 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /StompNet/IO/StompSerialFrameReader.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Threading; 20 | using System.Threading.Tasks; 21 | using StompNet.Helpers; 22 | using StompNet.Models.Frames; 23 | 24 | namespace StompNet.IO 25 | { 26 | /// 27 | /// Wrapper around an IStompFrameReader for thread-safety. 28 | /// STOMP frames will be read in a sequential/serial FIFO manner. 29 | /// 30 | public class StompSerialFrameReader : IStompFrameReader 31 | { 32 | private readonly IStompFrameReader _reader; 33 | private readonly ITaskExecuter _serialTaskExecuter; 34 | 35 | public string ProtocolVersion 36 | { 37 | get { return _reader.ProtocolVersion; } 38 | } 39 | 40 | /// 41 | /// Constructor. 42 | /// 43 | /// Frame reader to be wrapped. 44 | public StompSerialFrameReader(IStompFrameReader reader) 45 | { 46 | _reader = reader; 47 | _serialTaskExecuter = new SerialTaskExecuter(); 48 | } 49 | 50 | public Task ReadFrameAsync(CancellationToken cancellationToken) 51 | { 52 | return _serialTaskExecuter.Execute(() => _reader.ReadFrameAsync(cancellationToken), cancellationToken); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | Dispose(true); 58 | } 59 | 60 | protected virtual void Dispose(bool disposing) 61 | { 62 | 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /StompNet/Helpers/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | namespace StompNet.Helpers 23 | { 24 | /// 25 | /// Some IEnumerable extensions. 26 | /// 27 | internal static class EnumerableExtensions 28 | { 29 | /// 30 | /// For each element of the IEnumerable, invoke an action. 31 | /// 32 | /// IEnumerable source of the elements. 33 | /// Action to be invoked for each element of the IEnumerable. 34 | public static void Do(this IEnumerable source, Action action) 35 | { 36 | foreach (var item in source) 37 | { 38 | action(item); 39 | } 40 | } 41 | 42 | /// 43 | /// For each element of the IEnumerable, invoke an action. 44 | /// If the action fails, continue with the next one. 45 | /// 46 | /// IEnumerable source of the elements. 47 | /// Action to be invoked for each element of the IEnumerable. 48 | public static void DoIgnoringExceptions(this IEnumerable source, Action action) 49 | { 50 | foreach (var item in source) 51 | { 52 | try 53 | { 54 | action(item); 55 | } 56 | catch { } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /StompNet/StompTransaction.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | using StompNet.IO; 23 | 24 | namespace StompNet 25 | { 26 | /// 27 | /// Stomp Transaction. 28 | /// 29 | internal class StompTransaction : StompConnectionBase, IStompTransaction 30 | { 31 | public string Id 32 | { 33 | get { return TransactionId; } 34 | } 35 | 36 | public StompTransaction(StompClient client, string id) 37 | : base(client, id) 38 | { 39 | } 40 | 41 | public Task CommitAsync( 42 | bool useReceipt = false, 43 | IEnumerable> extraHeaders = null, 44 | CancellationToken? cancellationToken = null) 45 | { 46 | return Client.WriteCommitAsync( 47 | Id, 48 | useReceipt ? Client.GetNextReceiptId() : null, 49 | extraHeaders, 50 | cancellationToken); 51 | } 52 | 53 | public Task AbortAsync( 54 | bool useReceipt = false, 55 | IEnumerable> extraHeaders = null, 56 | CancellationToken? cancellationToken = null) 57 | { 58 | return Client.WriteAbortAsync( 59 | Id, 60 | useReceipt ? Client.GetNextReceiptId() : null, 61 | extraHeaders, 62 | cancellationToken); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /StompNet/StompConnection.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | using StompNet.IO; 23 | 24 | namespace StompNet 25 | { 26 | /// 27 | /// Stomp Connection. 28 | /// 29 | internal class StompConnection : StompConnectionBase, IStompConnection 30 | { 31 | public StompConnection(StompClient client) 32 | : base(client) 33 | { 34 | 35 | } 36 | 37 | public async Task BeginTransactionAsync( 38 | bool useReceipt = false, 39 | IEnumerable> extraHeaders = null, 40 | CancellationToken? cancellationToken = null) 41 | { 42 | string transactionId = Client.GetNextTransactionId(); 43 | await Client.WriteBeginAsync( 44 | transactionId, 45 | useReceipt ? Client.GetNextReceiptId() : null, 46 | extraHeaders, 47 | cancellationToken); 48 | 49 | return new StompTransaction(Client, transactionId); 50 | } 51 | 52 | public Task DisconnectAsync( 53 | bool useReceipt = false, 54 | IEnumerable> extraHeaders = null, 55 | CancellationToken? cancellationToken = null) 56 | { 57 | return Client.WriteDisconnectAsync( 58 | useReceipt ? Client.GetNextReceiptId() : null, 59 | extraHeaders, 60 | cancellationToken); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/Heartbeat.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | 21 | namespace StompNet.Models.Frames 22 | { 23 | /// 24 | /// Class representing a Heartbeat. 25 | /// 26 | public class Heartbeat 27 | { 28 | public readonly static Heartbeat NoHeartbeat = new Heartbeat(0, 0); 29 | 30 | public int Outgoing { get; private set; } 31 | public int Incoming { get; private set; } 32 | public string RawHeartbeat { get; private set; } 33 | 34 | public Heartbeat(int outgoing, int incoming) 35 | { 36 | Outgoing = outgoing; 37 | Incoming = incoming; 38 | RawHeartbeat = outgoing + "," + incoming; 39 | } 40 | 41 | public static Heartbeat GetHeartbeat(string heartbeat) 42 | { 43 | if (string.IsNullOrEmpty(heartbeat)) 44 | return NoHeartbeat; 45 | 46 | string[] heartBeatParts = heartbeat.Split(','); 47 | 48 | if (heartBeatParts.Length != 2) 49 | throw new FormatException("A heart-beat header MUST contain two positive integers separated by a comma."); 50 | 51 | int outgoing, incoming; 52 | if (!int.TryParse(heartBeatParts[0], out outgoing) || outgoing < 0 || !int.TryParse(heartBeatParts[1], out incoming) || incoming < 0) 53 | throw new FormatException("A heart-beat header MUST contain two positive integers separated by a comma."); 54 | 55 | if(outgoing == 0 && incoming == 0) 56 | return NoHeartbeat; 57 | 58 | return new Heartbeat(outgoing, incoming); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/ErrorFrame.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | 21 | namespace StompNet.Models.Frames 22 | { 23 | /// 24 | /// Class representing a STOMP ERROR frame. 25 | /// 26 | public class ErrorFrame : Frame 27 | { 28 | public string ReceiptId { get; private set; } 29 | 30 | public string Message { get; private set; } 31 | 32 | public string ContentType { get; private set; } 33 | 34 | public int ContentLength { get; private set; } 35 | 36 | internal ErrorFrame(IEnumerable> headers, byte[] body = null) 37 | : base (StompCommands.Error, headers, body) 38 | { 39 | int missingHeaders = 3; 40 | 41 | foreach (var header in Headers) 42 | { 43 | if (ReceiptId == null && header.Key == StompHeaders.ReceiptId) 44 | { 45 | ReceiptId = header.Value; 46 | missingHeaders--; 47 | } 48 | else if (Message == null && header.Key == StompHeaders.Message) 49 | { 50 | Message = header.Value; 51 | missingHeaders--; 52 | } 53 | else if (ContentType == null && header.Key == StompHeaders.ContentType) 54 | { 55 | ContentType = header.Value; 56 | missingHeaders--; 57 | } 58 | 59 | if (missingHeaders == 0) break; 60 | } 61 | 62 | ContentLength = BodyArray.Length; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /StompNet/IStompConnection.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | 23 | namespace StompNet 24 | { 25 | /// 26 | /// Stomp connection interface. 27 | /// 28 | public interface IStompConnection : IStompConnectionBase 29 | { 30 | /// 31 | /// Begin a transaction. 32 | /// 33 | /// Indicates whether to require the service to confirm receipt of the message. 34 | /// Non-standard headers to include in the connect request frame. 35 | /// The token to monitor for cancellation requests. 36 | /// A task representing the commit operation. The result of this task is a transaction instance. 37 | Task BeginTransactionAsync( 38 | bool useReceipt = false, 39 | IEnumerable> extraHeaders = null, 40 | CancellationToken? cancellationToken = null); 41 | 42 | /// 43 | /// Disconnect from STOMP service. 44 | /// 45 | /// Indicates whether to require the service to confirm receipt of the message. 46 | /// Non-standard headers to include in the connect request frame. 47 | /// The token to monitor for cancellation requests. 48 | /// A task representing the disconnect operation. 49 | Task DisconnectAsync( 50 | bool useReceipt = false, 51 | IEnumerable> extraHeaders = null, 52 | CancellationToken? cancellationToken = null); 53 | } 54 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /StompNet/IStompTransaction.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | 23 | namespace StompNet 24 | { 25 | /// 26 | /// Stomp transaction interface. 27 | /// 28 | public interface IStompTransaction : IStompConnectionBase 29 | { 30 | string Id { get; } 31 | 32 | ///// 33 | ///// Connect to the STOMP service. 34 | ///// 35 | 36 | 37 | /// 38 | /// Commit the transaction. 39 | /// 40 | /// Indicates whether to require the service to confirm receipt of the message. 41 | /// Non-standard headers to include in the connect request frame. 42 | /// The token to monitor for cancellation requests. 43 | /// A task representing the commit operation. 44 | Task CommitAsync( 45 | bool useReceipt = false, 46 | IEnumerable> extraHeaders = null, 47 | CancellationToken? cancellationToken = null); 48 | 49 | /// 50 | /// Abort the transaction. 51 | /// 52 | /// Indicates whether to require the service to confirm receipt of the message. 53 | /// Non-standard headers to include in the connect request frame. 54 | /// The token to monitor for cancellation requests. 55 | /// A task representing the abort operation. 56 | Task AbortAsync( 57 | bool useReceipt = false, 58 | IEnumerable> extraHeaders = null, 59 | CancellationToken? cancellationToken = null); 60 | } 61 | } -------------------------------------------------------------------------------- /StompNet/Models/StompCommands.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | 21 | namespace StompNet.Models 22 | { 23 | /// 24 | /// Commands as defined by STOMP protocol specification. 25 | /// 26 | public static class StompCommands 27 | { 28 | #region Client Commands 29 | 30 | public const string Abort = "ABORT"; 31 | public const string Ack = "ACK"; 32 | public const string Begin = "BEGIN"; 33 | public const string Commit = "COMMIT"; 34 | public const string Connect = "CONNECT"; 35 | public const string Disconnect = "DISCONNECT"; 36 | public const string Nack = "NACK"; 37 | public const string Send = "SEND"; 38 | public const string Stomp = "STOMP"; 39 | public const string Subscribe = "SUBSCRIBE"; 40 | public const string Unsubscribe = "UNSUBSCRIBE"; 41 | 42 | private static readonly ISet _clientCommandSet = new HashSet { Send, Subscribe, Unsubscribe, Begin, Commit, Abort, Ack, Nack, Disconnect, Connect, Stomp }; 43 | 44 | public static bool IsClientCommand(string command) 45 | { 46 | return _clientCommandSet.Contains(command); 47 | } 48 | 49 | #endregion 50 | 51 | #region Server Commands 52 | 53 | public const string Connected = "CONNECTED"; 54 | public const string Error = "ERROR"; 55 | public const string Message = "MESSAGE"; 56 | public const string Receipt = "RECEIPT"; 57 | public const string Heartbeat = "HEARTBEAT"; // THIS IS AN 'ARTIFICIAL' COMMAND 58 | 59 | private static readonly ISet _serverCommandSet = new HashSet { Connected, Message, Receipt, Error }; 60 | 61 | public static bool IsServerCommand(string command) 62 | { 63 | return _serverCommandSet.Contains(command); 64 | } 65 | 66 | #endregion 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/ConnectedFrame.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | 21 | namespace StompNet.Models.Frames 22 | { 23 | /// 24 | /// Class representing a STOMP CONNECTED frame. 25 | /// 26 | public class ConnectedFrame : Frame 27 | { 28 | public string Version { get; private set; } 29 | 30 | public string Session { get; private set; } 31 | 32 | public string Server { get; private set; } 33 | 34 | public Heartbeat Heartbeat { get; private set; } 35 | 36 | public ConnectedFrame(IEnumerable> headers) 37 | : base (StompCommands.Connected, headers) 38 | { 39 | int missingHeaders = 4; 40 | 41 | foreach (var header in Headers) 42 | { 43 | if (Version == null && header.Key == StompHeaders.Version) 44 | { 45 | Version = header.Value; 46 | missingHeaders--; 47 | } 48 | else if (Session == null && header.Key == StompHeaders.Session) 49 | { 50 | Session = header.Value; 51 | missingHeaders--; 52 | } 53 | else if (Server == null && header.Key == StompHeaders.Server) 54 | { 55 | Server = header.Value; 56 | missingHeaders--; 57 | } 58 | else if (Heartbeat == null && header.Key == StompHeaders.Heartbeat) 59 | { 60 | Heartbeat = Heartbeat.GetHeartbeat(header.Value); 61 | missingHeaders--; 62 | } 63 | 64 | if (missingHeaders == 0) break; 65 | } 66 | 67 | if (missingHeaders != 0) 68 | { 69 | if (string.IsNullOrEmpty(Version)) 70 | { 71 | Version = "1.0"; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /StompNet/Models/StompInterpreter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.IO; 21 | using StompNet.Models.Frames; 22 | 23 | namespace StompNet.Models 24 | { 25 | /// 26 | /// Class to transform frames into an instance of a 'subclass' matching its command. 27 | /// 28 | public static class StompInterpreter 29 | { 30 | /// 31 | /// Transform a frame into a 'subclass' matching its command. 32 | /// These subclasses are: ReceiptFrame, MessageFrame, ErrorFrame, ConnectedFrame. 33 | /// 34 | /// Frame to be interpreted. 35 | /// A new frame which class matches its command type. 36 | public static Frame Interpret(Frame frame) 37 | { 38 | if (frame == null) 39 | throw new ArgumentNullException("frame"); 40 | 41 | switch (frame.Command) 42 | { 43 | case StompCommands.Heartbeat: 44 | return frame; 45 | case StompCommands.Receipt: 46 | if (frame.BodyArray != Frame.EmptyBody) 47 | throw new InvalidDataException("Receipt frame MUST NOT have a body."); 48 | return new ReceiptFrame(frame.Headers); 49 | case StompCommands.Message: 50 | return new MessageFrame(frame.Headers, frame.BodyArray); 51 | case StompCommands.Error: 52 | return new ErrorFrame(frame.Headers, frame.BodyArray); 53 | case StompCommands.Connected: 54 | if (frame.BodyArray != Frame.EmptyBody) 55 | throw new InvalidDataException("Connected frame MUST NOT have a body."); 56 | return new ConnectedFrame(frame.Headers); 57 | default: 58 | throw new InvalidDataException(string.Format("'{0}' is not a valid STOMP server command.", frame.Command)); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | /.vs 158 | -------------------------------------------------------------------------------- /StompNet/IO/Stomp12Client.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.IO; 21 | 22 | namespace StompNet.IO 23 | { 24 | /// 25 | /// STOMP 1.2 Client. 26 | /// 27 | /// ATTENTION: This is a disposable class. 28 | /// 29 | public class Stomp12Client : StompClient 30 | { 31 | 32 | /// 33 | /// Constructor of a client for STOMP 1.2. 34 | /// 35 | /// Stream for incoming/outgoing data from/to STOMP service. 36 | /// When sending messages that require receipt confirmation, 37 | /// this interval specifies how much time to wait before sending the frame again if 38 | /// no receipt is received. 39 | /// Flag to indicate random numbers must 40 | /// be used when creating sequence numbers for receipts, subscriptions and transactions 41 | public Stomp12Client(Stream stream, TimeSpan? retryInterval = null, bool useRandomNumberGenerator = false) 42 | : this (stream, stream, retryInterval, useRandomNumberGenerator) 43 | { 44 | 45 | } 46 | 47 | /// 48 | /// Constructor of a client for STOMP 1.2. 49 | /// 50 | /// Stream for incoming data from STOMP service. 51 | /// Stream for outgoing data to STOMP service. 52 | /// When sending messages that requires receipt confirmation, 53 | /// this interval specifies how much time to wait before sending the frame again if 54 | /// no receipt is received. 55 | /// Flag to indicate random numbers must 56 | /// be used when creating sequence numbers for receipts, subscriptions and transactions 57 | public Stomp12Client(Stream inStream, Stream outStream, TimeSpan? retryInterval = null, bool useRandomNumberGenerator = false) 58 | : base (new Stomp12FrameReader(inStream), new Stomp12FrameWriter(outStream), retryInterval, true, useRandomNumberGenerator) 59 | { 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /StompNet.Examples/StompNet.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {84A7D6AB-D0C5-4A83-A205-35FE232F8547} 8 | Exe 9 | Properties 10 | StompNet.Examples 11 | StompNet.Examples 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {2be5619b-b63b-4583-b434-371c925fe18c} 58 | StompNet 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/MessageFrame.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using StompNet.Helpers; 20 | using System.Collections.Generic; 21 | 22 | namespace StompNet.Models.Frames 23 | { 24 | /// 25 | /// Class representing a STOMP message frame. 26 | /// 27 | public class MessageFrame : Frame 28 | { 29 | public string Destination { get; private set; } 30 | 31 | public string MessageId { get; private set; } 32 | 33 | public string Subscription { get; private set; } 34 | 35 | public string Ack { get; private set; } 36 | 37 | public string ContentType { get; private set; } 38 | 39 | public int ContentLength { get; private set; } 40 | 41 | internal MessageFrame(IEnumerable> headers, byte[] body = null) 42 | : base (StompCommands.Message, headers, body) 43 | { 44 | int missingHeaders = 5; 45 | 46 | foreach(var header in Headers) 47 | { 48 | if (Destination == null && header.Key == StompHeaders.Destination) 49 | { 50 | Destination = header.Value; 51 | missingHeaders--; 52 | } 53 | else if (MessageId == null && header.Key == StompHeaders.MessageId) 54 | { 55 | MessageId = header.Value; 56 | missingHeaders--; 57 | } 58 | else if (Subscription == null && header.Key == StompHeaders.Subscription) 59 | { 60 | Subscription = header.Value; 61 | missingHeaders--; 62 | } 63 | else if (ContentType == null && header.Key == StompHeaders.ContentType) 64 | { 65 | ContentType = header.Value; 66 | missingHeaders--; 67 | } 68 | else if (Ack == null && header.Key == StompHeaders.Ack) 69 | { 70 | Ack = header.Value; 71 | missingHeaders--; 72 | } 73 | 74 | if(missingHeaders == 0) break; 75 | } 76 | 77 | if(missingHeaders != 0) 78 | { 79 | if (string.IsNullOrEmpty(Destination)) 80 | ThrowMandatoryHeaderException(StompHeaders.Destination); 81 | if (string.IsNullOrEmpty(MessageId)) 82 | ThrowMandatoryHeaderException(StompHeaders.MessageId); 83 | if (string.IsNullOrEmpty(Subscription)) 84 | ThrowMandatoryHeaderException(StompHeaders.Subscription); 85 | if(string.IsNullOrEmpty(ContentType)) 86 | ContentType = MediaTypeNames.ApplicationOctet; 87 | } 88 | 89 | ContentLength = BodyArray.Length; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /StompNet/Helpers/SerialTaskExecuter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | 25 | namespace StompNet.Helpers 26 | { 27 | /// 28 | /// Class to execute tasks serially. 29 | /// 30 | internal class SerialTaskExecuter : ITaskExecuter where T : class 31 | { 32 | private readonly LinkedList> _queue; 33 | 34 | public SerialTaskExecuter() 35 | { 36 | _queue = new LinkedList>(); 37 | } 38 | 39 | public Task Execute(Func> task, CancellationToken cancellationToken) 40 | { 41 | return Execute(task as Func, cancellationToken) as Task; 42 | } 43 | 44 | public Task Execute(Func task, CancellationToken cancellationToken) 45 | { 46 | TaskCompletionSource tcs = new TaskCompletionSource(); 47 | 48 | Func nextTask = 49 | () => 50 | Task.Run(task, cancellationToken).ContinueWith( 51 | awaitedTask => 52 | { 53 | tcs.TrySet(awaitedTask); 54 | Monitor.Enter(_queue); 55 | _queue.RemoveFirst(); 56 | Func next = _queue.FirstOrDefault(); 57 | Monitor.Exit(_queue); 58 | if(next != null) next(); 59 | }); 60 | 61 | 62 | Monitor.Enter(_queue); 63 | _queue.AddLast(nextTask); 64 | bool start = _queue.Count == 1; 65 | Monitor.Exit(_queue); 66 | if (start) nextTask(); 67 | 68 | return tcs.Task; 69 | } 70 | } 71 | 72 | /// 73 | /// Class to execute tasks serially. 74 | /// 75 | internal class SerialTaskExecuter : SerialTaskExecuter 76 | { 77 | 78 | } 79 | 80 | /// 81 | /// TaskCompletionSource extensions. 82 | /// 83 | internal static class TaskCompletionSourceExtensions 84 | { 85 | public static void TrySet(this TaskCompletionSource tcs, Task task) where T : class 86 | { 87 | if (task.IsFaulted) 88 | { 89 | tcs.TrySetException(task.Exception.InnerExceptions); 90 | } 91 | else if (task.IsCanceled) 92 | { 93 | tcs.TrySetCanceled(); 94 | } 95 | else 96 | { 97 | Task taskWithResult = task as Task; 98 | tcs.TrySetResult(taskWithResult == null ? null : taskWithResult.Result); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /StompNet/Models/StompOctets.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.IO; 20 | 21 | namespace StompNet.Models 22 | { 23 | /// 24 | /// STOMP protocol special octets. 25 | /// This class is used to escape/unescape headers for STOMP 1.2. 26 | /// 27 | internal static class StompOctets 28 | { 29 | public const char Backslash = '\\'; 30 | public const char CarriageReturn = '\r'; 31 | public const char Colon = ':'; 32 | public const char EndOfFrame = '\0'; 33 | public const char LineFeed = '\n'; 34 | 35 | public const char EscapedBackslash = '\\'; 36 | public const char EscapedCarriageReturn = 'r'; 37 | public const char EscapedColon = 'c'; 38 | public const char EscapedLineFeed = 'n'; 39 | 40 | public const byte BackslashByte = 92; 41 | public const byte CarriageReturnByte = 13; 42 | public const byte ColonByte = 58; 43 | public const byte EndOfFrameByte = 0; 44 | public const byte LineFeedByte = 10; 45 | 46 | public const byte EscapedBackslashByte = 92; /*\*/ 47 | public const byte EscapedCarriageReturnByte = 114; /*r*/ 48 | public const byte EscapedColonByte = 99; /*c*/ 49 | public const byte EscapedLineFeedByte = 110; /*n*/ 50 | 51 | public static readonly byte[] EscapedBackslashArray = new[] { BackslashByte, EscapedBackslashByte }; 52 | public static readonly byte[] EscapedCarriageReturnArray = new[] { BackslashByte, EscapedCarriageReturnByte }; 53 | public static readonly byte[] EscapedColonArray = new[] { BackslashByte, EscapedColonByte }; 54 | public static readonly byte[] EscapedLineFeedArray = new[] { BackslashByte, EscapedLineFeedByte }; 55 | 56 | public static byte[] EscapeOctet(char octet) 57 | { 58 | switch (octet) 59 | { 60 | case Colon: 61 | return EscapedColonArray; 62 | case Backslash: 63 | return EscapedBackslashArray; 64 | case CarriageReturn: 65 | return EscapedCarriageReturnArray; 66 | case LineFeed: 67 | return EscapedLineFeedArray; 68 | default: 69 | return null; 70 | } 71 | } 72 | 73 | public static char UnescapeOctet(char octet) 74 | { 75 | switch (octet) 76 | { 77 | case EscapedColon: 78 | return Colon; 79 | case EscapedBackslash: 80 | return Backslash; 81 | case EscapedLineFeed: 82 | return LineFeed; 83 | case EscapedCarriageReturn: 84 | return CarriageReturn; 85 | default: 86 | throw new InvalidDataException(string.Format("Escape sequence '\\{0}' is invalid.'", octet)); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /StompNet.Examples/0.ReadmeExample.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | using System.Net.Sockets; 23 | using System.Text; 24 | using System.Threading.Tasks; 25 | using StompNet; 26 | 27 | namespace Stomp.Net.Examples 28 | { 29 | partial class Program 30 | { 31 | /// 32 | /// Example used in the README.md file. 33 | /// 34 | static async Task ReadmeExample() 35 | { 36 | // Establish a TCP connection with the STOMP service. 37 | using (TcpClient tcpClient = new TcpClient()) 38 | { 39 | await tcpClient.ConnectAsync("localhost", 61613); 40 | 41 | //Create a connector. 42 | using (IStompConnector stompConnector = 43 | new Stomp12Connector( 44 | tcpClient.GetStream(), 45 | "localhost", // Virtual host name. 46 | "admin", 47 | "password")) 48 | { 49 | // Create a connection. 50 | IStompConnection connection = await stompConnector.ConnectAsync(); 51 | 52 | // Send a message. 53 | await connection.SendAsync("/queue/example", "Anybody there!?"); 54 | 55 | // Send two messages using a transaction. 56 | IStompTransaction transaction = await connection.BeginTransactionAsync(); 57 | await transaction.SendAsync("/queue/example", "Hi!"); 58 | await transaction.SendAsync("/queue/example", "My name is StompNet"); 59 | await transaction.CommitAsync(); 60 | 61 | // Receive messages back. 62 | // Message handling is made by the ConsoleWriterObserver instance. 63 | await transaction.SubscribeAsync( 64 | new ConsoleWriterObserver(), 65 | "/queue/example"); 66 | 67 | // Wait for messages to be received. 68 | await Task.Delay(250); 69 | 70 | // Disconnect. 71 | await connection.DisconnectAsync(); 72 | } 73 | } 74 | } 75 | 76 | class ConsoleWriterObserver : IObserver 77 | { 78 | public void OnNext(IStompMessage message) 79 | { 80 | Console.WriteLine("MESSAGE: " + message.GetContentAsString()); 81 | 82 | if (message.IsAcknowledgeable) 83 | message.Acknowledge(); 84 | } 85 | 86 | // ERROR frames come through here. 87 | public void OnError(Exception error) 88 | { 89 | Console.WriteLine("ERROR: " + error.Message); 90 | } 91 | 92 | public void OnCompleted() 93 | { 94 | Console.WriteLine("UNSUBSCRIBED!"); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /StompNet/StompMessage.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | using StompNet.IO; 25 | using StompNet.Models.Frames; 26 | 27 | namespace StompNet 28 | { 29 | /// 30 | /// Stomp Message. 31 | /// 32 | internal class StompMessage : IStompMessage 33 | { 34 | private readonly IStompClient _client; 35 | private readonly string _transactionId; 36 | 37 | public MessageFrame MessageFrame { get; private set; } 38 | 39 | public string ContentType 40 | { 41 | get { return MessageFrame.ContentType; } 42 | } 43 | 44 | public IEnumerable Content 45 | { 46 | get { return MessageFrame.Body; } 47 | } 48 | 49 | public bool IsAcknowledgeable { get; private set; } 50 | 51 | public string GetContentAsString(Encoding encoding) 52 | { 53 | return MessageFrame.GetBodyAsString(encoding); 54 | } 55 | 56 | public string GetContentAsString() 57 | { 58 | return MessageFrame.GetBodyAsString(); 59 | } 60 | 61 | 62 | public StompMessage(IStompClient client, MessageFrame messageFrame, bool acknowledgeable, string transactionId = null) 63 | { 64 | _client = client; 65 | MessageFrame = messageFrame; 66 | IsAcknowledgeable = acknowledgeable; 67 | _transactionId = transactionId; 68 | } 69 | 70 | public Task Acknowledge( 71 | bool useReceipt = false, 72 | string transactionId = null, 73 | IEnumerable> extraHeaders = null, 74 | CancellationToken? cancellationToken = null) 75 | { 76 | if (MessageFrame.Ack == null) 77 | throw new InvalidOperationException("This message is not acknowledgeable"); 78 | 79 | return _client.WriteAckAsync( 80 | MessageFrame.Ack, 81 | useReceipt ? _client.GetNextReceiptId() : null, 82 | transactionId ?? _transactionId, 83 | extraHeaders, 84 | cancellationToken); 85 | } 86 | 87 | public Task AcknowledgeNegative( 88 | bool useReceipt = false, 89 | string transactionId = null, 90 | IEnumerable> extraHeaders = null, 91 | CancellationToken? cancellationToken = null) 92 | { 93 | if (MessageFrame.Ack == null) 94 | throw new InvalidOperationException("This message is not acknowledgeable"); 95 | 96 | return _client.WriteNackAsync( 97 | MessageFrame.Ack, 98 | useReceipt ? _client.GetNextReceiptId() : null, 99 | transactionId ?? _transactionId, 100 | extraHeaders, 101 | cancellationToken); 102 | } 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /StompNet/IStompMessage.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System.Collections.Generic; 20 | using System.Text; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | using StompNet.Models.Frames; 24 | 25 | namespace StompNet 26 | { 27 | /// 28 | /// Stomp message interface. 29 | /// 30 | public interface IStompMessage 31 | { 32 | /// 33 | /// The received message frame. 34 | /// 35 | MessageFrame MessageFrame { get; } 36 | 37 | /// 38 | /// Message content. 39 | /// 40 | IEnumerable Content { get; } 41 | 42 | /// 43 | /// Message content type. 44 | /// 45 | string ContentType { get; } 46 | 47 | /// 48 | /// Returns true if the message should be acknowledged after processing it. 49 | /// 50 | bool IsAcknowledgeable { get; } 51 | 52 | /// 53 | /// Get content as a string using the specified encoding. 54 | /// 55 | /// Encoding to be used to decode the message content. 56 | /// A string representing the message content decoded using the specified encoding. 57 | string GetContentAsString(Encoding encoding); 58 | 59 | /// 60 | /// Get content as a string using UTF-8 encoding. 61 | /// 62 | /// A string representing the message content decoded using UTF-8 encoding. 63 | string GetContentAsString(); 64 | 65 | /// 66 | /// Acknowledge this message. 67 | /// 68 | /// Indicates whether to require the service to confirm receipt of the message. 69 | /// The id of the transaction this acknowledge makes part of. 70 | /// Non-standard headers to include in the connect request frame. 71 | /// The token to monitor for cancellation requests. 72 | /// A task representing the acknowledge operation. 73 | Task Acknowledge( 74 | bool useReceipt = false, 75 | string transactionId = null, 76 | IEnumerable> extraHeaders = null, 77 | CancellationToken? cancellationToken = null); 78 | 79 | /// 80 | /// Acknowledge negative this message. 81 | /// 82 | /// Indicates whether to require the service to confirm receipt of the message. 83 | /// The id of the transaction this acknowledge negative makes part of. 84 | /// Non-standard headers to include in the connect request frame. 85 | /// The token to monitor for cancellation requests. 86 | /// A task representing the acknowledge negative operation. 87 | Task AcknowledgeNegative( 88 | bool useReceipt = false, 89 | string transactionId = null, 90 | IEnumerable> extraHeaders = null, 91 | CancellationToken? cancellationToken = null); 92 | } 93 | } -------------------------------------------------------------------------------- /StompNet/IO/StompFrameObservable.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Concurrent; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | using StompNet.Exceptions; 24 | using StompNet.Helpers; 25 | using StompNet.Models; 26 | using StompNet.Models.Frames; 27 | 28 | namespace StompNet.IO 29 | { 30 | /// 31 | /// Implementation of a wrapper of an IStompFrameReader into an IStompFrameObservable. 32 | /// 33 | public class StompFrameObservable : IStompFrameObservable 34 | { 35 | private readonly ConcurrentDictionary, IObserver> _observers; 36 | private readonly IStompFrameReader _reader; 37 | 38 | /// 39 | /// Status of the processing of incoming frames. 40 | /// 0 => Created, 1 => Started 41 | /// 42 | private int _status; 43 | 44 | public bool IsStarted 45 | { 46 | get { return (_status == 1); } 47 | } 48 | 49 | public string ProtocolVersion 50 | { 51 | get { return _reader.ProtocolVersion; } 52 | } 53 | 54 | /// 55 | /// Constructor. 56 | /// 57 | /// Frame reader. 58 | public StompFrameObservable(IStompFrameReader reader) 59 | { 60 | _observers = new ConcurrentDictionary, IObserver>(); 61 | _reader = reader; 62 | _status = 0; 63 | } 64 | 65 | public void Start(CancellationToken cancellationToken) 66 | { 67 | //Avoid multiple starts. 68 | if (Interlocked.CompareExchange(ref _status, 1, 0) != 0) 69 | throw new InvalidOperationException("The observable is already started."); 70 | 71 | Task.Factory.StartNew( 72 | async () => await ReadFrames(cancellationToken), 73 | cancellationToken, 74 | TaskCreationOptions.None, //TaskCreationOptions.LongRunning, 75 | TaskScheduler.Default); 76 | } 77 | 78 | private async Task ReadFrames(CancellationToken cancellationToken) 79 | { 80 | try 81 | { 82 | while (!cancellationToken.IsCancellationRequested) 83 | { 84 | Frame value = await _reader.ReadFrameAsync(cancellationToken); 85 | 86 | if (value.Command == StompCommands.Error) 87 | throw new ErrorFrameException(new ErrorFrame(value.Headers, value.BodyArray)); 88 | 89 | _observers.Values.DoIgnoringExceptions(o => o.OnNext(value)); 90 | } 91 | } 92 | catch (OperationCanceledException) 93 | { 94 | _observers.Values.DoIgnoringExceptions(o => o.OnCompleted()); 95 | } 96 | catch (Exception e) 97 | { 98 | _observers.Values.DoIgnoringExceptions(o => o.OnError(e)); 99 | } 100 | } 101 | 102 | public IDisposable Subscribe(IObserver observer) 103 | { 104 | _observers[observer] = observer; 105 | 106 | return new ActionDisposable(() => _observers.TryRemove(observer, out observer)); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /StompNet/StompSubscription.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Concurrent; 21 | using System.Threading; 22 | using StompNet.IO; 23 | using StompNet.Models; 24 | using StompNet.Models.Frames; 25 | 26 | namespace StompNet 27 | { 28 | /// 29 | /// Stomp Subscription. 30 | /// 31 | /// This is a class to filter all elements incoming from the server. 32 | /// It only allows the observer (received in the constructor) to receive 33 | /// messages from a given subscription id. 34 | /// 35 | /// This class does NOT do any communication (send/receive). Anyway, it 36 | /// receives an IStompClient because IStompMessages may need it. 37 | /// 38 | internal sealed class StompSubscription : IObserver, IDisposable 39 | { 40 | // I think that if I don't root my subscriptions and the user does not keep 41 | // a reference then they may be garbage-collected. 42 | private static readonly ConcurrentDictionary Root = new ConcurrentDictionary(); 43 | 44 | private readonly IStompClient _client; 45 | private readonly IObserver _observer; 46 | private readonly string _id; 47 | private readonly bool _acknowledgeableMessages; 48 | private readonly string _transactionId; 49 | private readonly IDisposable _disposableSubscription; 50 | 51 | public StompSubscription(IStompClient client, IObserver observer, string id, bool acknowledgeableMessages, string transactionId = null) 52 | { 53 | _client = client; 54 | _observer = observer; 55 | _id = id; 56 | _transactionId = transactionId; 57 | _acknowledgeableMessages = acknowledgeableMessages; 58 | _disposableSubscription = client.Subscribe(this); 59 | 60 | Root[this] = this; 61 | } 62 | 63 | 64 | public void OnNext(Frame value) 65 | { 66 | if (value.Command == StompCommands.Message) 67 | { 68 | MessageFrame messageFrame = value as MessageFrame ?? new MessageFrame(value.Headers, value.BodyArray); 69 | 70 | if (messageFrame.Subscription == _id) 71 | { 72 | _observer.OnNext(new StompMessage(_client, messageFrame, _acknowledgeableMessages, _transactionId)); 73 | } 74 | } 75 | } 76 | 77 | public void OnError(Exception error) 78 | { 79 | _observer.OnError(error); 80 | Dispose(false); 81 | } 82 | 83 | public void OnCompleted() 84 | { 85 | _observer.OnCompleted(); 86 | Dispose(false); 87 | } 88 | 89 | #region IDisposable Implementation 90 | 91 | private int _disposed = 0; 92 | 93 | public void Dispose() 94 | { 95 | Dispose(true); 96 | } 97 | 98 | private void Dispose(bool disposing) 99 | { 100 | if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) 101 | return; 102 | 103 | _disposableSubscription.Dispose(); 104 | StompSubscription _; 105 | Root.TryRemove(this, out _); 106 | 107 | if(disposing) 108 | { 109 | _observer.OnCompleted(); 110 | } 111 | } 112 | 113 | //This may not be called before disposing because I'm rooting all subscriptions until they are disposed. 114 | ~StompSubscription() 115 | { 116 | Dispose(false); 117 | } 118 | 119 | #endregion 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /StompNet.Examples/3.ExampleConnectorConcurrent.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Collections.ObjectModel; 22 | using System.Net.Sockets; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | using StompNet; 26 | using StompNet.Models; 27 | 28 | namespace Stomp.Net.Examples 29 | { 30 | partial class Program 31 | { 32 | /// 33 | /// This example will demonstrate the thread-safety of IStompConnector 34 | /// 35 | /// 36 | public static async Task ExampleConnectorConcurrent() 37 | { 38 | using (TcpClient tcpClient = new TcpClient(serverHostname, serverPort)) 39 | using (IStompConnector stompConnector = new Stomp12Connector(tcpClient.GetStream(), virtualHost, login, passcode)) 40 | { 41 | IStompConnection connection = await stompConnector.ConnectAsync(); 42 | 43 | // Create threads to send messages. 44 | ICollection threads = new Collection(); 45 | for (int i = 0; i < 10; i++) 46 | { 47 | Thread t = new Thread( 48 | async () => 49 | { 50 | for(int j = 0; j < 100; j++) 51 | await connection.SendAsync(aQueueName, messageContent); 52 | }); 53 | threads.Add(t); 54 | } 55 | 56 | // Start threads 57 | foreach(Thread t in threads) 58 | t.Start(); 59 | 60 | // Subscribe 61 | IDisposable subscription = 62 | await connection.SubscribeAsync( 63 | new CounterObserver(), // An observer that count messages and print the count on completed. 64 | aQueueName, 65 | StompAckValues.AckClientIndividualValue); // Messages must be acked before the broker discards them. 66 | 67 | Console.WriteLine("Please wait a few seconds..."); 68 | Console.WriteLine(); 69 | 70 | // Wait for the threads to finish. 71 | foreach (Thread t in threads) 72 | t.Join(); 73 | 74 | // Wait for a little longer for messages to arrive. 75 | await Task.Delay(3000); 76 | 77 | // Unsubscribe. 78 | // After this invocation, observers's OnCompleted is going to be called. 79 | // For this example, a count of 1000 should be shown unless there was 80 | // previous data in the queue. 81 | subscription.Dispose(); 82 | 83 | // Disconnect. 84 | await connection.DisconnectAsync(); 85 | } 86 | } 87 | 88 | /// 89 | /// An observable that counts the number of messages received. 90 | /// When unsubscribing, this will print the count. 91 | /// 92 | class CounterObserver : IObserver 93 | { 94 | private int _count = 0; 95 | 96 | public void OnNext(IStompMessage message) 97 | { 98 | _count++; 99 | 100 | if (message.IsAcknowledgeable) 101 | message.Acknowledge(true); 102 | } 103 | 104 | public void OnError(Exception error) 105 | { 106 | Console.WriteLine("EXCEPTION!" ); 107 | Console.WriteLine(error.Message); 108 | Console.WriteLine(); 109 | } 110 | 111 | public void OnCompleted() 112 | { 113 | Console.WriteLine(); 114 | Console.WriteLine("{0} MESSAGES WERE OBSERVED.", _count); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /StompNet.Tests/StompNet.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {546EC5FE-779D-4CFA-9679-091162FE0C6F} 7 | Library 8 | Properties 9 | StompNet.Tests 10 | StompNet.Tests 11 | v4.5.2 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {2BE5619B-B63B-4583-B434-371C925FE18C} 61 | StompNet 62 | 63 | 64 | 65 | 66 | 67 | 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StompNet 2 | StompNet is an asynchronous [STOMP 1.2][stomp12specification] client library for .NET 4.5 and .NET Standard 1.5. 3 | 4 | ## Features 5 | - High level interfaces to ease the flow of the application. 6 | - Built using the observer pattern. It can be used for reactive 7 | application approaches. This is a great library to be used together 8 | with [Reactive Extensions library][reactive-extensions]. 9 | - Thread-safe. 10 | - Library user would rarely have to interact with STOMP frames directly, unless 11 | it is desired. 12 | - Message receipt confirmations and sequence number generation for frames are 13 | automatically handled by the library. 14 | 15 | ## Installation 16 | On your Package Manager Console: 17 | Install-Package StompNet 18 | 19 | ## Usage 20 | 21 | The following code is an example to show the basic usage of StompNet. 22 | 23 | ```csharp 24 | // Example: Send three messages before receiving them back. 25 | static async Task ReadmeExample() 26 | { 27 | // Establish a TCP connection with the STOMP service. 28 | using (TcpClient tcpClient = new TcpClient()) 29 | { 30 | await tcpClient.ConnectAsync("localhost", 61613); 31 | 32 | //Create a connector. 33 | using (IStompConnector stompConnector = 34 | new Stomp12Connector( 35 | tcpClient.GetStream(), 36 | "localhost", // Virtual host name. 37 | "admin", 38 | "password")) 39 | { 40 | // Create a connection. 41 | IStompConnection connection = await stompConnector.ConnectAsync(); 42 | 43 | // Send a message. 44 | await connection.SendAsync("/queue/example", "Anybody there!?"); 45 | 46 | // Send two messages using a transaction. 47 | IStompTransaction transaction = await connection.BeginTransactionAsync(); 48 | await transaction.SendAsync("/queue/example", "Hi!"); 49 | await transaction.SendAsync("/queue/example", "My name is StompNet"); 50 | await transaction.CommitAsync(); 51 | 52 | // Receive messages back. 53 | // Message handling is made by the ConsoleWriterObserver instance. 54 | await transaction.SubscribeAsync( 55 | new ConsoleWriterObserver(), 56 | "/queue/example"); 57 | 58 | // Wait for messages to be received. 59 | await Task.Delay(250); 60 | 61 | // Disconnect. 62 | await connection.DisconnectAsync(); 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | The observer `ConsoleWriterObserver` class for handling the incoming messages is: 69 | 70 | 71 | ```csharp 72 | class ConsoleWriterObserver : IObserver 73 | { 74 | // Incoming messages are processed here. 75 | public void OnNext(IStompMessage message) 76 | { 77 | Console.WriteLine("MESSAGE: " + message.GetContentAsString()); 78 | 79 | if (message.IsAcknowledgeable) 80 | message.Acknowledge(); 81 | } 82 | 83 | // Any ERROR frame or stream exception comes through here. 84 | public void OnError(Exception error) 85 | { 86 | Console.WriteLine("ERROR: " + error.Message); 87 | } 88 | 89 | // OnCompleted is invoked when unsubscribing. 90 | public void OnCompleted() 91 | { 92 | Console.WriteLine("UNSUBSCRIBED!"); 93 | } 94 | } 95 | ``` 96 | 97 | For more examples, take a look at the examples project where 98 | you can see how to: 99 | - Use receipt confirmations automatically. 100 | - Send byte array messages and set its content type. 101 | - Set acknowledgement mode for subscriptions. 102 | - Use low level APIs to directly read/write frames into the stream. 103 | 104 | ### Comments about the usage of the library. 105 | - IO stream connection management is not handled by the library. 106 | If the TCP connection fails, a new IStompConnector must be created. 107 | - Exceptions will be thrown by currently awaited tasks and they will 108 | also be notified to observers through the OnError method. 109 | - One and only one of the methods OnError or OnCompleted will be 110 | invoked. Both methods indicate the end of the incoming messages 111 | stream. 112 | - SubscribeAsync returns a Task that can be 113 | used to unsubscribe at any time by calling DisposeAsync. 114 | If an unsubscription is not done explicitly, unsubscription is 115 | done when the connector is disposed or an exception occurs in the 116 | stream. 117 | - All methods may receive a CancellationToken. Anyway, cancelling 118 | before a method is finished may leave the IO stream in an invalid 119 | state. 120 | 121 | ## License 122 | Apache License 2.0 123 | 124 | ## ToDo 125 | - Heartbeat support 126 | - STOMP 1.1 support 127 | 128 | [reactive-extensions]: http://msdn.microsoft.com/en-us/data/gg577609.aspx 129 | [stomp12specification]: http://stomp.github.io/stomp-specification-1.2.html -------------------------------------------------------------------------------- /StompNet.Examples/Program.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.IO; 21 | using System.Net.Sockets; 22 | using System.Threading.Tasks; 23 | 24 | namespace Stomp.Net.Examples 25 | { 26 | /// 27 | /// This is the entry point to the project contains a set of examples on how to use StompNet. 28 | /// 29 | /// You can test these using a clean installation of [Apache Apollo][http://activemq.apache.org/apollo/]. 30 | /// 31 | partial class Program 32 | { 33 | //Parameters to configure the examples. 34 | private const string serverHostname = "localhost"; 35 | private const int serverPort = 61613; 36 | private const string virtualHost = "localhost"; 37 | private const string login = "admin"; 38 | private const string passcode = "password"; 39 | private const string aQueueName = "/queue/stompNetExampleQueueONE"; 40 | private const string anotherQueueName = "/queue/stompNetExampleQueueTWO"; 41 | private const string aTopicName = "/topic/stompNetExampleTopic"; 42 | private const string messageContent = "Hola Mundo!"; 43 | 44 | static void Main() 45 | { 46 | MainAsync().Wait(); 47 | } 48 | 49 | static async Task MainAsync() 50 | { 51 | bool exampling = true; 52 | 53 | while (exampling) 54 | { 55 | WriteTitle("StompNet Examples"); 56 | Console.WriteLine("1. Send/receive MESSAGES using hi-level API (IStompConnector)"); 57 | Console.WriteLine("2. Another send/receive MESSAGES example using hi-level API (IStompConnector)"); 58 | Console.WriteLine("3. Concurrent send/receive using hi-level API (IStompConnector)"); 59 | Console.WriteLine("4. Transactions using hi-level API (IStompConnector)"); 60 | Console.WriteLine("5. Send/receive COMMANDS using mid-level API (IStompClient)"); 61 | Console.WriteLine("6. Send/receive COMMANDS using lo-level API (IStompFrame(Writer|Reader))"); 62 | Console.WriteLine("7. Exit"); 63 | Console.Write("Select operation: "); 64 | int op; 65 | int.TryParse(Console.ReadLine(), out op); 66 | 67 | Console.WriteLine(); 68 | Console.WriteLine(); 69 | try 70 | { 71 | switch (op) 72 | { 73 | case 1: 74 | await ExampleConnector(); 75 | break; 76 | case 2: 77 | await ExampleConnectorAnother(); 78 | break; 79 | case 3: 80 | await ExampleConnectorConcurrent(); 81 | break; 82 | case 4: 83 | await ExampleConnectorTransaction(); 84 | break; 85 | case 5: 86 | await ExampleClient(); 87 | break; 88 | case 6: 89 | await ExampleWriterAndReader(); 90 | break; 91 | case 7: 92 | exampling = false; 93 | break; 94 | default: 95 | WriteTitle("Invalid Selection"); 96 | break; 97 | } 98 | } 99 | catch (Exception ex) 100 | { 101 | if (ex is IOException || ex is SocketException) 102 | WriteTitle("Verify network connection, configuration parameters and broker status."); 103 | else 104 | throw; 105 | } 106 | 107 | Console.WriteLine(); 108 | Console.WriteLine(); 109 | } 110 | } 111 | 112 | //Helper to write titles. 113 | static void WriteTitle(string str) 114 | { 115 | Console.WriteLine(new string('-', str.Length + 4)); 116 | Console.WriteLine("- " + str + " -"); 117 | Console.WriteLine(new string('-', str.Length + 4)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /StompNet/StompConnectionBase.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | using StompNet.Helpers; 25 | using StompNet.IO; 26 | using StompNet.Models; 27 | 28 | namespace StompNet 29 | { 30 | /// 31 | /// This is an implementation of the basic common communication commands 32 | /// that are used by IStompConnection and IStompTransaction. Basic DRY! 33 | /// 34 | internal class StompConnectionBase 35 | { 36 | protected readonly string TransactionId; 37 | protected readonly StompClient Client; 38 | 39 | public StompConnectionBase(StompClient client, string transactionId = null) 40 | { 41 | Client = client; 42 | TransactionId = transactionId; 43 | } 44 | 45 | public Task SendAsync( 46 | string destination, 47 | byte[] content = null, 48 | string contentType = MediaTypeNames.ApplicationOctet, 49 | bool useReceipt = false, 50 | IEnumerable> extraHeaders = null, 51 | CancellationToken? cancellationToken = null) 52 | { 53 | return Client.WriteSendAsync( 54 | destination, 55 | content, 56 | contentType, 57 | useReceipt ? Client.GetNextReceiptId() : null, 58 | TransactionId, 59 | extraHeaders, 60 | cancellationToken); 61 | } 62 | 63 | public Task SendAsync( 64 | string destination, 65 | string content, 66 | Encoding encoding, 67 | bool useReceipt = false, 68 | IEnumerable> extraHeaders = null, 69 | CancellationToken? cancellationToken = null) 70 | { 71 | return Client.WriteSendAsync( 72 | destination, 73 | content, 74 | encoding, 75 | useReceipt ? Client.GetNextReceiptId() : null, 76 | TransactionId, 77 | extraHeaders, 78 | cancellationToken); 79 | } 80 | 81 | public Task SendAsync( 82 | string destination, 83 | string content, 84 | bool useReceipt = false, 85 | IEnumerable> extraHeaders = null, 86 | CancellationToken? cancellationToken = null) 87 | { 88 | return Client.WriteSendAsync( 89 | destination, 90 | content, 91 | useReceipt ? Client.GetNextReceiptId() : null, 92 | TransactionId, 93 | extraHeaders, 94 | cancellationToken); 95 | } 96 | 97 | public async Task SubscribeAsync( 98 | IObserver observer, 99 | string destination, 100 | string ack = null, 101 | bool useReceipt = true, 102 | IEnumerable> extraHeaders = null, 103 | CancellationToken? cancellationToken = null) 104 | { 105 | string subscriptionId = Client.GetNextSubscriptionId(); 106 | IDisposable disposableSubscription = 107 | new StompSubscription( 108 | Client, 109 | observer, 110 | subscriptionId, 111 | ack == StompAckValues.AckClientIndividualValue || ack == StompAckValues.AckClientValue, 112 | TransactionId); 113 | 114 | try 115 | { 116 | await Client.WriteSubscribeAsync( 117 | destination, 118 | subscriptionId, 119 | useReceipt ? Client.GetNextReceiptId() : null, 120 | ack, 121 | extraHeaders, 122 | cancellationToken); 123 | } 124 | catch (Exception) 125 | { 126 | disposableSubscription.Dispose(); 127 | throw; 128 | } 129 | 130 | return new AsyncActionDisposable( 131 | async (cancelToken) => 132 | { 133 | await Client.WriteUnsubscribeAsync(subscriptionId, useReceipt ? Client.GetNextReceiptId() : null, null, cancelToken); 134 | disposableSubscription.Dispose(); 135 | }); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /StompNet/IO/StompClient.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Threading; 21 | using System.Threading.Tasks; 22 | using StompNet.Helpers; 23 | using StompNet.Models.Frames; 24 | 25 | namespace StompNet.IO 26 | { 27 | /// 28 | /// Stomp Client. 29 | /// 30 | /// ATTENTION: This is a disposable class. 31 | /// 32 | public class StompClient : IStompClient 33 | { 34 | private const string _receiptPrefix = "rcpt-"; 35 | private const string _subscriptionPrefix = "sub-"; 36 | private const string _transactionPrefix = "trx-"; 37 | 38 | private readonly bool _cascadeDispose; 39 | private readonly IStompFrameReader _reader; 40 | private readonly IStompFrameWriter _writer; 41 | 42 | private readonly IStompFrameObservable _frameObservable; 43 | private readonly IStompFrameWriter _frameWriter; 44 | private readonly ISequenceNumberGenerator _receiptNumberGenerator; 45 | private readonly ISequenceNumberGenerator _subscriptionNumberGenerator; 46 | private readonly ISequenceNumberGenerator _transactionNumberGenerator; 47 | 48 | public bool IsStarted 49 | { 50 | get { return _frameObservable.IsStarted; } 51 | } 52 | 53 | public string ProtocolVersion 54 | { 55 | get { return _frameWriter.ProtocolVersion; } 56 | } 57 | 58 | internal StompClient( 59 | IStompFrameReader reader, 60 | IStompFrameWriter writer, 61 | TimeSpan? retryInterval = null, 62 | bool cascadeDispose = false, 63 | bool useRandomNumberGenerator = false) 64 | { 65 | if(reader.ProtocolVersion != writer.ProtocolVersion) 66 | throw new ArgumentException("Reader and writer MUST use the same protocol version."); 67 | 68 | _cascadeDispose = cascadeDispose; 69 | _reader = reader; 70 | _writer = writer; 71 | 72 | _frameObservable = new StompFrameObservable(reader); 73 | _frameWriter = new StompFrameWriterWithConfirmation(writer, _frameObservable, retryInterval); 74 | 75 | if (!useRandomNumberGenerator) 76 | { 77 | _receiptNumberGenerator = new SequenceNumberGenerator(); 78 | _subscriptionNumberGenerator = new SequenceNumberGenerator(); 79 | _transactionNumberGenerator = new SequenceNumberGenerator(); 80 | } 81 | else 82 | { 83 | _receiptNumberGenerator = new RandomSequenceNumberGenerator(); 84 | _subscriptionNumberGenerator = _receiptNumberGenerator; 85 | _transactionNumberGenerator = _receiptNumberGenerator; 86 | } 87 | } 88 | 89 | public string GetNextReceiptId() 90 | { 91 | return _receiptPrefix + _receiptNumberGenerator.Next(); 92 | } 93 | 94 | public string GetNextSubscriptionId() 95 | { 96 | return _subscriptionPrefix + _subscriptionNumberGenerator.Next(); 97 | } 98 | 99 | public string GetNextTransactionId() 100 | { 101 | return _transactionPrefix + _transactionNumberGenerator.Next(); 102 | } 103 | 104 | public Task WriteAsync(Frame frame, CancellationToken cancellationToken) 105 | { 106 | return _frameWriter.WriteAsync(frame, cancellationToken); 107 | } 108 | 109 | public void Start(CancellationToken cancellationToken) 110 | { 111 | _frameObservable.Start(cancellationToken); 112 | } 113 | 114 | public IDisposable Subscribe(IObserver observer) 115 | { 116 | return _frameObservable.Subscribe(observer); 117 | } 118 | 119 | #region IDisposable Implementation 120 | 121 | private bool _disposed = false; 122 | 123 | public void Dispose() 124 | { 125 | Dispose(true); 126 | GC.SuppressFinalize(this); 127 | } 128 | 129 | protected virtual void Dispose(bool disposing) 130 | { 131 | if(_disposed) return; 132 | 133 | _frameWriter.Dispose(); 134 | if(_cascadeDispose) 135 | { 136 | _reader.Dispose(); 137 | _writer.Dispose(); 138 | } 139 | 140 | _disposed = true; 141 | } 142 | 143 | ~StompClient() 144 | { 145 | Dispose(false); 146 | } 147 | 148 | #endregion 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /StompNet/Stomp12Connector.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.IO; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | using StompNet.IO; 25 | using StompNet.Models.Frames; 26 | 27 | namespace StompNet 28 | { 29 | /// 30 | /// Connector for Stomp 1.2 protocol. 31 | /// 32 | /// ATTENTION: This is a disposable class. 33 | /// 34 | public sealed class Stomp12Connector : IStompConnector 35 | { 36 | private readonly string _host; 37 | private readonly string _user; 38 | private readonly string _password; 39 | 40 | private readonly CancellationTokenSource _cts; 41 | private readonly StompClient _client; 42 | 43 | /// 44 | /// Constructor of a connector for STOMP 1.2. 45 | /// 46 | /// Stream for incoming data from STOMP service. 47 | /// Stream for outgoing data to STOMP service. 48 | /// Virtual host to connect to STOMP service. 49 | /// Username to authenticate to STOMP service. 50 | /// Password to authenticate to STOMP service. 51 | /// When sending messages that requires receipt confirmation, 52 | /// this interval specifies how much time to wait before sending the frame again if 53 | /// no receipt is received. 54 | /// Flag to indicate random numbers must 55 | /// be used when creating sequence numbers for receipts, subscriptions and transactions 56 | public Stomp12Connector( 57 | Stream inStream, 58 | Stream outStream, 59 | string host, 60 | string user = null, 61 | string password = null, 62 | TimeSpan? retryInterval = null, 63 | bool useRandomNumberGenerator = false) 64 | { 65 | if(host == null) 66 | throw new ArgumentNullException("host"); 67 | 68 | _cts = new CancellationTokenSource(); 69 | _client = new Stomp12Client(inStream, outStream, retryInterval, useRandomNumberGenerator); 70 | 71 | _host = host; 72 | _user = user; 73 | _password = password; 74 | } 75 | 76 | /// 77 | /// Constructor of a connector for STOMP 1.2. 78 | /// 79 | /// Stream for incoming/outgoing data from/to STOMP service. 80 | /// Virtual host to connect to STOMP service. 81 | /// Username to authenticate to STOMP service. 82 | /// Password to authenticate to STOMP service. 83 | /// When sending messages that require receipt confirmation, 84 | /// this interval specifies how much time to wait before sending the frame again if 85 | /// no receipt is received. 86 | /// Flag to indicate random numbers must 87 | /// be used when creating sequence numbers for receipts, subscriptions and transactions 88 | public Stomp12Connector( 89 | Stream stream, 90 | string host, 91 | string user = null, 92 | string password = null, 93 | TimeSpan? retryInterval = null, 94 | bool useRandomNumberGenerator = false) 95 | : this(stream, stream, host, user, password, retryInterval, useRandomNumberGenerator) 96 | { } 97 | 98 | 99 | /// 100 | /// Connect to the STOMP 1.2 service. 101 | /// 102 | /// Non-standard headers to include in the connect request frame. 103 | /// The token to monitor for cancellation requests. 104 | /// A task representing the connect operation. 105 | public async Task ConnectAsync( 106 | IEnumerable> extraHeaders = null, 107 | CancellationToken? cancellationToken = null) 108 | { 109 | _client.Start(_cts.Token); 110 | 111 | await _client.WriteConnectAsync(_host, _user, _password, Heartbeat.NoHeartbeat, extraHeaders, cancellationToken); 112 | 113 | return new StompConnection(_client); 114 | } 115 | 116 | public void Dispose() 117 | { 118 | _cts.Cancel(); 119 | _cts.Dispose(); 120 | _client.Dispose(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /StompNet/IStompConnectionBase.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using StompNet.Helpers; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Text; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | 26 | namespace StompNet 27 | { 28 | /// 29 | /// This is an interface that has the basic common communication commands 30 | /// that are used by IStompConnection and IStompTransaction. DRY! 31 | /// 32 | public interface IStompConnectionBase 33 | { 34 | /// 35 | /// Send a message. 36 | /// 37 | /// Queue/topic name which is destination of the message. 38 | /// Content of the message. 39 | /// Type of the content. 40 | /// Indicates whether to require the service to confirm receipt of the message. 41 | /// Non-standard headers to include in the connect request frame. 42 | /// The token to monitor for cancellation requests. 43 | /// A task representing the send operation. 44 | Task SendAsync( 45 | string destination, 46 | byte[] content = null, 47 | string contentType = MediaTypeNames.ApplicationOctet, 48 | bool useReceipt = false, 49 | IEnumerable> extraHeaders = null, 50 | CancellationToken? cancellationToken = null); 51 | 52 | /// 53 | /// Send a message. 54 | /// 55 | /// Queue/topic name which is destination of the message. 56 | /// Content of the message. 57 | /// Encoding used to encode the content of the message. 58 | /// Indicates whether to require the service to confirm receipt of the message. 59 | /// Non-standard headers to include in the connect request frame. 60 | /// The token to monitor for cancellation requests. 61 | /// A task representing the send operation. 62 | Task SendAsync( 63 | string destination, 64 | string content, 65 | Encoding encoding, 66 | bool useReceipt = false, 67 | IEnumerable> extraHeaders = null, 68 | CancellationToken? cancellationToken = null); 69 | 70 | /// 71 | /// Send a message. 72 | /// 73 | /// Name of the queue/topic which is destination of the message. 74 | /// Content of the message. UTF-8 encoding is used. 75 | /// Indicates whether to require the service to confirm receipt of the message. 76 | /// Non-standard headers to include in the connect request frame. 77 | /// The token to monitor for cancellation requests. 78 | /// A task representing the send operation. 79 | Task SendAsync( 80 | string destination, 81 | string content, 82 | bool useReceipt = false, 83 | IEnumerable> extraHeaders = null, 84 | CancellationToken? cancellationToken = null); 85 | 86 | /// 87 | /// Subscribe to a queue/topic. 88 | /// 89 | /// Observer to handle the received messages. 90 | /// Name of the queue/topic from which messages are get. 91 | /// Type of acknowledgement that is going to be used by the subscription. 92 | /// Its value can be: "auto" (default), "client" or "client-individual". 93 | /// Indicates whether to require the service to confirm receipt of the message. 94 | /// Non-standard headers to include in the connect request frame. 95 | /// The token to monitor for cancellation requests. 96 | /// A task representing the subscribe operation. The result of this task is an IAsyncDisposable that may be used to unsubscribe. 97 | Task SubscribeAsync( 98 | IObserver observer, 99 | string destination, 100 | string ack = null, 101 | bool useReceipt = false, 102 | IEnumerable> extraHeaders = null, 103 | CancellationToken? cancellationToken = null); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /StompNet/IO/Stomp12FrameWriter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.IO; 21 | using System.Linq; 22 | using System.Text; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | using StompNet.Models; 26 | using StompNet.Models.Frames; 27 | 28 | namespace StompNet.IO 29 | { 30 | /// 31 | /// Frame writer for Stomp 1.2. 32 | /// 33 | /// This class is not thread-safe. 34 | /// For thread-safety use StompSerialFrameWriter as a wrapper of this one. 35 | /// 36 | public sealed class Stomp12FrameWriter : IStompFrameWriter 37 | { 38 | private readonly AsyncStreamWriter _writer; 39 | private readonly bool _writerOwner; 40 | 41 | public string ProtocolVersion 42 | { 43 | get { return "1.2"; } 44 | } 45 | 46 | internal Stomp12FrameWriter(AsyncStreamWriter writer) 47 | { 48 | _writer = writer; 49 | _writerOwner = false; 50 | } 51 | 52 | /// 53 | /// Constructor for a STOMP 1.2 frame writer. 54 | /// 55 | /// The stream to write to. 56 | /// Initial write buffer capacity. 57 | /// Maximum write buffer capacity. 58 | public Stomp12FrameWriter(Stream stream, int initialBufferCapacity = AsyncStreamWriter.DefaultIniBufferCapacity, int maxBufferCapacity = AsyncStreamWriter.DefaultMaxBufferCapacity) 59 | { 60 | _writer = new AsyncStreamWriter(stream, Encoding.UTF8, initialBufferCapacity, maxBufferCapacity); 61 | _writerOwner = true; 62 | } 63 | 64 | public async Task WriteAsync(Frame frame, CancellationToken cancellationToken) 65 | { 66 | //Make sure IO operations do not get executed on UI thread. 67 | await WriteFrameAsyncImpl(frame, cancellationToken).ConfigureAwait(false); 68 | } 69 | 70 | private async Task WriteFrameAsyncImpl(Frame frame, CancellationToken cancellationToken) 71 | { 72 | #if TRACE 73 | Console.WriteLine(frame); 74 | #endif 75 | if (frame.Command == StompCommands.Connect) 76 | { 77 | if (frame.Headers.Any(kvp => kvp.Key.Any(ch => ch == '\n' || ch == ':') || kvp.Value.Any(ch => ch == '\n'))) 78 | throw new InvalidDataException("Connect header names MUST not contain LF or ':' characters; Connected header values MUST not contain any LF."); 79 | 80 | await _writer.WriteAsync(frame.Command, cancellationToken); 81 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 82 | 83 | await WriteConnectHeadersAsync(frame, cancellationToken); 84 | 85 | await _writer.WriteAsync(frame.BodyArray, cancellationToken); 86 | await _writer.WriteAsync(StompOctets.EndOfFrameByte, cancellationToken); 87 | } 88 | else 89 | { 90 | await _writer.WriteAsync(frame.Command, cancellationToken); 91 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 92 | 93 | await WriteHeadersAsync(frame, cancellationToken); 94 | 95 | await _writer.WriteAsync(frame.BodyArray, cancellationToken); 96 | await _writer.WriteAsync(StompOctets.EndOfFrameByte, cancellationToken); 97 | } 98 | 99 | await _writer.FlushAsync(cancellationToken); 100 | } 101 | 102 | private async Task WriteConnectHeadersAsync(Frame frame, CancellationToken cancellationToken) 103 | { 104 | foreach (var header in frame.Headers.Where(header => header.Value != null)) 105 | { 106 | await _writer.WriteAsync(header.Key, cancellationToken); 107 | await _writer.WriteAsync(StompOctets.ColonByte, cancellationToken); 108 | await _writer.WriteAsync(header.Value, cancellationToken); 109 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 110 | } 111 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 112 | } 113 | 114 | private async Task WriteHeadersAsync(Frame frame, CancellationToken cancellationToken) 115 | { 116 | foreach (var header in frame.Headers.Where(header => header.Value != null)) 117 | { 118 | await WriteEscapedString(header.Key, cancellationToken); 119 | await _writer.WriteAsync(StompOctets.ColonByte, cancellationToken); 120 | await WriteEscapedString(header.Value, cancellationToken); 121 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 122 | } 123 | await _writer.WriteAsync(StompOctets.LineFeedByte, cancellationToken); 124 | } 125 | 126 | private async Task WriteEscapedString(string str, CancellationToken cancellationToken) 127 | { 128 | int cue = 0; 129 | for(int i = 0; i < str.Length; i++) 130 | { 131 | byte[] escapedChar = StompOctets.EscapeOctet(str[i]); 132 | 133 | if (escapedChar != null) 134 | { 135 | await _writer.WriteAsync(str, cue, i - cue, cancellationToken); 136 | await _writer.WriteAsync(escapedChar, cancellationToken); 137 | cue = i + 1; 138 | } 139 | } 140 | await _writer.WriteAsync(str, cue, str.Length - cue, cancellationToken); 141 | } 142 | 143 | public void Dispose() 144 | { 145 | if (_writerOwner) 146 | _writer.Dispose(); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /StompNet/Models/Frames/Frame.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | using System.Text; 23 | using StompNet.Helpers; 24 | 25 | namespace StompNet.Models.Frames 26 | { 27 | /// 28 | /// Class representing a STOMP FRAME. 29 | /// 30 | /// No validation is done here except a command string is required. 31 | /// All validations must be done in inheritors. 32 | /// 33 | /// This object is not immutable, BUT should be used as if it were. 34 | /// 35 | public class Frame 36 | { 37 | /// 38 | /// Value used when the body is empty. 39 | /// 40 | public static readonly byte[] EmptyBody = new byte[0]; 41 | 42 | /// 43 | /// Value to be used when a heartbeat is received. 44 | /// 45 | public static readonly Frame HeartbeatFrame = new Frame(StompCommands.Heartbeat, null); 46 | 47 | /// 48 | /// Frame command. 49 | /// 50 | public string Command { get; private set; } 51 | 52 | /// 53 | /// Frame headers. 54 | /// 55 | public IEnumerable> Headers { get; private set; } 56 | 57 | /// 58 | /// Frame body. 59 | /// 60 | public IEnumerable Body { get { return BodyArray; } } 61 | 62 | /// 63 | /// Frame body as a byte array. To be used internally by the library. 64 | /// 65 | internal byte[] BodyArray { get; private set; } 66 | 67 | /// 68 | /// Constructor. 69 | /// 70 | /// Frame command. 71 | /// Frame headers. 72 | /// Frame body. 73 | public Frame(string command, IEnumerable> headers, byte[] body = null) 74 | { 75 | if(command == null) 76 | throw new ArgumentNullException("command"); 77 | 78 | Command = command; 79 | Headers = headers ?? Enumerable.Empty>(); 80 | BodyArray = body == null || body.Length == 0 ? EmptyBody : body; 81 | } 82 | 83 | /// 84 | /// Create and throws the exception used when a mandatory header is not included. 85 | /// 86 | /// Name of the mandatory header. 87 | protected static void ThrowMandatoryHeaderException(string headerName) 88 | { 89 | throw new ArgumentException("'" + headerName + "' header is mandatory.", headerName); 90 | } 91 | 92 | /// 93 | /// Gets the value of the first ocurrence of the specified header. 94 | /// 95 | /// Name of the header. 96 | /// The value of the header or null if it is not found. 97 | public string GetHeader(string name) 98 | { 99 | return Headers.Where(h => h.Key == name).Select(h => h.Value).FirstOrDefault(); 100 | } 101 | 102 | /// 103 | /// Gets all the values of the specified header. 104 | /// 105 | /// Name of the header. 106 | /// An enumerable of the values of the header. 107 | public IEnumerable GetAllHeaderValues(string name) 108 | { 109 | return Headers.Where(h => h.Key == name).Select(h => h.Value); 110 | } 111 | 112 | /// 113 | /// Get body as a string using the specified encoding. 114 | /// 115 | /// Encoding to be used to decode the message body. 116 | /// A string representing the message body decoded using the specified encoding. 117 | public string GetBodyAsString(Encoding encoding) 118 | { 119 | if(encoding == null) 120 | throw new ArgumentNullException("encoding"); 121 | 122 | return BodyArray != null ? encoding.GetString(BodyArray) : null; 123 | } 124 | 125 | /// 126 | /// Get body as a string using UTF-8 encoding. 127 | /// 128 | /// A string representing the message body decoded using UTF-8 encoding. 129 | public string GetBodyAsString() 130 | { 131 | return GetBodyAsString(Encoding.UTF8); 132 | } 133 | 134 | /// 135 | /// String representation of the Frame. 136 | /// 137 | /// A string representing the frame. 138 | public override string ToString() 139 | { 140 | StringBuilder str = new StringBuilder(); 141 | 142 | str.AppendLine(Command); 143 | Headers.Do(kvp => str.AppendLine(kvp.Key + ":" + kvp.Value)); 144 | str.AppendLine(); 145 | 146 | string contentType = GetHeader(StompHeaders.ContentType); 147 | if (contentType != null && contentType.StartsWith("text/") && (contentType.Contains("utf-8") || contentType.Contains("ascii"))) 148 | { 149 | str.Append(Encoding.UTF8.GetString(BodyArray.Take(60).ToArray())); 150 | if (BodyArray.Length > 60) str.Append(" ..."); 151 | } 152 | else 153 | { 154 | str.Append(string.Join(" ", BodyArray.Take(20).Select(b => b.ToString("X2")))); 155 | if (BodyArray.Length > 20) str.Append(" ..."); 156 | } 157 | 158 | str.AppendLine(); 159 | return str.ToString(); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /StompNet.Examples/5.ExampleClient.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Net.Sockets; 21 | using System.Threading.Tasks; 22 | using StompNet.Helpers; 23 | using StompNet.IO; 24 | using StompNet.Models; 25 | using StompNet.Models.Frames; 26 | 27 | namespace Stomp.Net.Examples 28 | { 29 | partial class Program 30 | { 31 | /// 32 | /// This example shows the basic usage of the mid level API of the library using IStompClient interface. 33 | /// 34 | /// This API as the high level one is based on a Observer pattern. But IStompClient does NOT have high 35 | /// level classes nor it manages receipts automatically. Using this part of the library allows you to 36 | /// have direct access to STOMP frames. 37 | /// 38 | /// In this example, a couple of messages are going to be sent to a queue and they will be read from 39 | /// the same queue. 40 | /// 41 | public static async Task ExampleClient() 42 | { 43 | using (TcpClient tcpClient = new TcpClient(serverHostname, serverPort)) 44 | //In this example we are creating a Stomp12Client, NOT a Stomp12Connector. 45 | using (IStompClient stompClient = new Stomp12Client(tcpClient.GetStream())) 46 | { 47 | // After building the client, at least one subscription to (ALL) incoming FRAMES should be made. 48 | // Do NOT confuse STOMP subscriptions in the high level API to these FRAME subscriptions. 49 | // Observers can be subscribed in two ways: 50 | // 1. Using SubscribeEx extension method which receives three delegate/Actions: OnNext, OnError 51 | // and OnCompleted. 52 | IDisposable subscription1 = 53 | stompClient.SubscribeEx( 54 | (frame) => //This is an instance of a Frame class, not an IStompMessage. 55 | { 56 | Console.WriteLine("RECEIVED COMMAND: " + frame.Command); 57 | Console.WriteLine(); 58 | } 59 | ); 60 | 61 | // 2. Implement an IObserver and pass an instance as an argument. 62 | IDisposable subscription2 = 63 | stompClient.Subscribe(new MessageFrameOnlyObserver()); 64 | 65 | // NOTE ABOUT SUBSCRIBE: Order of observer invocation is not guaranteed. 66 | 67 | // Start receiving messages. 68 | stompClient.Start(); 69 | 70 | // Now you can write STOMP commands on the stream. 71 | 72 | // CONNECT Command 73 | await stompClient.WriteConnectAsync(virtualHost, login, passcode); 74 | 75 | // SUBSCRIBE Command 76 | string subscriptionId = stompClient.GetNextReceiptId(); 77 | await stompClient.WriteSubscribeAsync( 78 | aQueueName, 79 | stompClient.GetNextSubscriptionId(), 80 | subscriptionId); 81 | 82 | // SEND Command 83 | await stompClient.WriteSendAsync( 84 | aQueueName, 85 | messageContent + " (WITHOUT RECEIPT)."); 86 | 87 | 88 | // SEND Command 89 | await stompClient.WriteSendAsync( 90 | aQueueName, 91 | messageContent + (" (WITH RECEIPT)."), 92 | stompClient.GetNextReceiptId()); 93 | //Notice a receipt id has to be created manually and a RECEIPT command should be expected in the observers. 94 | 95 | // Wait some time for the messages to be received. 96 | await Task.Delay(500); 97 | 98 | // UNSUBSCRIBE Command 99 | await stompClient.WriteUnsubscribeAsync(subscriptionId); 100 | 101 | // DISCONNECT Command 102 | await stompClient.WriteDisconnectAsync(); 103 | } 104 | } 105 | } 106 | 107 | /// 108 | /// This class will receive all kind of Frames, but it will only process message frames. 109 | /// 110 | class MessageFrameOnlyObserver : IObserver 111 | { 112 | public void OnNext(Frame frame) 113 | { 114 | //If frame command is MESSAGE. 115 | if (frame.Command == StompCommands.Message) 116 | { 117 | // Using the incoming frame, Interpret will create a new instance of a sub-class 118 | // of Frame depending on the Command. If the frame is a HEARTBEAT, the same frame 119 | // will be returned. Possible sub-classes: ConnectedFrame, ErrorFrame, MessageFrame, 120 | // ReceiptFrame. 121 | // Interpret throws exception if the incoming frame is malformed (not standard). 122 | MessageFrame message = StompInterpreter.Interpret(frame) as MessageFrame; 123 | 124 | 125 | //Print MESSAGE frame data. 126 | Console.WriteLine("MESSAGE FRAME"); 127 | Console.WriteLine("Destination: " + message.Destination); 128 | Console.WriteLine("ContentType: " + message.ContentType); 129 | Console.WriteLine("ContentLength: " + message.ContentLength); 130 | Console.WriteLine("Content:" + message.GetBodyAsString()); 131 | Console.WriteLine(); 132 | } 133 | } 134 | 135 | // IMPORTANT: ERROR frames will come through this method. 136 | public void OnError(Exception error) 137 | { 138 | Console.WriteLine("EXCEPTION!"); 139 | Console.WriteLine(error.Message); 140 | Console.WriteLine(); 141 | } 142 | 143 | public void OnCompleted() 144 | { 145 | // Do nothing. 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /StompNet/IO/StompFrameWriterWithConfirmation.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Concurrent; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | using StompNet.Helpers; 24 | using StompNet.Models; 25 | using StompNet.Models.Frames; 26 | 27 | namespace StompNet.IO 28 | { 29 | /// 30 | /// A wrapper of an IStompFrameWriter (plus an IStompFrameObservable) that waits for the receipt 31 | /// confirmation frame before returning. If a receipt message is not received, the message is 32 | /// re-send in intervals of 30 seconds. The default retry interval can be changed at the constructor. 33 | /// 34 | /// This wrapper also handles the case for connect and connected frames. As the second one is the 35 | /// confirmation of the first one. Receipt header "~connect" is reserved. (quotation marks for clarification). 36 | /// 37 | /// Receipt headers should not be reused. It may produce unexpected results. 38 | /// 39 | /// The receipt header is assumed to already come with the frame to be written on the stream. 40 | /// 41 | public class StompFrameWriterWithConfirmation : IStompFrameWriter 42 | { 43 | public static readonly TimeSpan DefRetryInterval = TimeSpan.FromSeconds(30); 44 | 45 | private const string _connectReceiptHeader = "~connect"; 46 | 47 | private readonly IStompFrameWriter _writer; 48 | private readonly ConcurrentDictionary> _receiptWaiters; 49 | private readonly TimeSpan _retryInterval; 50 | 51 | public string ProtocolVersion 52 | { 53 | get { return _writer.ProtocolVersion; } 54 | } 55 | 56 | /// 57 | /// Constructor. 58 | /// 59 | /// Frame writer. 60 | /// Frame observable to be used to receive the confirmations. 61 | /// When sending messages that requires receipt confirmation, 62 | /// this interval specifies how much time to wait before sending the frame again if 63 | /// no receipt is received. 64 | public StompFrameWriterWithConfirmation(IStompFrameWriter writer, IStompFrameObservable frameObservable, TimeSpan? retryInterval = null) 65 | { 66 | if (writer == null) 67 | throw new ArgumentNullException("writer"); 68 | 69 | if (frameObservable == null) 70 | throw new ArgumentNullException("frameObservable"); 71 | 72 | if (frameObservable.ProtocolVersion != writer.ProtocolVersion) 73 | throw new ArgumentException("Reader and writer MUST use the same protocol version."); 74 | 75 | _writer = _writer as StompSerialFrameWriter ?? new StompSerialFrameWriter(writer); 76 | frameObservable.SubscribeEx(OnNext, OnError, OnCompleted); 77 | 78 | _receiptWaiters = new ConcurrentDictionary>(); 79 | _retryInterval = retryInterval ?? DefRetryInterval; 80 | } 81 | 82 | /// 83 | /// Write a frame and wait for its receipt frame before returning. 84 | /// 85 | /// This method does not add a receipt header if it is not included already. 86 | /// If the receipt header is not in the frame headers then it returns after sending the message. 87 | /// 88 | /// 89 | /// 90 | /// 91 | public async Task WriteAsync(Frame frame, CancellationToken cancellationToken) 92 | { 93 | string receipt = frame.Command == StompCommands.Connect ? _connectReceiptHeader : frame.GetHeader(StompHeaders.Receipt); 94 | 95 | if (receipt == null) 96 | { 97 | await _writer.WriteAsync(frame, cancellationToken); 98 | } 99 | else 100 | { 101 | TaskCompletionSource receiptWaiter = new TaskCompletionSource(); 102 | Task task = receiptWaiter.Task; 103 | _receiptWaiters[receipt] = receiptWaiter; 104 | 105 | do 106 | { 107 | await _writer.WriteAsync(frame, cancellationToken); 108 | } while (await Task.WhenAny(task, Task.Delay(_retryInterval, cancellationToken)) != task 109 | && !task.IsFaulted && !task.IsCanceled); 110 | 111 | if (task.IsFaulted) throw task.Exception; 112 | if (task.IsCanceled) throw new OperationCanceledException(cancellationToken); 113 | } 114 | } 115 | 116 | private void OnNext(Frame frame) 117 | { 118 | string receiptId = 119 | frame.Command == StompCommands.Receipt ? frame.GetHeader(StompHeaders.ReceiptId) 120 | : frame.Command == StompCommands.Connected ? _connectReceiptHeader 121 | : null; 122 | 123 | if(receiptId != null) 124 | { 125 | TaskCompletionSource tcs; 126 | if (_receiptWaiters.TryGetValue(receiptId, out tcs)) 127 | { 128 | tcs.TrySetResult(null); 129 | _receiptWaiters.TryRemove(receiptId, out tcs); 130 | } 131 | } 132 | } 133 | 134 | private void OnError(Exception error) 135 | { 136 | _receiptWaiters.Values.Do(tcs => tcs.TrySetException(error)); 137 | } 138 | 139 | private void OnCompleted() 140 | { 141 | _receiptWaiters.Values.Do(tcs => tcs.TrySetCanceled()); 142 | } 143 | 144 | public void Dispose() 145 | { 146 | Dispose(true); 147 | } 148 | 149 | protected virtual void Dispose(bool disposing) 150 | { 151 | 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /StompNet.Examples/2.ExampleConnectorAnother.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Net.Sockets; 21 | using System.Threading.Tasks; 22 | using StompNet; 23 | using StompNet.Models; 24 | 25 | namespace Stomp.Net.Examples 26 | { 27 | partial class Program 28 | { 29 | /// 30 | /// This is another example of the basic usage of the high level API of the library. 31 | /// 32 | /// Multiple messages are going to be sent to a topic and a queue. After that, 33 | /// multiple observers will receive the messages. 34 | /// 35 | /// 36 | public static async Task ExampleConnectorAnother() 37 | { 38 | using (TcpClient tcpClient = new TcpClient(serverHostname, serverPort)) 39 | using (IStompConnector stompConnector = new Stomp12Connector(tcpClient.GetStream(), virtualHost, login, passcode)) 40 | { 41 | IStompConnection connection = await stompConnector.ConnectAsync(); 42 | // 43 | // QUEUE CASE 44 | // 45 | Console.WriteLine("TWO OBSERVERS ON A QUEUE"); 46 | Console.WriteLine("------------------------"); 47 | Console.WriteLine(); 48 | 49 | // Subscribe two observers to one queue. 50 | await connection.SubscribeAsync(new ExampleObserver("QUEUE OBSERVER 1"), aQueueName, StompAckValues.AckClientIndividualValue, true); 51 | await connection.SubscribeAsync(new ExampleObserver("QUEUE OBSERVER 2"), aQueueName, StompAckValues.AckClientIndividualValue, true); 52 | 53 | // Send three messages. 54 | for (int i = 1; i <= 3; i++) 55 | // As you see the receipt flag is true, so SendAsync will wait for a receipt confirmation. 56 | await connection.SendAsync(aQueueName, messageContent + " #" + i, true); 57 | 58 | // Wait some time for the messages to be received. 59 | await Task.Delay(500); 60 | 61 | // 62 | // TOPIC CASE 63 | // 64 | Console.WriteLine("TWO OBSERVERS ON A TOPIC"); 65 | Console.WriteLine("------------------------"); 66 | Console.WriteLine(); 67 | 68 | // Subscribe two observers to one topic. 69 | await connection.SubscribeAsync(new ExampleObserver("TOPIC OBSERVER 1"), aTopicName, StompAckValues.AckClientIndividualValue, true); 70 | await connection.SubscribeAsync(new ExampleObserver("TOPIC OBSERVER 2"), aTopicName, StompAckValues.AckClientIndividualValue, true); 71 | 72 | // Send three messages. As you see the receipt flag is true. 73 | for (int i = 1; i <= 3; i++) 74 | // As you see the receipt flag is true, so SendAsync will wait for a receipt confirmation. 75 | await connection.SendAsync(aTopicName, messageContent + " #" + i, true); 76 | 77 | // Wait some time for the messages to be received. 78 | await Task.Delay(500); 79 | 80 | // Disconnect. 81 | await connection.DisconnectAsync(); 82 | } 83 | 84 | // This delay is for console output formatting purposes. 85 | // It makes sure the OnCompleted methods of the observers are invoked before 86 | // showing the main menu again. In other words, when this is removed, completion 87 | // messages of the observer may appear after the main menu has been showed. 88 | await Task.Delay(500); 89 | } 90 | 91 | ///// 92 | ///// Class observer of STOMP incoming messages. 93 | ///// 94 | //class ExampleObserver : IObserver 95 | //{ 96 | // private string _name; 97 | 98 | // public ExampleObserver(string name = null) 99 | // { 100 | // _name = name ?? "OBSERVER"; 101 | // } 102 | 103 | // /// 104 | // /// Each message from the subscription is processed on this method. 105 | // /// 106 | // public void OnNext(IStompMessage message) 107 | // { 108 | // Console.WriteLine("RECEIVED ON '{0}'", _name); 109 | 110 | // if(message.ContentType == MediaTypeNames.Application.Octet) 111 | // Console.WriteLine("Message Content (HEX): " + string.Join(" ", message.Content.Select(b => b.ToString("X2")))); 112 | // else 113 | // Console.WriteLine("Message Content: " + message.GetContentAsString()); 114 | 115 | // Console.WriteLine(); 116 | 117 | // // If the message is acknowledgeable then acknowledge. 118 | // if (message.IsAcknowledgeable) 119 | // message.Acknowledge(); //This command may receive as parameters: a receipt flag, extra-headers and/or a cancellation token. 120 | // } 121 | 122 | // /// 123 | // /// Any error on the input stream or will come through this method. 124 | // /// If an ERROR frame is received, this method will also be called 125 | // /// with a ErrorFrameException as parameter. 126 | // /// 127 | // /// If this method is invoked, no more messages will be received in 128 | // /// this subscriptions. 129 | // /// 130 | // public void OnError(Exception error) 131 | // { 132 | // Console.WriteLine("EXCEPTION ON '{0}'", _name); 133 | // Console.WriteLine(error.Message); 134 | // Console.WriteLine(); 135 | // } 136 | 137 | // /// 138 | // /// This method is invoked when unsubscribing. 139 | // /// 140 | // /// If this method is invoked, no more messages will be received in 141 | // /// this subscriptions. 142 | // /// 143 | // public void OnCompleted() 144 | // { 145 | // Console.WriteLine("OBSERVING ON '{0}' IS COMPLETED.", _name); 146 | // Console.WriteLine(); 147 | // } 148 | //} 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /StompNet/IO/AsyncStreamWriter.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.IO; 21 | using System.Text; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | 25 | namespace StompNet.IO 26 | { 27 | /// 28 | /// Custom asynchronous StreamWriter. 29 | /// 30 | internal sealed class AsyncStreamWriter : IDisposable 31 | { 32 | public const int DefaultIniBufferCapacity = 1 << 10; 33 | public const int DefaultMaxBufferCapacity = 1 << 20; 34 | 35 | private byte[] _buffer; 36 | private int _count; 37 | private readonly int _maxBufferCapacity; 38 | 39 | private readonly Stream _stream; 40 | private readonly Encoder _encoder; 41 | 42 | public AsyncStreamWriter(Stream stream, Encoding encoding, int initialBufferCapacity = DefaultIniBufferCapacity, int maxBufferCapacity = DefaultMaxBufferCapacity) 43 | { 44 | if (initialBufferCapacity < 4) 45 | throw new ArgumentOutOfRangeException("initialBufferCapacity"); 46 | 47 | if (initialBufferCapacity > maxBufferCapacity) 48 | maxBufferCapacity = initialBufferCapacity; 49 | 50 | _stream = stream; 51 | _buffer = new byte[initialBufferCapacity]; 52 | _maxBufferCapacity = maxBufferCapacity; 53 | _encoder = encoding.GetEncoder(); 54 | } 55 | 56 | public AsyncStreamWriter(Stream stream, int initialBufferCapacity = DefaultIniBufferCapacity, int maxBufferCapacity = DefaultMaxBufferCapacity) 57 | : this (stream, Encoding.UTF8, initialBufferCapacity, maxBufferCapacity) 58 | { 59 | 60 | } 61 | 62 | public async Task WriteAsync(byte value, CancellationToken cancellationToken) 63 | { 64 | if (await TryPrepareBuffer(1, false, cancellationToken)) 65 | { 66 | _buffer[_count] = value; 67 | _count++; 68 | } 69 | else 70 | { 71 | _stream.WriteByte(value); 72 | } 73 | } 74 | 75 | public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 76 | { 77 | if (await TryPrepareBuffer(count, false, cancellationToken)) 78 | { 79 | Buffer.BlockCopy(buffer, offset, _buffer, _count, count); 80 | _count += count; 81 | } 82 | else 83 | { 84 | await _stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken); 85 | } 86 | } 87 | 88 | public Task WriteAsync(byte[] buffer, CancellationToken cancellationToken) 89 | { 90 | return WriteAsync(buffer, 0, buffer.Length, cancellationToken); 91 | } 92 | 93 | public async Task WriteAsync(char[] chars, int charOffset, int charCount, CancellationToken cancellationToken) 94 | { 95 | bool completed; 96 | int charsUsed, bytesUsed; 97 | 98 | _encoder.Convert(chars, 0, chars.Length, _buffer, _count, _buffer.Length - _count, false, out charsUsed, out bytesUsed, out completed); 99 | _count += bytesUsed; 100 | 101 | while (!completed) 102 | { 103 | try 104 | { 105 | await TryPrepareBuffer(_buffer.Length * 2, true, cancellationToken); 106 | } 107 | catch (OperationCanceledException) 108 | { 109 | _encoder.Reset(); 110 | throw; 111 | } 112 | 113 | _encoder.Convert(chars, 0, chars.Length, _buffer, _count, _buffer.Length - _count, false, out charsUsed, out bytesUsed, out completed); 114 | _count += bytesUsed; 115 | } 116 | 117 | _encoder.Reset(); 118 | } 119 | 120 | public Task WriteAsync(char[] chars, CancellationToken cancellationToken) 121 | { 122 | return WriteAsync(chars, 0, chars.Length, cancellationToken); 123 | } 124 | 125 | public Task WriteAsync(string str, int strOffset, int strCount, CancellationToken cancellationToken) 126 | { 127 | char[] chars = str.ToCharArray(strOffset, strCount); 128 | return WriteAsync(chars, 0, chars.Length, cancellationToken); 129 | } 130 | 131 | public Task WriteAsync(string str, CancellationToken cancellationToken) 132 | { 133 | return WriteAsync(str, 0, str.Length, cancellationToken); 134 | } 135 | 136 | public async Task FlushAsync(CancellationToken cancellationToken) 137 | { 138 | await _stream.WriteAsync(_buffer, 0, _count, cancellationToken); 139 | _count = 0; 140 | 141 | await _stream.FlushAsync(cancellationToken); 142 | } 143 | 144 | private async Task TryPrepareBuffer(int size, bool toMaxSizeWhenFailed, CancellationToken cancellationToken) 145 | { 146 | if (_count + size <= _buffer.Length) 147 | { 148 | return true; 149 | } 150 | 151 | if (_count + size <= _maxBufferCapacity) 152 | { 153 | int newLength = _buffer.Length * 2; 154 | while (newLength < _count + size && newLength < _maxBufferCapacity) 155 | newLength *= 2; 156 | 157 | if (newLength > _maxBufferCapacity) 158 | newLength = _maxBufferCapacity; 159 | 160 | Array.Resize(ref _buffer, newLength); 161 | 162 | return true; 163 | } 164 | 165 | if (size <= _maxBufferCapacity) 166 | { 167 | await FlushAsync(cancellationToken); 168 | return await TryPrepareBuffer(size, toMaxSizeWhenFailed, cancellationToken); 169 | } 170 | 171 | await FlushAsync(cancellationToken); 172 | 173 | if (toMaxSizeWhenFailed && _buffer.Length < _maxBufferCapacity) 174 | { 175 | _buffer = new byte[_maxBufferCapacity]; 176 | //Array.Resize(ref _buffer, _maxBufferCapacity); 177 | } 178 | 179 | return false; 180 | } 181 | 182 | public void Dispose() 183 | { 184 | 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /StompNet/IO/AsyncStreamReader.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.IO; 22 | using System.Linq; 23 | using System.Text; 24 | using System.Threading; 25 | using System.Threading.Tasks; 26 | 27 | namespace StompNet.IO 28 | { 29 | /// 30 | /// Custom asynchronous StreamReader. 31 | /// 32 | internal sealed class AsyncStreamReader : IDisposable 33 | { 34 | private readonly bool _bufferedStreamOwner; 35 | private readonly Stream _stream; 36 | private readonly Decoder _decoder; 37 | 38 | private readonly byte[] _oneByte; 39 | private readonly char[] _oneChar; 40 | private readonly MemoryStream _memoryStream; 41 | private readonly StringBuilder _stringBuilder; 42 | 43 | public AsyncStreamReader(Stream stream, Encoding encoding = null, int? bufferCapacity = null) 44 | { 45 | _bufferedStreamOwner = !(stream is BufferedStream) || bufferCapacity.HasValue; 46 | 47 | _stream = 48 | ! _bufferedStreamOwner ? stream // CAREFUL! This is a negative(!) condition. 49 | : bufferCapacity == null ? new BufferedStream(stream) 50 | : new BufferedStream(stream, bufferCapacity.Value); 51 | 52 | _decoder = (encoding ?? Encoding.UTF8).GetDecoder(); 53 | 54 | _oneByte = new byte[1]; 55 | _oneChar = new char[1]; 56 | _memoryStream = new MemoryStream(); 57 | _stringBuilder = new StringBuilder(); 58 | } 59 | 60 | // On some cases disposing is propagated faster than cancellation and ObjectDisposedException is generated. 61 | // This is a helper for these cases. 62 | private static async Task ThrowIfCancellationRequested(Task task, CancellationToken cancellationToken) 63 | { 64 | try 65 | { 66 | return await task; 67 | } 68 | catch (OperationCanceledException) 69 | { 70 | throw; 71 | } 72 | catch (Exception) 73 | { 74 | cancellationToken.ThrowIfCancellationRequested(); 75 | throw; 76 | } 77 | } 78 | 79 | public Task ReadBytesAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 80 | { 81 | return ThrowIfCancellationRequested( 82 | _stream.ReadAsync(buffer, offset, count, cancellationToken), 83 | cancellationToken); 84 | } 85 | 86 | public async Task ReadNBytesAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 87 | { 88 | int totalRead = 0; 89 | while (totalRead < count) 90 | { 91 | int bRead = await ReadBytesAsync(buffer, offset + totalRead, count - totalRead, cancellationToken); 92 | 93 | if (bRead == 0) 94 | { 95 | throw new EndOfStreamException(); 96 | } 97 | totalRead += bRead; 98 | } 99 | } 100 | 101 | public async Task ReadByteAsync(CancellationToken cancellationToken) 102 | { 103 | await ReadNBytesAsync(_oneByte, 0, 1, cancellationToken); 104 | 105 | return _oneByte[0]; 106 | } 107 | 108 | public async Task ReadCharAsync(CancellationToken cancellationToken) 109 | { 110 | do 111 | { 112 | await ReadNBytesAsync(_oneByte, 0, 1, cancellationToken); 113 | } while (_decoder.GetChars(_oneByte, 0, 1, _oneChar, 0, false) == 0); 114 | 115 | return _oneChar[0]; 116 | } 117 | 118 | public async Task ReadBytesUntilAsync(byte targetByte, CancellationToken cancellationToken) 119 | { 120 | _memoryStream.Seek(0, SeekOrigin.Begin); 121 | _memoryStream.SetLength(0); 122 | 123 | byte b; 124 | while ((b = await ReadByteAsync(cancellationToken)) != targetByte) 125 | { 126 | _memoryStream.WriteByte(b); 127 | } 128 | 129 | return _memoryStream.ToArray(); 130 | } 131 | 132 | public async Task ReadStringUntilAsync(IEnumerable targetChars, CancellationToken cancellationToken) 133 | { 134 | char[] targetCharsArray = targetChars as char[] ?? targetChars.ToArray(); 135 | _stringBuilder.Clear(); 136 | 137 | char ch = await ReadCharAsync(cancellationToken); 138 | while (!targetCharsArray.Contains(ch)) 139 | { 140 | _stringBuilder.Append(ch); 141 | ch = await ReadCharAsync(cancellationToken); 142 | } 143 | 144 | ReadStringUntilResult result; 145 | result.String = _stringBuilder.ToString(); 146 | result.TargetChar = ch; 147 | return result; 148 | } 149 | 150 | public async Task ReadStringUntilAsync(char targetChar, CancellationToken cancellationToken) 151 | { 152 | return (await ReadStringUntilAsync(new[] {targetChar}, cancellationToken)).String; 153 | } 154 | 155 | public async Task ReadLineAsync(CancellationToken cancellationToken) 156 | { 157 | _stringBuilder.Clear(); 158 | char ch; 159 | 160 | while ((ch = await ReadCharAsync(cancellationToken)) != '\n') 161 | { 162 | _stringBuilder.Append(ch); 163 | } 164 | 165 | if (_stringBuilder.Length > 0) 166 | { 167 | int lastCharPos = _stringBuilder.Length - 1; 168 | if (_stringBuilder[lastCharPos] == '\r') 169 | { 170 | _stringBuilder.Remove(lastCharPos, 1); 171 | } 172 | } 173 | 174 | return _stringBuilder.ToString(); 175 | } 176 | 177 | public void Dispose() 178 | { 179 | if(_bufferedStreamOwner) 180 | _stream.Dispose(); 181 | _memoryStream.Dispose(); 182 | } 183 | 184 | public struct ReadStringUntilResult 185 | { 186 | public string String; 187 | public char TargetChar; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /StompNet.Examples/6.ExampleWriterAndReader.cs: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2014 Carlos Campo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *******************************************************************************/ 18 | 19 | using System; 20 | using System.Net.Sockets; 21 | using System.Threading.Tasks; 22 | using StompNet.IO; 23 | using StompNet.Models; 24 | using StompNet.Models.Frames; 25 | 26 | namespace Stomp.Net.Examples 27 | { 28 | partial class Program 29 | { 30 | /// 31 | /// Example to demonstrate STOMP transactions using the StompNet low level API. 32 | /// 33 | /// In this example, a single message is going to be sent to a queue and it will be read from 34 | /// the same queue. 35 | /// 36 | public static async Task ExampleWriterAndReader() 37 | { 38 | using (TcpClient tcpClient = new TcpClient(serverHostname, serverPort)) 39 | 40 | // Create a frame writer and frame reader. These classes are NOT thread safe. 41 | // Stomp12FrameWriter and Stomp12FrameReader are NOT thread safety. They could 42 | // wrapped into a StompSerialFrameWriter and a StompSerialFrameReader for 43 | // serializing operations (thread-safe). 44 | using (IStompFrameWriter writer = new Stomp12FrameWriter(tcpClient.GetStream())) 45 | using (IStompFrameReader reader = new Stomp12FrameReader(tcpClient.GetStream())) 46 | { 47 | Frame inFrame; 48 | 49 | //--------------------------------- 50 | // Write CONNECT. 51 | // 52 | await writer.WriteConnectAsync(virtualHost, login, passcode); 53 | 54 | // Read CONNECTED. 55 | // Keep reading while receiving Heartbeats. 56 | do 57 | { 58 | inFrame = await reader.ReadFrameAsync(); 59 | } while (inFrame.Command == StompCommands.Heartbeat); 60 | 61 | //Verify a CONNECTED frame was received. 62 | if(!AssertExpectedCommandFrame(inFrame, StompCommands.Connected)) 63 | return; 64 | 65 | Console.WriteLine("Connected"); 66 | 67 | 68 | //--------------------------------- 69 | // Write SEND command with receipt. 70 | // 71 | await writer.WriteSendAsync( 72 | aQueueName, 73 | messageContent + (" (WITH RECEIPT)."), 74 | "myreceiptid-123"); 75 | 76 | // Read RECEIPT. 77 | // Keep reading while receiving Heartbeats. 78 | do 79 | { 80 | inFrame = await reader.ReadFrameAsync(); 81 | } while (inFrame.Command == StompCommands.Heartbeat); 82 | 83 | //Verify a RECEIPT frame was received. 84 | if (!AssertExpectedCommandFrame(inFrame, StompCommands.Receipt)) 85 | return; 86 | 87 | // Process incoming RECEIPT. 88 | // Using the incoming frame, Interpret will create a new instance of a sub-class 89 | // of Frame depending on the Command. If the frame is a HEARTBEAT, the same frame 90 | // will be returned. Possible sub-classes: ConnectedFrame, ErrorFrame, MessageFrame, 91 | // ReceiptFrame. 92 | // Interpret throws exception if the incoming frame is malformed (not standard). 93 | ReceiptFrame rptFrame = StompInterpreter.Interpret(inFrame) as ReceiptFrame; 94 | 95 | if (rptFrame.ReceiptId != "myreceiptid-123") 96 | { 97 | Console.WriteLine("ERROR: Unexpected receipt " + rptFrame.ReceiptId + "."); 98 | return; 99 | 100 | } 101 | 102 | Console.WriteLine("Received matching receipt."); 103 | 104 | //--------------------------------- 105 | // Write SUBSCRIBE. 106 | // 107 | string subscriptionId = new Random().Next().ToString(); 108 | await writer.WriteSubscribeAsync(aQueueName, subscriptionId, ack: StompAckValues.AckAutoValue); 109 | 110 | // Read MESSAGE. 111 | // Keep reading while receiving Heartbeats. 112 | do 113 | { 114 | inFrame = await reader.ReadFrameAsync(); 115 | } while (inFrame.Command == StompCommands.Heartbeat); 116 | 117 | //Verify a MESSAGE frame was received. 118 | if(!AssertExpectedCommandFrame(inFrame, StompCommands.Message)) 119 | return; 120 | 121 | // Process incoming RECEIPT. 122 | MessageFrame msgFrame = StompInterpreter.Interpret(inFrame) as MessageFrame; 123 | Console.WriteLine("Received Message:"); 124 | Console.WriteLine(); 125 | Console.WriteLine("Destination: " + msgFrame.Destination); 126 | Console.WriteLine("ContentType: " + msgFrame.ContentType); 127 | Console.WriteLine("ContentLength: " + msgFrame.ContentLength); 128 | Console.WriteLine("Content:" + msgFrame.GetBodyAsString()); 129 | Console.WriteLine(); 130 | 131 | // Write DISCONNECT. 132 | await writer.WriteDisconnectAsync(); 133 | Console.WriteLine("Disconnected."); 134 | } 135 | } 136 | 137 | static bool AssertExpectedCommandFrame(Frame frame, string expectedFrameCommand) 138 | { 139 | // If error 140 | if (frame.Command == StompCommands.Error) 141 | { 142 | // Using the incoming frame, Interpret will create a new instance of a sub-class 143 | // of Frame depending on the Command. If the frame is a HEARTBEAT, the same frame 144 | // will be returned. Possible sub-classes: ConnectedFrame, ErrorFrame, MessageFrame, 145 | // ReceiptFrame. 146 | // Interpret throws exception if the incoming frame is malformed (not standard). 147 | ErrorFrame errFrame = StompInterpreter.Interpret(frame) as ErrorFrame; 148 | 149 | Console.WriteLine("ERROR RESPONSE"); 150 | Console.WriteLine("Message : " + errFrame.Message); 151 | 152 | return false; 153 | } 154 | 155 | if (frame.Command != expectedFrameCommand) 156 | { 157 | Console.WriteLine("UNEXPECTED FRAME."); 158 | Console.WriteLine(frame.ToString()); 159 | 160 | return false; 161 | } 162 | 163 | return true; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 44 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 45 | 46 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 47 | 48 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 49 | 50 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 51 | 52 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 53 | 54 | END OF TERMS AND CONDITIONS --------------------------------------------------------------------------------