2016-05-23 4 views
2

Ich versuche, Nachrichten mithilfe von Server Sent Events an JS-Clients zu senden. Der Kunde bekommt nur jedes 6. oder 7. Event. Was mache ich falsch?Fehlende Ereignisse mithilfe von IServerEvents.NotifyChannel

Das Verhalten kann mit einer einfachen Standalone-Probe wiedergegeben werden:

using System; 
using System.Threading; 

using Funq; 
using ServiceStack; 

namespace ServerSentEvents 
{ 
    public class AppHost : AppSelfHostBase 
    { 
     /// <summary> 
     /// Default constructor. 
     /// Base constructor requires a name and assembly to locate web service classes. 
     /// </summary> 
     public AppHost() 
      : base("ServerSentEvents", typeof(AppHost).Assembly) 
     { 

     } 

     /// <summary> 
     /// Application specific configuration 
     /// This method should initialize any IoC resources utilized by your web service classes. 
     /// </summary> 
     /// <param name="container"></param> 
     public override void Configure(Container container) 
     { 
      SetConfig(new HostConfig 
      { 
#if DEBUG 
       DebugMode = true, 
       WebHostPhysicalPath = "~/../..".MapServerPath(), 
#endif 
      }); 

      container.Register<IServerEvents>(c => new MemoryServerEvents()); 
      Plugins.Add(new ServerEventsFeature 
      { 
       OnPublish = (res, msg) => 
       { 
        // Throws an exception 
        //res.Write("\n\n\n\n\n\n\n\n\n\n"); // Force flush: http://stackoverflow.com/questions/25960723/servicestack-sever-sent-events/25983774#25983774 
        //res.Flush(); 
       } 
      }); 

      container.Register(new FrontendMessages(container.Resolve<IServerEvents>())); 
     } 
    } 

    public class FrontendMessage 
    { 
     public string Level { get; set; } 
     public string Message { get; set; } 
    } 

    public class FrontendMessages 
    { 
     private readonly IServerEvents _serverEvents; 
     private Timer _timer; 

     public FrontendMessages(IServerEvents serverEvents) 
     { 
      if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents)); 
      _serverEvents = serverEvents; 

      var ticks = 0; 
      _timer = new Timer(_ => Info($"Tick {ticks++}"), null, 500, 500); 
     } 

     public void Info(string message, params object[] parameters) 
     { 
      var frontendMessage = new FrontendMessage 
      { 
       Level = "success", 
       Message = message 
      }; 

      Console.WriteLine("Sending message: " + frontendMessage.Message); 
      _serverEvents.NotifyChannel("messages", frontendMessage); 
     } 
    } 
} 

und den Kunden:

<!DOCTYPE html> 

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <meta charset="utf-8" /> 
    <title></title> 
    <script src="js/jquery-1.11.1.min.js"></script> 
    <script src="js/ss-utils.js"></script> 
</head> 
<body> 
<script> 
    // Handle messages 
    var msgSource = new EventSource('event-stream?channel=messages&t=' + new Date().getTime()); 
    $(msgSource).handleServerEvents({ 
     handlers: { 
      FrontendMessage: function (msg) { 
       console.log('Message from server', msg); 
      } 
     } 
    }); 
</script> 
</body> 
</html> 

Konsolenprotokoll wie folgt aussieht:

Message from server Object {Level: "success", Message: "Tick 28"} 
Message from server Object {Level: "success", Message: "Tick 35"} 
Message from server Object {Level: "success", Message: "Tick 42"} 
Message from server Object {Level: "success", Message: "Tick 49"} 
+1

Haben Sie [diese Frage] überprüft (http: // stackoverflo w.com/questions/25960723/servicestack-sever-sent-events?lq=1)? Es sieht ähnlich aus –

+0

Ja, wie Sie in der Probe sehen können, habe ich den vorgeschlagenen OnPublish-Code enthalten. Ich habe es auskommentiert, da es in diesem Fall Ausnahmen auslöst. Es ist nicht mein richtiger Welt Code Gedanke. Aber es funktioniert auch nicht. –

Antwort

2

Das Problem ist, dass Sie versuchen, Nachrichten an IServerEvents vor derzu sendenwurde sogar registriert, seit Sie es sofort in AppHost.Configure() statt starten, nachdem die AppHost initialisiert wurde. Die eigentliche Ursache des Problems war, dass IdleTimeout beim Start des Timers nicht ordnungsgemäß initialisiert wurde und jede Server-Ereignisverbindung eine Lebensdauer von 00:00:00 aufwies, was bedeuten würde, dass sie eine Nachricht erhielten, die automatisch entsorgt und automatisch neu verbunden wurde wieder - der ganze Prozess würde ungefähr 6-7 Ticks dauern :)

ServiceStack Plugins sind nicht registriert, wenn sie hinzugefügt werden, sie werden zusammen nach AppHost.Configure() registriert, was anderen Plugins eine Chance gibt, andere Plugins vorher hinzuzufügen/zu entfernen/zu inspizieren Sie sind registriert. Sie müssen auch nicht MemoryServerEvents registrieren, da dies die Standardeinstellung ist und die recommended way to initialize a timer with an intervaltimer.Change() im Timer-Callback verwenden soll.

Vor diesem Hintergrund würde ich Ihre AppHost umschreiben als:

public class AppHost : AppSelfHostBase 
{ 
    public AppHost() 
     : base("ServerSentEvents", typeof(AppHost).Assembly) { } 

    public override void Configure(Container container) 
    { 
     SetConfig(new HostConfig { 
#if DEBUG 
      DebugMode = true, 
      WebHostPhysicalPath = "~/../..".MapServerPath(), 
#endif 
     }); 

     Plugins.Add(new ServerEventsFeature()); 
     container.Register(c => new FrontendMessages(c.Resolve<IServerEvents>())); 
    } 
} 

Und Ihren FrontendMessages nur starten, wenn Start() explizit genannt wird, das heißt:

public class FrontendMessage 
{ 
    public string Level { get; set; } 
    public string Message { get; set; } 
} 

public class FrontendMessages 
{ 
    private readonly IServerEvents _serverEvents; 
    private Timer _timer; 

    public FrontendMessages(IServerEvents serverEvents) 
    { 
     if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents)); 
     _serverEvents = serverEvents; 
    } 

    public void Start() 
    { 
     var ticks = 0; 
     _timer = new Timer(_ => { 
      Info($"Tick {ticks++}"); 
      _timer.Change(500, Timeout.Infinite); 
     }, null, 500, Timeout.Infinite); 
    } 

    public void Info(string message, params object[] parameters) 
    { 
     var frontendMessage = new FrontendMessage { 
      Level = "success", 
      Message = message 
     }; 

     Console.WriteLine("Sending message: " + frontendMessage.Message); 
     _serverEvents.NotifyChannel("messages", frontendMessage); 
    } 
} 

Dann es nur starten, nachdem der AppHost gewesen initialisiert, dh:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var appHost = new AppHost() 
      .Init() 
      .Start("http://*:2000/"); //Start AppSelfHost 

     appHost.Resolve<FrontendMessages>().Start(); //Start timer 

     Process.Start("http://localhost:2000/"); //View in Web browser 
     Console.ReadLine(); //Prevent Console App from existing 
    } 
} 
+0

Danke Demis, jetzt klappt es wie erwartet :) Danke auch für den Timer tip. –

Verwandte Themen