Sample 02 - SubProtocol
The web socket protocol has support for so-called sub-protocols. These are protocols that you yourself define and with Nugget they can be used to send "models" between the browser and the server.Typically the data you send between the server and the browser would be JSON but it could be on any format. In the app on the server you define a model factory that analyses the data coming from the browser and wraps it in an object of a specified type, this is then passed on to the socket handler.
The sample contains two files: client.html and Server.cs, they represent the client and the server respectively.
client.html
Like in sample 01 this file contains the javascript:ws = new WebSocket('ws://localhost:8181/subsample', 'post'); ws.onmessage = function (evt) { debug.innerHTML += evt.data + '<br/>'; }; ws.onopen = function () { debug.innerHTML += '.. connection open<br/>'; }; ws.onclose = function () { debug.innerHTML += '.. connection closed<br/>'; }
The above isn't very different from the javascript in sample 01, except that in this sample the web socket connects to the server using a sub protocol "post".
The file also contains some input fields, and some javascript used to send data to the server:
<input type="text" name="name" id="name" placeholder="name"/><br /> <textarea id="message" cols="50" rows="5" placeholder="message"></textarea><br /> <input type="button" value="Send" id="submit_button" onclick="clicks()" /> var clicks = function () { // get the author and the message body var name = document.getElementById('name').value; var msg = document.getElementById('message').value; // create a json string var json = JSON.stringify({ author: name, body: msg }); debug.innerHTML += "sending " + json + "<br/>"; // send it ws.send(json); }
Server.cs
Like in sample 01 we are using a handler class to handle the connection server-side:// the handler class // note that the class inherits the generic interface and uses the model as the type parameter class PostSocket : WebSocket<Post> { // this method is called when data is comming from the client // note that the method takes a Post object instead of a string public override void Incomming(Post post) { Console.WriteLine("{0} posted {1}", post.Author, post.Body); } public override void Disconnected() { Console.WriteLine("--- disconnected ---"); } public override void Connected(ClientHandshake handshake) { Console.WriteLine("--- connected ---"); } }
This time however, the class uses the generic interface and a "model"-class as type argument. The model:
// a "model" representing a post made by someone class Post { public string Author { get; set; } public string Body { get; set; } public bool IsValid() { return !String.IsNullOrEmpty(Author) && !String.IsNullOrEmpty(Body); } }
The handler class now gets an object of the type Post instead of just a string. In order to create the model, we need to register a model factory:
// the model factory // this class is used to convert the data coming from the client into a model object class PostFactory : ISubProtocolModelFactory<Post> { // create a new model // "data" is the data comming from the client // "connection" is an object representing the connection to the client, it can be used to identify the client if needed public Post Create(string data, WebSocketConnection connection) { // parse the data (we assume that the data string is on json format) var js = JSON.Parse(data); if (js.hasOwnProperty("author") && js.hasOwnProperty("body")) { // create a new model using the data from the client // "return" passes the model on to the handler class return new Post() { Author = js.author, Body = js.body }; } return null; } // this method is used to define valid models // if a model is not valid, then it is not passed on to the handler class // this method is called internally in the server before the model is passed on public bool IsValid(Post p) { if (p == null) return false; else return p.IsValid(); } }
The factory implements the interface ISubProtocolModelFactory<>, this interface specifies two methods:
public interface ISubProtocolModelFactory<TModel> { TModel Create(string data, WebSocketConnection connection); bool IsValid(TModel model); }
Create, should create the model based on the string coming from the client, a connection object is also mad available, if the factory needs to identify the client.
IsValid, should return true for valid models and false for invalid models. This method is used to determine whether or not to pass the object along to the handler class. If Create, creates a model that is invalid (null in this case) the model is not passed on to the handler class.
In a sense you jam a "parser" in between the browser and the server. The model factory (the "parser") reads, validates and formats the data coming from the browser, before passing it on to the handler class.
The handler classes can have multiple Incoming methods defined, by implementing the IWebSocket<> interface.
class MyHandler : WebSocket<MyModel>, IWebSocket<MyOtherModel> { public overide Incoming(MyModel mm) { /* ... */ } public Incoming(MyOtherModel mom) { /* ... */ }
The method to call is selected based on the sub protocol used by the browser and the type of the model created by the model factory.
The model factory is registered with the following line:
nugget.SetSubProtocolModelFactory(new PostFactory(), "post");
Where the string specifies which sub protocol to use the factory for.