{"id":4883,"date":"2021-04-14T15:32:15","date_gmt":"2021-04-14T14:32:15","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=4883"},"modified":"2022-07-25T08:35:46","modified_gmt":"2022-07-25T07:35:46","slug":"server-sent-events-sse","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/server-sent-events-sse\/","title":{"rendered":"Server Sent Events (SSE)"},"content":{"rendered":"\n<p><a href=\"https:\/\/www.w3schools.com\/html\/html5_serversentevents.asp\">https:\/\/www.w3schools.com\/html\/html5_serversentevents.asp<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/stackoverflow.com\/questions\/36227565\/aspnet-core-server-sent-events-response-flush\">https:\/\/stackoverflow.com\/questions\/36227565\/aspnet-core-server-sent-events-response-flush<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/www.smashingmagazine.com\/2018\/02\/sse-websockets-data-flow-http2\/\">https:\/\/www.smashingmagazine.com\/2018\/02\/sse-websockets-data-flow-http2\/<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/stackoverflow.com\/a\/58565850\">https:\/\/stackoverflow.com\/a\/58565850<\/a><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/&lt;body>\n\/\/    &lt;script type=\"text\/javascript\">\n\nvar source = new EventSource(\"\/sse\");\nsource.addEventListener(\"message\", (event) => console.log(event.data));\nsource.addEventListener(\"open\", (event) => console.log(\"SSE open\", event));\nsource.addEventListener(\"close\", (event) => console.log(\"SSE close\", event));\nsource.addEventListener(\"error\", function (e) {\n    if (e.readyState == EventSource.CLOSED) {\n        \/\/ Connection was closed.\n        console.log(\"SSE: connection closed\");\n    } else {\n        console.log(e);\n    }\n}, false);\n\/\/    &lt;\/script>\n\/\/&lt;\/body><\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class Counter \n{\n\tpublic Counter(int start = 0, int resetAt = 100000) {\n\t\tcounter = start;\n\t\treset = resetAt;\n\t}\n\tprivate volatile int counter;\n\tprivate int reset;\n\tpublic int Current => counter;\n\tpublic int GetNext() \n\t{\n\t\tcounter += 1;\n\t\tcounter %= reset;\n\t\treturn counter;\n\t} \n}\n\n[HttpGet(\"html\")]\npublic ContentResult GetHtml()\n{\n\tvar html = string.Join(Environment.NewLine, new string[] {\n\t\t\"&lt;body>\",\n\t\t\"&lt;script type=\\\"text\/javascript\\\">\",\n\t\t\"var source = new EventSource(\\\"\/WeatherForecast\/stream\\\");\",\n\t\t\"source.addEventListener(\\\"message_a\\\", (event) => console.log('message_a', event.data, event.lastEventId));\",\n\t\t\"source.addEventListener(\\\"message\\\", (event) => console.log(event.data));\",\n\t\t\"source.addEventListener(\\\"open\\\", (event) => console.log(\\\"SSE open\\\", event));\",\n\t\t\"source.addEventListener(\\\"close\\\", (event) => console.log(\\\"SSE close\\\", event));\",\n\t\t\"source.addEventListener(\\\"error\\\", (event) => console.log(\\\"SSE error\\\", event));\",\n\t\t\"&lt;\/script>\",\n\t\t\"&lt;\/body>\"\n\t});\n\treturn Content(html, \"text\/html\");\n}\n\npublic static ConcurrentDictionary&lt;int, HttpResponse> Responses = new ConcurrentDictionary&lt;int, HttpResponse>();\npublic static Counter ResponseCounter = new Counter();\npublic static Counter EventCounter = new Counter();\n\npublic static void SendEvent() {\n\tif (!Responses.Any()) return;\n\tvar rng = new Random();\n\tint delay = 100 * rng.Next(3, 40);\n\tvar json = JsonConvert.SerializeObject(new { random = rng.NextDouble() * 10, delay = delay });\n\tvar i = EventCounter.GetNext();\n\tvar message = string.Join(Environment.NewLine, new string[] { \n\t\t$\"id: {i}\",\n\t\t\"event: message_a\",\n\t\t$\"data: {json}\",\n\t\t\"\\n\" });\n\tforeach(var kv in Responses) {\n\t\tTask.Run(async () => {\n\t\t\ttry\n\t\t\t{\n\t\t\t\tawait kv.Value.WriteAsync(message);\n\t\t\t} \n\t\t\tcatch(ObjectDisposedException ex)\n\t\t\t{   \n\t\t\t\tResponses.Remove(kv.Key, out var _);\n\t\t\t\tConsole.WriteLine(ex.ToString()); \/\/ Should not occur by default\n\t\t\t}\n\t\t});\n\t}\n}\n\n[HttpGet(\"stream\")]\npublic async Task GetStream()\n{\n\tvar response = Response;\n\tvar key = ResponseCounter.GetNext();\n\n\tResponses.TryAdd(key, response);\n\tresponse.Headers.Add(\"Content-Type\", \"text\/event-stream\");\n\n\tvar cancellationToken = HttpContext.RequestAborted;\n\tawait Task.Run(() => {\n\t\tcancellationToken.WaitHandle.WaitOne();\n\t\tResponses.Remove(key, out var _);\n\t});\n}<\/pre>\n\n\n\n<p><strong>Middleware<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ Server with middleware\napp.Use(async (context, next) =>\n{\n    if (context.Request.Path.ToString().Equals(\"\/sse\"))\n    {\n        var response = context.Response;\n        response.Headers.Add(\"Content-Type\", \"text\/event-stream\");\n\/\/ Connection: keep-alive\n\n        for(var i = 0; true; ++i)\n        {\n            \/\/ WriteAsync requires `using Microsoft.AspNetCore.Http`\n            await response\n                .WriteAsync($\"data: Middleware {i} at {DateTime.Now}\\r\\r\");\n\n            await response.Body.FlushAsync();\n            await Task.Delay(5 * 1000);\n        }\n    }\n\n    await next.Invoke();\n});<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>https:\/\/www.w3schools.com\/html\/html5_serversentevents.asp https:\/\/stackoverflow.com\/questions\/36227565\/aspnet-core-server-sent-events-response-flush https:\/\/www.smashingmagazine.com\/2018\/02\/sse-websockets-data-flow-http2\/ https:\/\/stackoverflow.com\/a\/58565850 Middleware<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[5,4,1],"tags":[],"class_list":["post-4883","post","type-post","status-publish","format-standard","hentry","category-javascript","category-programming","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/4883","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=4883"}],"version-history":[{"count":11,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/4883\/revisions"}],"predecessor-version":[{"id":5017,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/4883\/revisions\/5017"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=4883"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=4883"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=4883"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}