8
votes

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.

5
check this link will help you to check your latest update js and .css or not stackoverflow.com/questions/2185872/…. Else add Expiration and the Expires Header with this link will help you support.microsoft.com/en-us/kb/234067Saineshwar
@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

5 Answers

1
votes

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>
1
votes

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

0
votes

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();
0
votes

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.

0
votes

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.