Custom "No Host" Protocol
Creating a custom protocol may sound like a hard and complex task, and it can be! However, it can also be very easy since it is all about how much complexity you add to your protocol. To just a a protocol like the one we use for connecting Putty is very easy. Writing something like AMQP
or MQTT
will require a lot more from you :)
If you will write the client your self your first decision will probably be about how your frames should be handled. Read about messages framing here. It is a pretty old post by Stephen Cleary, but a good one.
If there already is a client you will have no choice, you have to implement the protocol the way the client expects it to work (like we had to do with WebSockets
for example).
The Challange
Every now and then our users have unusual requests. Recently we had a user that wanted to connect to XSockets with WebSockets from a browser. However, he did not want to host the .html in a webserver but instead open a socket from a .html file on disk. Like file://...
.
Since XSockets WebSocket-protocol expects the user to have a valid URI as origin this is not possible with the default WebSocket-protocol in XSockets. So how can we solve this?
The Solution
As it turns out this was very easy to implement. Basically all we had to do was to implement a new WebSocket-protocol with a specific sub-protocol for this purpose. We just inherit the default WebSocket-protocol in XSockets and then override the Match
method to check if the handshake is from a client using the NoHost
subprotocol.
NoHost Protocol
The protocol is so small that it is pasted in below. The important parts is when we check for the NoHost
protocol in the Match
method and the SetUri
that just says that this origin is localhost
/// <summary>
/// A subprotocol for allowing websockets without a valid URI
/// This means that you can connect with a browser from c:\somepath\somefile.html
/// </summary>
[Export(typeof(IXSocketProtocol), Rewritable = Rewritable.No)]
public class NoHostProtocol : Rfc6455Protocol, IRfc6455Protocol
{
/// <summary>
/// Check that the client is using websocket and the sub-protocol for NoHost
/// </summary>
/// <param name="handshake"></param>
/// <returns></returns>
public override async Task<bool> Match(IList<byte> handshake)
{
var s = Encoding.UTF8.GetString(handshake.ToArray());
Composable.GetExport<IXLogger>().Debug("NoHost-Handshake: {s}", s);
return
Regex.Match(s, @"(^Sec-WebSocket-Version:\s13)", RegexOptions.Multiline).Success
&&
Regex.Match(s, @"(^Sec-WebSocket-Protocol:\sNoHost)", RegexOptions.Multiline).Success;
}
/// <summary>
/// We do not have a calid request uri, set it to be localhost
/// </summary>
public override void SetUri()
{
ConnectionContext.RequestUri = new Uri("http://localhost");
}
public override IXSocketProtocol NewInstance()
{
return new NoHostProtocol();
}
}
Test
Since this protocol is WebSocket based we can try it from the browser via a file on disk. So I created a index.html file and wrote a few lines of JavaScript
Note that we do not use the XSockets JavaScript API in this sample. We could ofcourse use that, but we want to show that you can connect with native websockets as well.
You can see the source on GitHub, but the only important part is really the usage of the new SubProtocol. So creating the WebSocket looks like this
//Open a native websocket with the new sub-protocol
var ws = new WebSocket('ws://localhost:4502', ['NoHost']);
The image below show the sample in action where 2 browsers talk to each other from the location file://
.
Source Code
This simple sample is available on GitHub