
Implementing Server Push for Nancy

With the help of my module, in just a minute and a couple of simple steps, you can get a reliable feedback channel from the server to the browser. Want to know the details?
A little background
While you come to your senses after viewing the picture to attract attention, I will briefly tell you where this module of mine came from. At the end of 2011, in one of the projects we needed to organize a real-time communication channel from the server to the web interface. At that time I was familiar with the technique of solving such a problem, fortunately, there were already several articles on Comet, Server Push, and Long Polling on the hub Moreover, according to my feelings, it was at that time that a fashion came for web interfaces and web applications that update data in real time without reloading the entire page under the conditions of the web.
I decided not to reinvent the wheel and find a ready-made solution. Our project was implemented on ASP .Net MVC 3 (or 2 more?), So the obvious choice fell on SignalR, which miraculously was released just a month before we needed it. SignalR connected and worked, at first glance, without problems, but after a while some unfortunate features were clarified regarding the detection of loss of connection with the server and restoration of the connection. I have no doubt that everything is good in SignalR, but at that time these “features” were annoying, as a result of which the natural desire to do everything from scratch with blackjack and boats overpowered the desire to understand what was wrong with someone else's code. For the evening I wrote a controller and a client script that existed almost unchanged until today, wandering from one project to another.
A year ago, our team and I discovered a great alternative to the Microsoft MVC “in the face” of the Nancy framework . Since we are now doing new projects on it, our old developments have slowly begun to take shape in the form of Nancy modules. As a result, the turn came to the long polling module, which turned out, in my opinion, self-sufficient and decent enough to share it with the community. You can download the code with an example and tests here .
Now that I, hopefully, have justified my cycling, let's get to the point.
Instructions for use
In order to run the module in your project, you will need:
- Download and build the Nancy.LongPoll project, connect it to your project;
- register the PollService class as a singleton in the IoC container of the application ;
- on each web page where you want to receive notifications from the server, call startPoll (), and also override the pollEvent (messageName, stringData) function, where stringData will come as a string. Usually, it is most convenient to send json on this line, and then do JSON.parse (stringData).
After you complete these steps, everything will be ready to start sending messages from the server to browsers. To send messages, the PollService class provides several methods that are made in the IPollService interface:
interface IPollService
{
// Отправка сообщения списку клиентов
void SendMessage(List clientIds, string messageName, string message);
// Отправка сообщения клиенту
void SendMessage(string clientId, string messageName, string message);
// Отправка сообщения всем клиентам
void SendMessageToAllClients(string messageName, string message);
// Отправка сообщения клиентам с идентификатором сеанса sessId
void SendMessageToSession(string sessId, string messageName, string message);
// Отправка сообщения клиентам с сеансами из списка sessIds
void SendMessageToSessions(List sessIds, string messageName, string message);
// Отключить клиента
void StopClient(string clientId);
}
I’ll explain the terminology right away. A client refers to any browser window or tab connected to a service. A customer is the smallest unit to which a personal message can be sent. Clients are characterized by string identifiers that are created when the connection is established, stored on the server and sent with each request from the browser. The identifier string is randomly generated and is long enough so that the identifier cannot be falsified.
Session - Corresponds to the concept of a session commonly accepted in web technologies. The default session ID is stored in a cookie named nancy_long_poll_session_id. By sending a message to a session, you are most likely sending it to a specific browser, i.e. All browser tabs will receive this message immediately. You can override the server-side generation of the session identifier by implementing the ISessionProvider interface and registering your class in the IoC container of the application or request. You may need to redefine a session if you already have, for example, binding users to sessions. This way you can send messages to specific users. If you do not, the default implementation will be used, which generates a session identifier as a random string.
In addition, you can implement the ILogger interface in order to receive diagnostic messages from the module. The default implementation is EmptyLogger, which does nothing.
And finally, at any time, you can stop the client-initiated polling by the JavaScipt call to stopPoll (), although this is usually not required. You can resume polling by calling startPoll () again.
Module usage example
So that you can try it all in action, I wrote a small example of using the module, which is available at: https://github.com/AIexandr/Nancy.LongPoll/tree/master/Nancy.LongPoll.Example The

example is implemented as a self host console application running on the 80th port, so for it to work, you may need to run Visual Studio with administrator rights. In addition, do not forget that the port may be busy, for example, with Skype. Once a second, the server sends to all clients the value of the incrementing counter, which is displayed in the browser window. You can open several windows at once to make sure that the counter is updated synchronously.
At the top of the application page, the status of communication with the server is displayed. By clicking the Stop poll button, you can disconnect the connection at the initiative of the client. You can resume the connection by clicking the Start poll button. Using the Stop notifications on server button, you can pause the example of the server notification distribution module. The Start notifications on server button allows you to resume its work.
Using the chrome debug panel, you can track how the polling module works (see screenshot):

