Reviewing SignalR–Part II
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.
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:
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:
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:
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.
Comments
This is exactly why I hate JS. Too much magic happening behind the scene and you have to look at the documentation to understand the syntax at every line.
That is a straightforward best-practice variable initialization. It's refreshing to see you struggle with a language that is somewhat alien to you :).
Sorry Oren but that is not a use of the comma operator in assignment, it's just a plain old multiline assignment.
It's the same as if in C# you would've done this:
var connection = this, config = new { transport = "auto" }, initialize;
Oh, and yes, you can declare things on the fly, like:
var ayende = {}; ayende.firstname = "Oren"; ayende.lastname = "Eini";
@njy - How would you write it on seperate lines then?
var connection = this; var config = new { transport = "auto" }; var initialize;
Like that? That would be a lot more readable. The ", initialize" is very confusing to read in my opinion.
You have got to be kidding me :D. JavaScript is a scripting (duh!) language, of course it allows you to define things on the fly. You can redefine practically anything on the fly, including overwriting/hiding existing members on the exiting objects (like Array).
Look up Ruby and monkey-patching. You'd be surprised what the 'other side' has to put up with and even revels in :) If you're used to statically typed languages, I guess all this comes as a bit of shock to you :))
Drazen, What I meant, in JS you can write something like:
query.find_by_name_and_email(); query.find_by_user_and_age();
And stuff like that, which you can in Ruby, which allows you to capture unknown calls and handle them. You can't do that with JS
@Kristof: yes, that would be the way. Regarding readabilty, it depends on tastes and what you're used to, really: suppore you're a .net dev from the 2.0 times (that is, you don't know lambdas) , in that case a js closure may seem really ugly and obscure. But now that .net people know about lambdas and Func<T> stuff, js colsures feel more intuitive, for example.
@oren: that is not "defining" stuff on the fly, that is "calling" something undefined on the fly (and, after that call, it will still remain undefined) and having the object intercept the thing on the fly and do something. Much like the DynamicObject.TryGetMember(...) in .net.
In that case you're right, js can't do that.
@njy, If you look at the line of code Ayende was commenting on with the "on the fly", it was calling
$.connection.chat
. It wasn't assigning a value like your original example. So theoretically, the result should have been undefined, but it was surprising that it was an object.@Harry: yeah, with his next example in the comments i got what he meant ;-)
Interesting approach to figuring out what is going on with the implementation details. That whole search for context seems to be what is missing from a lot of technical articles and leads to a lot of mind-numbing debates where people just talk past each other.
Also, I will not rest until I've terrified someone with horrible misuse of that comma operator.
Ayende,
if we're talking about "method_missing" in Ruby, then you are definitely right - in standard JS that doesn't exist yet.
Btw, your CAPTCHA is killing me :) - I just posted essentially the same comment twice, plus I wasn't sure if the comment got posted or not.
Curiously, the captcha isn't there any more - is that because I've posted more than 2-3 times?
Drazen, Yes, after you authorized a few times, it just works
Having going from years of C# to doing a lot of both C# and JS in the last year or so, this is a remarkably familiar scenario :) It does take a bit of getting used to. Especially the idea that you can just add properties/methods on the fly, functions as first class objetcs. Not to mention dealing with nonexistent entities, or using a variable for any number of different things in a single context.
It is easy to dismiss or underestimate Javascript, but once you get the hang of it you start wishing you could do some of this stuff in C#. You lose some structure but you gain some incredibly terse code. And because JS is so easily extensible it's much easier to create natural, readable frameworks and constructs. After working in JS for a day I go back to C# and feel like I'm in "structure soup" and wish all those classes would just go away or maybe become a nice prototype. Pros and cons on both sides, but I've definitely learned to appreciate JS.
After knowing that Ayende struggled on SignalR I was hoping He would help us in understanding it, but he didn't and others too started talking on JS :) That is funny from Microsoft Surrounded guys that they go off topic in the frustration of Js. Well whoever says they hate Js, I don't think they loved it like others did who made lots of good things in js.
Glad to see you had the exact same questions I did when looking in the code.
I really like your blogging style of investigating and asking questions which you can later answer.. taking you along your thought process. Its very relieving to see we both followed similar paths.
Comment preview