9

I am seeing odd behavior with the code here.

Client-side (Javascript):

<input type="text" id="userid" placeholder="UserID" /><br /> 
<input type="button" id="ping" value="Ping" />
  
<script>
    var es = new EventSource('/home/message');
    es.onmessage = function (e) {
        console.log(e.data);
    };
    es.onerror = function () {
        console.log(arguments);
    };
    $(function () {
        $('#ping').on('click', function () {
            $.post('/home/ping', {
                UserID: parseInt($('#userid').val()) || 0
            });
        });
    });
</script>

Server-side (C#):

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Web.Mvc;
using Newtonsoft.Json;
   
namespace EventSourceTest2.Controllers {
    public class PingData {
        public int UserID { get; set; }
        public DateTime Date { get; set; } = DateTime.Now;
    }

    public class HomeController : Controller {
        public ActionResult Index() {
            return View();
        }  

        static ConcurrentQueue<PingData> pings = new ConcurrentQueue<PingData>();

        public void Ping(int userID) {
            pings.Enqueue(new PingData { UserID = userID });
        }

        public void Message() {
            Response.ContentType = "text/event-stream";
            do {
                PingData nextPing;
                if (pings.TryDequeue(out nextPing)) {
                    var msg = "data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n";
                    Response.Write(msg);
                }
                Response.Flush();
                Thread.Sleep(1000);
            } while (true);
        }
    }
}

Once I've pressed ping to add a new item to the pings queue, the loop inside the Message method picks the new item up and issues an event, via Response.Write (confirmed using Debug.Print on the server). However, the browser doesn't trigger onmessage until I press ping a second time, and the browser issues another event; at which point the data from the first event reaches onmessage.

How can I fix this?


To clarify, this is the behavior I would expect:

Client                       Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
                             Eneque new item to pings
                             Message loop issues server-sent event
EventSource calls onmessage

This is what is actually happening:

Client                       Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
                             Eneque new item to pings
                             Message loop issues server-sent event
(Nothing happens)
Press Ping button again
New XHR to /home/ping
EventSource calls onmessage with previous event data

(While running in Chrome the message request is listed in the Network tab as always pending. I'm not sure if this is the normal behavior of server-sent events, or perhaps it's related to the issue.)

Edit

The string representation of the msg variable after Response.Write looks like this:

"data:{\"UserID\":105,\"Date\":\"2016-03-11T04:20:24.1854996+02:00\"}\n\n"

very clearly including the newlines.

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • check this link will help you to check your latest update js and .css or not http://stackoverflow.com/questions/2185872/force-browsers-to-get-latest-js-and-css-files-in-asp-net-application. Else add Expiration and the Expires Header with this link will help you https://support.microsoft.com/en-us/kb/234067 – Saineshwar Bageri - MVP Mar 08 '16 at 07:27
  • @Saineshwar Did you even read the question? (1) I have no need to refresh the page; I already have the latest Javascript. (2) The Microsoft KB has nothing to do with server-sent events (nor could it, as it is focused on Internet Explorer which doesn't support server-sent events. – Zev Spitz Mar 08 '16 at 13:03

5 Answers5

1

This isn't an answer per say but hopefully it will lead one. I was able to get it working with the following code.

public void Ping(int id)
{
    pings.Enqueue(new PingData { ID = id });
    Response.ContentType = "text/plain";
    Response.Write("id received");
}
public void Message()
{
    int count = 0;
    Response.ContentType = "text/event-stream";
    do {
        PingData nextPing;
        if (pings.TryDequeue(out nextPing)) {
            Response.ClearContent();
            Response.Write("data:" + nextPing.ID.ToString() + " - " + nextPing.Date.ToLongTimeString() + "\n\n");
            Response.Write("event:time" + "\n" + "data:" + DateTime.Now.ToLongTimeString() + "\n\n");
            count = 0;
            Response.Flush();
        }
        if (!Response.IsClientConnected){break;}
        Thread.Sleep(1000);
        count++;
    } while (count < 30);   //end after 30 seconds of no pings
}

The line of code that makes the difference is the second Response.Write. The message doesn't appear in the browser until the next ping similar to your issue, but the ping always appears. Without that line the ping will appear only after the next ping, or once my 30 second counter runs out.

The missing message appearing after the 30 second timer leads me to conclude that this is either a .Net issue, or there's something we're missing. It doesn't seem to be an event source issue because the message appears on a server event, and I've had no trouble doing SSE with PHP.

For reference, here's the JavaScript and HTML I used to test with.

<input type="text" id="pingid" placeholder="ID" /><br />
<input type="button" id="ping" value="Ping" />

<div id="timeresponse"></div>
<div id="pingresponse"></div>

<script>
    var es = new EventSource('/Home/Message');
    es.onmessage = function (e) {
        console.log(e.data);
        document.getElementById('pingresponse').innerHTML += e.data + " - onmessage<br/>";
    };
    es.addEventListener("ping", function (e) {
        console.log(e.data);
        document.getElementById('pingresponse').innerHTML += e.data + " - onping<br/>";
    }, false);
    es.addEventListener("time", function (e) {
        document.getElementById('timeresponse').innerHTML = e.data;
    }, false);
    es.onerror = function () {
        console.log(arguments);
        console.log("event source closed");
        es.close();
    };
    window.onload = function(){
        document.getElementById('ping').onclick = function () {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.onload = function () {
                console.log(this.responseText);
            };
            var url = '/Home/Ping?id=' + document.getElementById('pingid').value;
            xmlhttp.open("GET", url);
            xmlhttp.send();
        };
    };
</script>
OffTheBricks
  • 425
  • 2
  • 9
  • I know this is an old post, but I solved the issue the same way. I believe it has to do with output buffering in the server side, hence when you flush() you are really flushing into the output buffer, which when full sends the data to the browser. I am no expert but the behavior suggests that MOA. In my case I did: > output my current data + flush() > output dummy data of the same size + flush() and works like a charm – Gutiman Dec 14 '22 at 00:14
1

Since an eventstream is just text data, missing the double line break before the first event is written to response could affect the client. The example from mdn docs suggests

header("Content-Type: text/event-stream\n\n");

Which could be applied apply to .NET response handling (note the side effects of Response.ClearContent()).

If it feels too hacky, you could start your stream with a keep-alive comment (if you want to avoid timing out you may have to send comments periodically):

: just a keep-alive comment followed by two line-breaks, Response.Write me first

Oleg
  • 24,465
  • 8
  • 61
  • 91
  • Once I do this, the `message` request is no longer pending in the Chrome network tab, but the issue still persists. – Zev Spitz Mar 11 '16 at 03:03
  • 1
    @ZevSpitz: interesting, looking over the code again (both front- and back-end) I'd check server config for known issues. [Application pool + dynamic compression](https://github.com/SignalR/SignalR/issues/273#issuecomment-9660117) look like a promising start – Oleg Mar 13 '16 at 10:08
  • @o.v. this was it for me, had this exact problem and [disabling dynamic compression](https://github.com/aspnet/Mvc/issues/6969#issuecomment-337842267) on my specific sse path fixed it. – user692942 Dec 23 '19 at 14:24
0

I'm not sure if this will work because I can't try it now, but what about to add an End?:

Response.Flush();
Response.End();
Juan
  • 131
  • 1
  • 7
  • `Response.End()` prevents all events. Apparently, the connection is closed, and the client recognizes that there will be no more events. – Zev Spitz Mar 11 '16 at 02:33
0

EDIT: Would you please try creating the object first and then passing it to Enqueue? As in:

PingData myData = new PingData { UserID = userID };
pings.Enqueue(myData);

There might be something strange going on where Dequeue thinks it's done the job but the the PingData object isn't properly constructed yet.

Also can we try console.log("I made it to the function") instead of console.log(e.data).

---- PREVIOUS INFORMATION REQUESTED BELOW ----

Please make sure that the server Debug.Print confirms this line of code:

Response.Write("data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n");

Is actually executed? Please double check this. If you can capture the server sent response then can we see what it is?

Also could we see what browsers you've tested on? Not all browsers support server events.

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
user1567453
  • 1,837
  • 2
  • 19
  • 22
  • I've tested on Chrome, which supports server-sent events. AFAIK the only browsers (assuming the latest version) that don't support SSE are Internet Explorer / Edge. – Zev Spitz Mar 04 '16 at 08:26
  • @ZevSpitz Thanks for the info. Always have to make sure it isn't one of the simple things. Updated the answer with a couple things to try. Also it would still be good to see what the server response looks like if you could post it. – user1567453 Mar 08 '16 at 13:12
  • Creating the object first, and then passing it to `Enqueue` didn't seem to help. – Zev Spitz Mar 11 '16 at 02:45
  • I tried `console.log('I made it to the function');` as the `onmessage` handler; no difference in the results. – Zev Spitz Mar 11 '16 at 02:49
0

The default behavior of .net is to serialize access to session state. It blocks parallel execution. Requests are processed sequentially and access to session state is exclusive for the session. You can override the default state per class.

 [SessionState(SessionStateBehavior.Disabled)]
 public class MyPulsingController
 {
 }

There is an illustration of this in the question here.

Community
  • 1
  • 1
Ross Bush
  • 14,648
  • 2
  • 32
  • 55