- After loading the page, the client has registered.
- About once per second, notifications from the server come.
- The Stop notifications on server button was pressed . A POST request was sent to the server, which turned off the demo notification generation service. Server notifications were also stopped within 2.8 minutes. hung a long HTTP request to the server (see the long horizontal green bar in the screenshot).
- The Start notifications on server button was pressed , a POST request was sent to the server, which resumed the operation of the demo service, after which messages began to arrive again once a second.
- The Stop poll button was pressed . The survey stopped at the initiative of the client.
- The Start poll button was pressed . The survey has resumed.
The example does not show the case of disconnecting clients at the server’s initiative. You can try to master this function yourself; the StopClient () method of the polling service is responsible for it.
Example project structure:
- Program.cs is the standard class for a console application. Launches Nancy Self Host and opens two browser windows.
- Bootstrapper.cs - Overrides the standard Nancy bootstrapper. Registers a polling service and a demo notification generation service in an IoC container.
- ExampleModule.cs - Demo NancyModule. It returns Index.html at the request of the browser and responds to POST requests / Start and / Stop, which start and stop the demo notification service.
- ExampleNotificationService.cs - demo notification service. Once a second, it sends to the polling service a “broadcast” (to all clients) notification with a new increment counter value.
- Index.html - the actual web interface page. Located in the project as Embedded Resource.
Module device
The Nancy.LongPoll.dll library includes server .Net classes and a poll.js client script built into the library as an Embedded Resource. Here is a list of library files:
- ContentModule.cs - Nancy module responsible for issuing embedded resources for HTTP requests. As part of the Nancy.LongPoll.dll is used to issue poll.js.
- Logger.cs - contains the interface and the empty implementation of the logger. Allows you to untie Nancy.LongPoll from a specific implementation of the logger.
- poll.js is a polling script containing the implementation of client polling logic.
- DefaultSessionProvider.cs - Contains the interface and default implementation of the session identifier provider.
- PollModule.cs - implementation of the Nancy module, necessary for the operation of the survey service.
- PollService.cs - the actual survey service.
At first, I wanted to describe in detail the device and module operation, but then I decided not to overload the article with unnecessary details. Instead, I will provide the most significant facts about poll.js and the PollService class:
- The PollService class contains a list of currently connected clients with reference to client and session identifiers. Clients are described by the PollService.Client class.
- Messages are sent from the server to the clients as a json structure. The message structure is described by the PollService.Message class and contains the following fields: success indicator, return code, message name, data line. Of course, it would be more correct to convey the success of the action and the return code with HTTP status codes, but I do not consider this to be such a big disadvantage of the implementation.
- The interaction of poll.js and PollService begins with the registration of the client. During the registration process, the client is assigned an identifier, a message queue, and seqNumber is the number of the message processed by the client.
- poll.js opens a “long” connection to PollService, reporting the number of the last message received. In the case when the client has a message with a number greater than the last processed by the client, the message is retrieved from the queue and transmitted as a response to the client, after which it is deleted from the queue. If there are no messages, then PollService does not release the HTTP connection and “takes time”. There is something to improve: if there are several messages accumulated in the message queue since the last time the client contacted, they will be sent to the client one at a time, although it would be more correct to give the client an array of messages immediately, and then parse it in poll.js.
- PollService remembers the time of the last call of each client. If more than the specified PollService.CLIENT_TIMEOUT field has passed since the last client, the client is considered disconnected. Again, there is something to improve. The client does not notify the server of its intention to disconnect, even if you call stopPoll ().
- The number of simultaneously connected clients can be limited by the value of the PollService.MAX_CLIENTS field.
- poll.js after calling startPoll () starts active attempts to establish a connection with the server and register. If the connection breaks or an error occurs during data transfer, poll.js automatically resumes attempts to establish a connection. The activity status of the script is in the variable isPollActive, the communication state is reflected in the variable isPollConnected. However, if you call the PollService.StopClient (clientId) method on the server, poll.js will stop trying to establish a connection until the next call to startPoll (). As a further improvement of the module, here you can add versions of the method to stop connecting the session and generally all clients.
- The PollService class is designed and implemented taking into account the multithreaded nature of the processes occurring here. In my opinion, it turned out not bad, but I used simple locks, although it would be nice to redo it with ReaderWriterLockSlim.
That, in fact, is all that I wanted to tell. Feel free to ask questions and also use my module. I would also be happy to consider sensible pull requests.