使用IServerEvents.NotifyChannel丢失事件

时间:2016-05-23 12:07:23

标签: servicestack server-sent-events

我正在尝试使用Server Sent Events向JS客户端发送消息。客户只获得每第6或第7次活动。我做错了什么?

可以使用简单的独立样本重现行为:

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);
        }
    }
}

客户:

<!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>

控制台日志如下所示:

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 个答案:

答案 0 :(得分:2)

问题是,您尝试在IServerEvents注册之前向ServerEventsFeature发送邮件,因为您已在AppHost.Configure()中立即启动邮件,而不是在AppHost之后1}}已初始化。问题的实际原因是,在启动计时器时IdleTimeout未正确初始化,导致每个服务器事件连接的生命周期为00:00:00,这意味着他们收到的消息不是自动处理并再次自动重新连接 - 整个过程大约需要6-7个滴答:)

ServiceStack Plugins在添加后未注册,它们在AppHost.Configure()之后一起注册,这使得其他插件有机会在注册之前添加/删除/检查其他插件。您也不需要注册MemoryServerEvents,因为这是默认值,recommended way to initialize a timer with an interval是在计时器回调中使用timer.Change()

鉴于此,我会将您的AppHost重写为:

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>()));
    }
}

只有在明确调用Start()时才开始使用FrontendMessages,即:

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);
    }
}

然后只在AppHost初始化后启动它,即:

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
    }
}