├── .gitignore ├── EventSource4Net.Test ├── EventSource4Net.Test.csproj ├── EventSourceTest.cs ├── MessagesTest.cs ├── Properties │ └── AssemblyInfo.cs ├── TestExtensions.cs ├── TestableEventSource.cs └── WebRequesterFactoryTest.cs ├── EventSource4Net.sln ├── EventSource4Net ├── ConnectedState.cs ├── ConnectingState.cs ├── DisconnectedState.cs ├── EventSource.cs ├── EventSource4Net.csproj ├── EventSource4Net.nuspec ├── EventSourceState.cs ├── IConnectionState.cs ├── IServerResponse.cs ├── IWebRequester.cs ├── IWebRequesterFactory.cs ├── Properties │ └── AssemblyInfo.cs ├── ServerResponse.cs ├── ServerSentEvent.cs ├── ServerSentEventReceivedEventArgs.cs ├── StateChangedEventArgs.cs ├── StringSplitter.cs ├── WatchDog.cs ├── WebRequester.cs ├── WebRequesterFactory.cs └── packages.config ├── LICENSE ├── README.md └── Sample ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── Sample.csproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /EventSource4Net.Test/EventSource4Net.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {9D54469C-274C-4856-97C4-D621CFA81050} 7 | Library 8 | Properties 9 | EventSource4Net.Test 10 | EventSource4Net.Test 11 | v4.5 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 | DEBUG;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 | 61 | 62 | {df919631-8c28-49e6-be61-23787af2a90d} 63 | EventSource4Net 64 | 65 | 66 | 67 | 68 | 69 | 70 | False 71 | 72 | 73 | False 74 | 75 | 76 | False 77 | 78 | 79 | False 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /EventSource4Net.Test/EventSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | 6 | namespace EventSource4Net.Test 7 | { 8 | [TestClass] 9 | public class EventSourceTest 10 | { 11 | [TestMethod] 12 | public void TestFailedConnection() 13 | { 14 | // setup 15 | Uri url = new Uri("http://test.com"); 16 | CancellationTokenSource cts = new CancellationTokenSource(); 17 | List states = new List(); 18 | ServiceResponseMock response = new ServiceResponseMock(url, System.Net.HttpStatusCode.NotFound); 19 | WebRequesterFactoryMock factory = new WebRequesterFactoryMock(response); 20 | ManualResetEvent stateIsClosed = new ManualResetEvent(false); 21 | 22 | TestableEventSource es = new TestableEventSource(url, factory); 23 | es.StateChanged += (o, e) => 24 | { 25 | states.Add(e.State); 26 | if (e.State == EventSourceState.CLOSED) 27 | { 28 | stateIsClosed.Set(); 29 | cts.Cancel(); 30 | } 31 | }; 32 | 33 | 34 | // act 35 | stateIsClosed.Reset(); 36 | 37 | es.Start(cts.Token); 38 | 39 | stateIsClosed.WaitOne(); 40 | 41 | // assert 42 | Assert.IsTrue(states.Count == 2); 43 | Assert.AreEqual(states[0], EventSourceState.CONNECTING); 44 | Assert.AreEqual(states[1], EventSourceState.CLOSED); 45 | } 46 | 47 | [TestMethod] 48 | public void TestSuccesfulConnection() 49 | { 50 | // setup 51 | Uri url = new Uri("http://test.com"); 52 | CancellationTokenSource cts = new CancellationTokenSource(); 53 | List states = new List(); 54 | ServiceResponseMock response = new ServiceResponseMock(url, System.Net.HttpStatusCode.OK); 55 | WebRequesterFactoryMock factory = new WebRequesterFactoryMock(response); 56 | ManualResetEvent stateIsOpen = new ManualResetEvent(false); 57 | 58 | TestableEventSource es = new TestableEventSource(url, factory); 59 | es.StateChanged += (o, e) => 60 | { 61 | states.Add(e.State); 62 | if (e.State == EventSourceState.OPEN) 63 | { 64 | stateIsOpen.Set(); 65 | cts.Cancel(); 66 | } 67 | }; 68 | 69 | 70 | // act 71 | stateIsOpen.Reset(); 72 | 73 | es.Start(cts.Token); 74 | 75 | stateIsOpen.WaitOne(); 76 | 77 | // assert 78 | Assert.IsTrue(states.Count == 2); 79 | Assert.AreEqual(states[0], EventSourceState.CONNECTING); 80 | Assert.AreEqual(states[1], EventSourceState.OPEN); 81 | } 82 | 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /EventSource4Net.Test/MessagesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Threading; 4 | using System.Collections.Generic; 5 | 6 | namespace EventSource4Net.Test 7 | { 8 | [TestClass] 9 | public class MessagesTest 10 | { 11 | Uri url = new Uri("http://test.com"); 12 | CancellationTokenSource cts; 13 | List states; 14 | ServiceResponseMock response; 15 | WebRequesterFactoryMock factory; 16 | ManualResetEvent stateIsOpen; 17 | List receivedMessages; 18 | ManualResetEvent eventReceived; 19 | 20 | private TestableEventSource SetupAndConnect() 21 | { 22 | // setup 23 | cts = new CancellationTokenSource(); 24 | states = new List(); 25 | response = new ServiceResponseMock(url, System.Net.HttpStatusCode.OK); 26 | factory = new WebRequesterFactoryMock(response); 27 | stateIsOpen = new ManualResetEvent(false); 28 | receivedMessages = new List(); 29 | eventReceived = new ManualResetEvent(false); 30 | 31 | TestableEventSource es = new TestableEventSource(url, factory); 32 | es.StateChanged += (o, e) => 33 | { 34 | states.Add(e.State); 35 | if (e.State == EventSourceState.OPEN) 36 | stateIsOpen.Set(); 37 | }; 38 | es.EventReceived += (o, e) => 39 | { 40 | receivedMessages.Add(e.Message); 41 | eventReceived.Set(); 42 | }; 43 | 44 | return es; 45 | } 46 | 47 | [TestMethod] 48 | public void TestDoubleLineFeed() 49 | { 50 | // setup 51 | TestableEventSource es = SetupAndConnect(); 52 | 53 | // act 54 | es.Start(cts.Token); 55 | stateIsOpen.WaitOrThrow(); 56 | response.WriteTestTextToStream("event: test\n\n"); 57 | eventReceived.WaitOrThrow(); 58 | 59 | // assert 60 | Assert.AreEqual(receivedMessages.Count, 1); 61 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 62 | } 63 | [TestMethod] 64 | public void TestDoubleCarriageReturn() 65 | { 66 | // setup 67 | TestableEventSource es = SetupAndConnect(); 68 | 69 | // act 70 | es.Start(cts.Token); 71 | stateIsOpen.WaitOrThrow(); 72 | response.WriteTestTextToStream("event: test\r\r"); 73 | eventReceived.WaitOrThrow(); 74 | 75 | // assert 76 | Assert.AreEqual(receivedMessages.Count, 1); 77 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 78 | } 79 | [TestMethod] 80 | public void TestDoubleCarriageReturnLineFeedPair() 81 | { 82 | // setup 83 | TestableEventSource es = SetupAndConnect(); 84 | 85 | // act 86 | es.Start(cts.Token); 87 | stateIsOpen.WaitOrThrow(); 88 | response.WriteTestTextToStream("event: test\r\n\r\n"); 89 | eventReceived.WaitOrThrow(); 90 | 91 | // assert 92 | Assert.AreEqual(receivedMessages.Count, 1); 93 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 94 | } 95 | [TestMethod] 96 | public void TestTwoLines() 97 | { 98 | // setup 99 | TestableEventSource es = SetupAndConnect(); 100 | 101 | // act 102 | es.Start(cts.Token); 103 | stateIsOpen.WaitOrThrow(); 104 | response.WriteTestTextToStream("event: test\ndata: simple\n\n"); 105 | eventReceived.WaitOrThrow(); 106 | 107 | // assert 108 | Assert.AreEqual(receivedMessages.Count, 1); 109 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 110 | Assert.AreEqual(receivedMessages[0].Data, "simple\n"); 111 | } 112 | [TestMethod] 113 | public void TestMixedSeparators() 114 | { 115 | // setup 116 | TestableEventSource es = SetupAndConnect(); 117 | 118 | // act 119 | es.Start(cts.Token); 120 | stateIsOpen.WaitOrThrow(); 121 | response.WriteTestTextToStream("event: test\rdata: simple\n\n"); 122 | eventReceived.WaitOrThrow(); 123 | 124 | // assert 125 | Assert.AreEqual(receivedMessages.Count, 1); 126 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 127 | Assert.AreEqual(receivedMessages[0].Data, "simple\n"); 128 | } 129 | [TestMethod] 130 | public void TestDataSentInParts() 131 | { 132 | // setup 133 | TestableEventSource es = SetupAndConnect(); 134 | 135 | // act 136 | es.Start(cts.Token); 137 | stateIsOpen.WaitOrThrow(); 138 | response.WriteTestTextToStream("event: tes"); 139 | Thread.Sleep(10); 140 | response.WriteTestTextToStream("t\ndata: simple\n\n"); 141 | eventReceived.WaitOrThrow(); 142 | 143 | // assert 144 | Assert.AreEqual(receivedMessages.Count, 1); 145 | Assert.AreEqual(receivedMessages[0].EventType, "test"); 146 | Assert.AreEqual(receivedMessages[0].Data, "simple\n"); 147 | } 148 | [TestMethod] 149 | public void TestMultipleEvents() 150 | { 151 | // setup 152 | TestableEventSource es = SetupAndConnect(); 153 | 154 | // act 155 | es.Start(cts.Token); 156 | stateIsOpen.WaitOrThrow(); 157 | 158 | //entire event in one line 159 | response.WriteTestTextToStream("id: 1" + "\n" + "event: newevent" + "\n" + "data: Hello" + "\n\n"); 160 | eventReceived.WaitOrThrow(); 161 | eventReceived.Reset(); 162 | 163 | //This event is split up over multiple res.write lines 164 | response.WriteTestTextToStream("id:2 " + "\n"); 165 | response.WriteTestTextToStream("event: event 3" + "\n"); 166 | response.WriteTestTextToStream("data: Hello again"); 167 | response.WriteTestTextToStream("\n\n"); 168 | eventReceived.WaitOrThrow(); 169 | eventReceived.Reset(); 170 | 171 | //Again an event that is split up over multiple res.write statements 172 | response.WriteTestTextToStream("id: 3" + "\n"); 173 | response.WriteTestTextToStream("event: event3" + "\n" + "data: Goodbye" + "\n\n"); 174 | eventReceived.WaitOrThrow(); 175 | eventReceived.Reset(); 176 | 177 | // assert 178 | Assert.AreEqual(receivedMessages.Count, 3); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /EventSource4Net.Test/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("EventSource4Net.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("EMEA")] 12 | [assembly: AssemblyProduct("EventSource4Net.Test")] 13 | [assembly: AssemblyCopyright("Copyright © EMEA 2013")] 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("4b9aa617-e499-4034-a237-2db14da92e88")] 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 | -------------------------------------------------------------------------------- /EventSource4Net.Test/TestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net.Test 9 | { 10 | static class TestExtensions 11 | { 12 | public static void WaitOrThrow(this ManualResetEvent mre) 13 | { 14 | #if DEBUG 15 | mre.WaitOne(); 16 | #else 17 | if (!mre.WaitOne(1000)) 18 | throw new TimeoutException("Timeout waiting for manualresetevent"); 19 | #endif 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EventSource4Net.Test/TestableEventSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace EventSource4Net.Test 8 | { 9 | class TestableEventSource : EventSource 10 | { 11 | public TestableEventSource(Uri url,IWebRequesterFactory factory) : base(url,factory) 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EventSource4Net.Test/WebRequesterFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace EventSource4Net.Test 11 | { 12 | class WebRequesterFactoryMock : IWebRequesterFactory 13 | { 14 | public WebRequesterMock WebRequesterMock 15 | { 16 | get; 17 | private set; 18 | } 19 | public WebRequesterFactoryMock(ServiceResponseMock response) 20 | { 21 | this.WebRequesterMock = new WebRequesterMock(response); 22 | } 23 | public IWebRequester Create() 24 | { 25 | return WebRequesterMock; 26 | } 27 | } 28 | 29 | class WebRequesterMock : IWebRequester 30 | { 31 | public ManualResetEvent GetCalled = new ManualResetEvent(false); 32 | public ServiceResponseMock Response { get; private set; } 33 | 34 | public WebRequesterMock(ServiceResponseMock response) 35 | { 36 | this.Response = response; 37 | } 38 | 39 | public System.Threading.Tasks.Task Get(Uri url) 40 | { 41 | return Task.Factory.StartNew(() => 42 | { 43 | GetCalled.Set(); 44 | return Response; 45 | }); 46 | } 47 | } 48 | 49 | class ServiceResponseMock : IServerResponse 50 | { 51 | private Stream mStream; 52 | private StreamWriter mStreamWriter; 53 | private Uri mUrl; 54 | private HttpStatusCode mStatusCode; 55 | 56 | public ManualResetEvent StatusCodeCalled = new ManualResetEvent(false); 57 | 58 | public ServiceResponseMock(Uri url, HttpStatusCode statusCode) 59 | { 60 | mUrl = url; 61 | mStatusCode = statusCode; 62 | mStream = new TestableStream(); 63 | mStreamWriter = new StreamWriter(mStream); 64 | } 65 | 66 | public System.Net.HttpStatusCode StatusCode 67 | { 68 | get 69 | { 70 | StatusCodeCalled.Set(); 71 | return mStatusCode; 72 | } 73 | } 74 | 75 | public System.IO.Stream GetResponseStream() 76 | { 77 | return mStream; 78 | } 79 | 80 | public Uri ResponseUri 81 | { 82 | get { return mUrl; } 83 | } 84 | 85 | public void WriteTestTextToStream(string text) 86 | { 87 | mStreamWriter.Write(text); 88 | mStreamWriter.Flush(); 89 | } 90 | } 91 | 92 | class GetIsCalledEventArgs : EventArgs 93 | { 94 | public ServiceResponseMock ServerResponse { get; private set; } 95 | public GetIsCalledEventArgs(ServiceResponseMock response) 96 | { 97 | ServerResponse = response; 98 | } 99 | } 100 | 101 | 102 | class TestableStream : Stream 103 | { 104 | long _pos = 0; 105 | System.Collections.Concurrent.BlockingCollection _texts = new System.Collections.Concurrent.BlockingCollection(); 106 | 107 | public override bool CanRead 108 | { 109 | get { return true; } 110 | } 111 | 112 | public override bool CanSeek 113 | { 114 | get { return true; } 115 | } 116 | 117 | public override bool CanWrite 118 | { 119 | get { return true; } 120 | } 121 | 122 | public override void Flush() 123 | { 124 | 125 | } 126 | 127 | public override long Length 128 | { 129 | get { return _texts.Count(); } 130 | } 131 | 132 | public override long Position 133 | { 134 | get 135 | { 136 | return _pos; 137 | } 138 | set 139 | { 140 | _pos = value; 141 | } 142 | } 143 | 144 | public override int Read(byte[] buffer, int offset, int count) 145 | { 146 | string s = _texts.Take(); 147 | 148 | byte[] encodedText = Encoding.UTF8.GetBytes(s); 149 | encodedText.CopyTo(buffer, offset); 150 | return encodedText.Length; 151 | } 152 | 153 | public override long Seek(long offset, SeekOrigin origin) 154 | { 155 | throw new NotImplementedException(); 156 | } 157 | 158 | public override void SetLength(long value) 159 | { 160 | throw new NotImplementedException(); 161 | } 162 | 163 | public override void Write(byte[] buffer, int offset, int count) 164 | { 165 | string s = Encoding.UTF8.GetString(buffer, offset, count); 166 | _texts.Add(s); 167 | //_texts.CompleteAdding(); 168 | } 169 | } 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /EventSource4Net.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventSource4Net", "EventSource4Net\EventSource4Net.csproj", "{DF919631-8C28-49E6-BE61-23787AF2A90D}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2D2EF32-A2A6-467E-B606-82514A6FB89B}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventSource4Net.Test", "EventSource4Net.Test\EventSource4Net.Test.csproj", "{9D54469C-274C-4856-97C4-D621CFA81050}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {DF919631-8C28-49E6-BE61-23787AF2A90D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {DF919631-8C28-49E6-BE61-23787AF2A90D}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {DF919631-8C28-49E6-BE61-23787AF2A90D}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {DF919631-8C28-49E6-BE61-23787AF2A90D}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {9D54469C-274C-4856-97C4-D621CFA81050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {9D54469C-274C-4856-97C4-D621CFA81050}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {9D54469C-274C-4856-97C4-D621CFA81050}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {9D54469C-274C-4856-97C4-D621CFA81050}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /EventSource4Net/ConnectedState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using System.Threading; 9 | 10 | namespace EventSource4Net 11 | { 12 | class ConnectedState : IConnectionState 13 | { 14 | private static readonly slf4net.ILogger _logger = slf4net.LoggerFactory.GetLogger(typeof(ConnectedState)); 15 | 16 | private IWebRequesterFactory mWebRequesterFactory; 17 | private ServerSentEvent mSse = null; 18 | private string mRemainingText = string.Empty; // the text that is not ended with a lineending char is saved for next call. 19 | private IServerResponse mResponse; 20 | public EventSourceState State { get { return EventSourceState.OPEN; } } 21 | 22 | public ConnectedState(IServerResponse response, IWebRequesterFactory webRequesterFactory) 23 | { 24 | mResponse = response; 25 | mWebRequesterFactory = webRequesterFactory; 26 | } 27 | 28 | public Task Run(Action msgReceived, CancellationToken cancelToken) 29 | { 30 | int i = 0; 31 | 32 | Task t = new Task(() => 33 | { 34 | //using (mResponse) 35 | { 36 | //using (var stream = mResponse.GetResponseStream()) 37 | var stream = mResponse.GetResponseStream(); 38 | { 39 | byte[] buffer = new byte[1024 * 8]; 40 | var taskRead = stream.ReadAsync(buffer, 0, buffer.Length, cancelToken); 41 | 42 | try 43 | { 44 | taskRead.Wait(cancelToken); 45 | } 46 | catch (Exception ex) 47 | { 48 | _logger.Trace(ex, "ConnectedState.Run"); 49 | } 50 | if (!cancelToken.IsCancellationRequested) 51 | { 52 | int bytesRead = taskRead.Result; 53 | if (bytesRead > 0) // stream has not reached the end yet 54 | { 55 | //Console.WriteLine("ReadCallback {0} bytesRead", bytesRead); 56 | string text = Encoding.UTF8.GetString(buffer, 0, bytesRead); 57 | text = mRemainingText + text; 58 | string[] lines = StringSplitter.SplitIntoLines(text, out mRemainingText); 59 | foreach (string line in lines) 60 | { 61 | if (cancelToken.IsCancellationRequested) break; 62 | 63 | // Dispatch message if empty lne 64 | if (string.IsNullOrEmpty(line.Trim()) && mSse != null) 65 | { 66 | _logger.Trace("Message received"); 67 | msgReceived(mSse); 68 | mSse = null; 69 | } 70 | else if (line.StartsWith(":")) 71 | { 72 | // This a comment, just log it. 73 | _logger.Trace("A comment was received: " + line); 74 | } 75 | else 76 | { 77 | string fieldName = String.Empty; 78 | string fieldValue = String.Empty; 79 | if (line.Contains(':')) 80 | { 81 | int index = line.IndexOf(':'); 82 | fieldName = line.Substring(0, index); 83 | fieldValue = line.Substring(index + 1).TrimStart(); 84 | } 85 | else 86 | fieldName = line; 87 | 88 | if (String.Compare(fieldName, "event", true) == 0) 89 | { 90 | mSse = mSse ?? new ServerSentEvent(); 91 | mSse.EventType = fieldValue; 92 | } 93 | else if (String.Compare(fieldName, "data", true) == 0) 94 | { 95 | mSse = mSse ?? new ServerSentEvent(); 96 | mSse.Data = fieldValue + '\n'; 97 | } 98 | else if (String.Compare(fieldName, "id", true) == 0) 99 | { 100 | mSse = mSse ?? new ServerSentEvent(); 101 | mSse.LastEventId = fieldValue; 102 | } 103 | else if (String.Compare(fieldName, "retry", true) == 0) 104 | { 105 | int parsedRetry; 106 | if (int.TryParse(fieldValue, out parsedRetry)) 107 | { 108 | mSse = mSse ?? new ServerSentEvent(); 109 | mSse.Retry = parsedRetry; 110 | } 111 | } 112 | else 113 | { 114 | // Ignore this, just log it 115 | _logger.Warn("A unknown line was received: " + line); 116 | } 117 | } 118 | } 119 | 120 | if (!cancelToken.IsCancellationRequested) 121 | return this; 122 | } 123 | else // end of the stream reached 124 | { 125 | _logger.Trace("No bytes read. End of stream."); 126 | } 127 | } 128 | 129 | //stream.Dispose() 130 | //stream.Close(); 131 | //mResponse.Close(); 132 | //mResponse.Dispose(); 133 | return new DisconnectedState(mResponse.ResponseUri, mWebRequesterFactory); 134 | } 135 | } 136 | }); 137 | 138 | t.Start(); 139 | return t; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /EventSource4Net/ConnectingState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Net; 7 | using System.IO; 8 | using System.Threading; 9 | 10 | namespace EventSource4Net 11 | { 12 | class ConnectingState : IConnectionState 13 | { 14 | private static readonly slf4net.ILogger _logger = slf4net.LoggerFactory.GetLogger(typeof(ConnectingState)); 15 | 16 | private Uri mUrl; 17 | private IWebRequesterFactory mWebRequesterFactory; 18 | public EventSourceState State { get { return EventSourceState.CONNECTING; } } 19 | 20 | public ConnectingState(Uri url, IWebRequesterFactory webRequesterFactory) 21 | { 22 | if (url == null) throw new ArgumentNullException("Url cant be null"); 23 | if (webRequesterFactory == null) throw new ArgumentNullException("Factory cant be null"); 24 | mUrl = url; 25 | mWebRequesterFactory = webRequesterFactory; 26 | } 27 | 28 | public Task Run(Action donothing, CancellationToken cancelToken) 29 | { 30 | IWebRequester requester = mWebRequesterFactory.Create(); 31 | var taskResp = requester.Get(mUrl); 32 | 33 | return taskResp.ContinueWith(tsk => 34 | { 35 | if (tsk.Status == TaskStatus.RanToCompletion && !cancelToken.IsCancellationRequested) 36 | { 37 | IServerResponse response = tsk.Result; 38 | if (response.StatusCode == HttpStatusCode.OK) 39 | { 40 | return new ConnectedState(response, mWebRequesterFactory); 41 | } 42 | else 43 | { 44 | _logger.Info("Failed to connect to: " + mUrl.ToString() + response ?? (" Http statuscode: " + response.StatusCode)); 45 | } 46 | } 47 | 48 | return new DisconnectedState(mUrl, mWebRequesterFactory); 49 | }); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /EventSource4Net/DisconnectedState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net 9 | { 10 | class DisconnectedState : IConnectionState 11 | { 12 | private Uri mUrl; 13 | private IWebRequesterFactory mWebRequesterFactory; 14 | public EventSourceState State 15 | { 16 | get { return EventSourceState.CLOSED; } 17 | } 18 | 19 | public DisconnectedState(Uri url, IWebRequesterFactory webRequesterFactory) 20 | { 21 | if (url == null) throw new ArgumentNullException("Url cant be null"); 22 | mUrl = url; 23 | mWebRequesterFactory = webRequesterFactory; 24 | } 25 | 26 | public Task Run(Action donothing, CancellationToken cancelToken) 27 | { 28 | if(cancelToken.IsCancellationRequested) 29 | return Task.Factory.StartNew(() => { return new DisconnectedState(mUrl, mWebRequesterFactory); }); 30 | else 31 | return Task.Factory.StartNew(() => { return new ConnectingState(mUrl, mWebRequesterFactory); }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EventSource4Net/EventSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using System.Threading; 9 | 10 | namespace EventSource4Net 11 | { 12 | public class EventSource 13 | { 14 | private static readonly slf4net.ILogger _logger = slf4net.LoggerFactory.GetLogger(typeof(EventSource)); 15 | 16 | public event EventHandler StateChanged; 17 | public event EventHandler EventReceived; 18 | 19 | private IWebRequesterFactory _webRequesterFactory = new WebRequesterFactory(); 20 | private int _timeout = 0; 21 | public Uri Url { get; private set; } 22 | public EventSourceState State { get { return CurrentState.State; } } 23 | public string LastEventId { get; private set; } 24 | private IConnectionState mCurrentState = null; 25 | private CancellationToken mStopToken; 26 | private CancellationTokenSource mTokenSource = new CancellationTokenSource(); 27 | private IConnectionState CurrentState 28 | { 29 | get { return mCurrentState; } 30 | set 31 | { 32 | if (!value.Equals(mCurrentState)) 33 | { 34 | StringBuilder sb = new StringBuilder("State changed from "); 35 | sb.Append(mCurrentState == null ? "Unknown" : mCurrentState.State.ToString()); 36 | sb.Append(" to "); 37 | sb.Append(value == null ? "Unknown" : value.State.ToString()); 38 | _logger.Trace(sb.ToString()); 39 | mCurrentState = value; 40 | OnStateChanged(mCurrentState.State); 41 | } 42 | } 43 | } 44 | 45 | public EventSource(Uri url, int timeout) 46 | { 47 | Initialize(url, timeout); 48 | } 49 | 50 | /// 51 | /// Constructor for testing purposes 52 | /// 53 | /// The factory that generates the WebRequester to use. 54 | protected EventSource(Uri url, IWebRequesterFactory factory) 55 | { 56 | _webRequesterFactory = factory; 57 | Initialize(url, 0); 58 | } 59 | 60 | private void Initialize(Uri url, int timeout) 61 | { 62 | _timeout = timeout; 63 | Url = url; 64 | CurrentState = new DisconnectedState(Url,_webRequesterFactory); 65 | _logger.Info("EventSource created for " + url.ToString()); 66 | } 67 | 68 | 69 | /// 70 | /// Start the EventSource. 71 | /// 72 | /// Cancel this token to stop the EventSource. 73 | public void Start(CancellationToken stopToken) 74 | { 75 | if (State == EventSourceState.CLOSED) 76 | { 77 | mStopToken = stopToken; 78 | mTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stopToken); 79 | Run(); 80 | } 81 | } 82 | 83 | protected void Run() 84 | { 85 | if (mTokenSource.IsCancellationRequested && CurrentState.State == EventSourceState.CLOSED) 86 | return; 87 | 88 | mCurrentState.Run(this.OnEventReceived, mTokenSource.Token).ContinueWith(cs => 89 | { 90 | CurrentState = cs.Result; 91 | Run(); 92 | }); 93 | } 94 | 95 | protected void OnEventReceived(ServerSentEvent sse) 96 | { 97 | if (EventReceived != null) 98 | { 99 | EventReceived(this, new ServerSentEventReceivedEventArgs(sse)); 100 | } 101 | } 102 | 103 | protected void OnStateChanged(EventSourceState newState) 104 | { 105 | if (StateChanged != null) 106 | { 107 | StateChanged(this, new StateChangedEventArgs(newState)); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /EventSource4Net/EventSource4Net.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DF919631-8C28-49E6-BE61-23787AF2A90D} 8 | Library 9 | Properties 10 | EventSource4Net 11 | EventSource4Net 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 | ..\packages\slf4net.0.1.32.1\lib\net35\slf4net.dll 35 | 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 | 61 | 62 | 63 | 64 | 65 | 66 | Designer 67 | 68 | 69 | 70 | 71 | 72 | if "$(ConfigurationName)" == "Release" ( 73 | 74 | NuGet.exe pack "$(ProjectDir)EventSource4Net.csproj" -Prop Configuration=Release 75 | 76 | ) 77 | 78 | 85 | -------------------------------------------------------------------------------- /EventSource4Net/EventSource4Net.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Erik Zetterqvist 8 | $author$ 9 | https://github.com/erizet/EventSource4Net 10 | false 11 | Eventsource for .net. Makes it simple to receive Server-Sent Events (SSE) in managed applications. 12 | Fixed bugs in handling of received data. 13 | Copyright 2012 14 | Eventsource Server-Sent Events SSE 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /EventSource4Net/EventSourceState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventSource4Net 7 | { 8 | public enum EventSourceState 9 | { 10 | CONNECTING, 11 | OPEN, 12 | CLOSED 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /EventSource4Net/IConnectionState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net 9 | { 10 | interface IConnectionState 11 | { 12 | EventSourceState State { get; } 13 | Task Run(Action MsgReceivedCallback, CancellationToken cancelToken); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EventSource4Net/IServerResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace EventSource4Net 8 | { 9 | public interface IServerResponse 10 | { 11 | HttpStatusCode StatusCode { get; } 12 | 13 | System.IO.Stream GetResponseStream(); 14 | 15 | Uri ResponseUri { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EventSource4Net/IWebRequester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net 9 | { 10 | public interface IWebRequester 11 | { 12 | Task Get(Uri url); 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EventSource4Net/IWebRequesterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventSource4Net 7 | { 8 | public interface IWebRequesterFactory 9 | { 10 | IWebRequester Create(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /EventSource4Net/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("EventSource4Net")] 9 | [assembly: AssemblyDescription("EventSource for .net")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("ZSoft")] 12 | [assembly: AssemblyProduct("EventSource4Net")] 13 | [assembly: AssemblyCopyright("Copyright © Zsoft 2012")] 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("14dce408-3cca-40a9-b63c-8243103a0774")] 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("0.4.0.0")] 36 | [assembly: AssemblyFileVersion("0.4.0.0")] 37 | -------------------------------------------------------------------------------- /EventSource4Net/ServerResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace EventSource4Net 8 | { 9 | class ServerResponse : IServerResponse 10 | { 11 | private System.Net.HttpWebResponse mHttpResponse; 12 | 13 | public ServerResponse(System.Net.WebResponse webResponse) 14 | { 15 | this.mHttpResponse = webResponse as HttpWebResponse; 16 | } 17 | 18 | public HttpStatusCode StatusCode 19 | { 20 | get 21 | { 22 | return mHttpResponse.StatusCode; 23 | } 24 | } 25 | 26 | public System.IO.Stream GetResponseStream() 27 | { 28 | return mHttpResponse.GetResponseStream(); 29 | } 30 | 31 | public Uri ResponseUri 32 | { 33 | get 34 | { 35 | return mHttpResponse.ResponseUri; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /EventSource4Net/ServerSentEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventSource4Net 7 | { 8 | public class ServerSentEvent 9 | { 10 | public string LastEventId { get; set; } 11 | public string EventType { get; set; } 12 | public string Data { get; set; } 13 | public int? Retry { get; set; } 14 | 15 | public override string ToString() 16 | { 17 | StringBuilder sb = new StringBuilder(); 18 | sb.Append("EventType: ").Append(EventType).AppendLine(); 19 | sb.Append("Data: ").Append(Data).AppendLine(); 20 | sb.Append("LastEventId: ").Append(LastEventId).AppendLine(); 21 | if(Retry.HasValue) 22 | sb.Append("Retry: ").Append(Retry.Value).AppendLine(); 23 | return sb.ToString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /EventSource4Net/ServerSentEventReceivedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventSource4Net 7 | { 8 | public class ServerSentEventReceivedEventArgs : EventArgs 9 | { 10 | public ServerSentEvent Message { get; private set; } 11 | public ServerSentEventReceivedEventArgs(ServerSentEvent message) 12 | { 13 | Message = message; 14 | } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EventSource4Net/StateChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace EventSource4Net 7 | { 8 | public class StateChangedEventArgs : EventArgs 9 | { 10 | public EventSourceState State { get; private set; } 11 | public StateChangedEventArgs(EventSourceState state) 12 | { 13 | State = state; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EventSource4Net/StringSplitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace EventSource4Net 8 | { 9 | internal class StringSplitter 10 | { 11 | public static string[] SplitIntoLines(string text, out string remainingText) 12 | { 13 | List lines = new List(); 14 | 15 | //bool endFound = false; 16 | //bool searchingForFirstChar = true; 17 | int lineLength = 0; 18 | char previous = char.MinValue; 19 | for (int i = 0; i < text.Length; i++) 20 | { 21 | char c = text[i]; 22 | if (c == '\n' || c == '\r') 23 | { 24 | bool isCRLFPair = previous=='\r' && c == '\n'; 25 | 26 | if (!isCRLFPair) 27 | { 28 | string line = text.Substring(i - lineLength, lineLength); 29 | lines.Add(line); 30 | } 31 | 32 | lineLength = 0; 33 | } 34 | else 35 | { 36 | lineLength++; 37 | } 38 | previous = c; 39 | } 40 | 41 | // Save the last chars that is not followed by a lineending. 42 | remainingText = text.Substring(text.Length - lineLength); 43 | 44 | return lines.ToArray(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /EventSource4Net/WatchDog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net 9 | { 10 | internal class Watchdog 11 | { 12 | private long _timeout; 13 | private Timer _timer; 14 | public event EventHandler TimerExpired; 15 | 16 | public void Start() 17 | { 18 | _timer = new Timer(new TimerCallback(OnTimerExpired), null, 0, _timeout); 19 | } 20 | 21 | public void Reset() 22 | { 23 | _timer.Change(0, _timeout); 24 | } 25 | 26 | private void OnTimerExpired(object State) 27 | { 28 | _timer.Change(Timeout.Infinite, Timeout.Infinite); 29 | if (TimerExpired != null) 30 | TimerExpired(this, new EventArgs()); 31 | } 32 | 33 | public Watchdog(long timeout) 34 | { 35 | if (timeout < 1) throw new ArgumentOutOfRangeException("timeout", "timeout muste be greater than zero."); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /EventSource4Net/WebRequester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace EventSource4Net 9 | { 10 | class WebRequester : IWebRequester 11 | { 12 | public Task Get(Uri url) 13 | { 14 | var wreq = (HttpWebRequest)WebRequest.Create(url); 15 | wreq.Method = "GET"; 16 | wreq.Proxy = null; 17 | 18 | var taskResp = Task.Factory.FromAsync(wreq.BeginGetResponse, 19 | wreq.EndGetResponse, 20 | null).ContinueWith(t => new ServerResponse(t.Result)); 21 | return taskResp; 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /EventSource4Net/WebRequesterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace EventSource4Net 8 | { 9 | class WebRequesterFactory : IWebRequesterFactory 10 | { 11 | public IWebRequester Create() 12 | { 13 | return new WebRequester(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EventSource4Net/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [nuget]: https://nuget.org/packages/EventSource4Net 2 | [slf4net]: https://github.com/englishtown/slf4net 3 | EventSource4Net 4 | =============== 5 | 6 | EventSource4Net is a eventsource implementation for .Net. By using EventSource4Net can you receive Server-Sent Event(SSE) in your native .Net program. 7 | 8 | ##Install## 9 | EventSource4Net is available as a [Nuget-package][nuget]. From the Package Manager Console enter: 10 | 11 | Install-package EventSource4Net 12 | 13 | ##How to use?## 14 | 15 | It's dead-simple to use. 16 | 17 | EventSource es = new EventSource(new Uri()); 18 | es.StateChanged += new EventHandler((o, e) => { Console.WriteLine("New state: " + e.State.ToString()); }); 19 | es.EventReceived += new EventHandler((o, e) => { Console.WriteLine("--------- Msg received -----------\n" + e.Message.ToString()); }); 20 | es.Start(); 21 | 22 | See the sample-project! 23 | 24 | ##Logging## 25 | EventSource4Net uses [slf4net] as a logging facade. 26 | 27 | ##ToDo## 28 | - Implement functionallity to cancel the eventsource. 29 | 30 | ##Contributions## 31 | I'll be more than happy to get contributions!!! 32 | -------------------------------------------------------------------------------- /Sample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using EventSource4Net; 7 | using slf4net; 8 | using slf4net.Resolvers; 9 | using System.Threading; 10 | 11 | 12 | namespace Sample 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | CancellationTokenSource cts = new CancellationTokenSource(); 19 | InitLogging(); 20 | 21 | EventSource es = new EventSource(new Uri(@"http://ssetest.apphb.com/api/sse"), 50000); 22 | es.StateChanged += new EventHandler((o, e) => { Console.WriteLine("New state: " + e.State.ToString()); }); 23 | es.EventReceived += new EventHandler((o, e) => { Console.WriteLine("--------- Msg received -----------\n" + e.Message.ToString()); }); 24 | es.Start(cts.Token); 25 | Console.WriteLine("EventSource started"); 26 | 27 | ConsoleKey key; 28 | while ((key = Console.ReadKey().Key) != ConsoleKey.X) 29 | { 30 | if (key == ConsoleKey.C) 31 | { 32 | cts.Cancel(); 33 | Console.WriteLine("Eventsource is cancelled."); 34 | } 35 | else if (key==ConsoleKey.S) 36 | { 37 | cts = new CancellationTokenSource(); 38 | es.Start(cts.Token); 39 | } 40 | } 41 | } 42 | 43 | private static void InitLogging() 44 | { 45 | // Create log4net ILoggerFactory and set the resolver 46 | var factory = new slf4net.Factories.SimpleLoggerFactory(new TraceLogger("Test")); 47 | var resolver = new SimpleFactoryResolver(factory); 48 | LoggerFactory.SetFactoryResolver(resolver); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sample/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("Sample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("Sample")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2012")] 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("707789a0-56fa-49da-ab3c-c6ea412b3b4a")] 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 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4A59424A-EA6A-49B6-805A-E8DDFB1CC8F3} 8 | Exe 9 | Properties 10 | Sample 11 | Sample 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | False 37 | ..\packages\slf4net.0.1.32.1\lib\net35\slf4net.dll 38 | 39 | 40 | ..\packages\slf4net.Simple.0.1.32.1\lib\net35\slf4net.Simple.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {df919631-8c28-49e6-be61-23787af2a90d} 61 | EventSource4Net 62 | 63 | 64 | 65 | 72 | -------------------------------------------------------------------------------- /Sample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------