Reviewing SignalR–Part II

time to read 6 min | 1099 words

After reading this post from Scott Hanselman, I decided that I really want to take a look at SignalR. This is part two of my review.

I cloned the repository and started the build, which run cleanly, so the next step was to take a look at the code. I didn’t notice any tests in the build, and I am really interested in knowing how SignalR is tested. Testing websites is notoriously hard, if only because you need to spin up IIS/WebDev to do so.

image

No tests… as I said, I can understand why, but it is worrying, because it means that if I wanted to use SignalR, I would have to come up with my own testing strategy, I hoped that there would be something there out of the box.

With RavenDB, we put special attention to making it as easy as possible to run in a unit test context:

using(var documentStore = new EmbeddableDocumentStore { RunInMemory = true }.Initialize())
{
    // run tests here
}

For the sake of doing something different, I decided to start looking at things by reading the JS scripts first. It would be interesting to look at things from that angle first.

The first interesting thing that I run into was this:

image

That is some funky syntax. I actually had to go to the docs to figure it out, and I got side tracked with the comma operator, but it seems like a typical three variable initialization, although I’ll admit that the initialize just showing up there screwed me up for a while. The rest of the code in the jquery.signalR.js is pretty straightforward, using web sockets or long polling, let us move on to hubs.js.

The next step for me was to figure out that I wanted to debug through this, so I got the SignalR-Chat sample app and tried it. It worked, perfectly, I just couldn’t figure out why it was working.

My main frustration was actually with this piece of code:

image

SignalR doesn’t have any chat property there, and I don’t think that JS allows you to define things on the fly. I was expecting this to fail, but it worked! That was quite annoying, so I set out to figure what was going on.

Looking at the network, it was making a call to http://chatapp.apphb.com/signalr/hubs, and that has this:

image

Where did this come from?! And how come that this thing got a response?

That really annoyed me, I could see no writing whatsoever for /signalr/hubs in the Chat application. I check the web.config, I check for .ashx files, I checked the global.asax, nothing.

Finally I went back to the SignalR code and figured out that it was using a PreApplicationStart hook to inject an http module, which would hook this up for you. Now I had a better understanding of what is going on, but I also needed to figure out where the chat references came from.

That is where the high level API comes from. SignalR Hub is going to process itself and generate the appropriate proxy on the client. Thinking about this, it is really nice, it was just surprising to to get there, and getting lost in web.config along the way didn’t help my state of mind.

Okay, now that I understand how the client side works, let us go and actually look at what is going on over the network. I am testing this using Chrome 13.0.782.220 against the Chat sample.

The first interesting thing is:

POST http://chatapp.apphb.com/signalr/negotiate HTTP/1.1
Host: chatapp.apphb.com
Connection: keep-alive
Referer: http://chatapp.apphb.com/
Content-Length: 0
Origin: http://chatapp.apphb.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: userid=26c2b4a1-f5d1-439c-8ad5-f598b7bd0644

I marked the pieces of interest. The cookie indicate that we can identify a user for a long period, which is good.

HTTP/1.1 200 OK
Server: nginx/1.0.2
Date: Tue, 06 Sep 2011 07:15:49 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Cache-Control: private
Content-Length: 68

{"Url":"/signalr","ClientId":"53d681cf-08d9-4afe-8357-7918f64e7a60"}

Note that the userid and client id are different. Maybe a new one is generated on every negotiation? Yep, that seems to be the case. I can see why, and why you would also want to have it persisted. Not really interesting now.

I dug into the the actual implementation, and it is using long polling. In order to do that, SignalR uses an interesting delay messages system that I find extremely interesting, mostly because it is very similar to some of the core concepts of RavenMQ. At any rate, the code seems pretty solid, and I have gone through most of it.

It is still somewhat in a state of flux, with things like API naming and conventions needing to be settled done, but so far, I think that I like what I am seeing.

One thing that bugs me about it is that I think that there is actually two different things happening at the same time. There is SignalR, which is all about persistent connections, and then there is the Hubs, which is a much higher level API. I would like to SignalR.Hubs as a separate assembly, if only to make sure that the core concepts can be used on their own, without the Hubs.