Saturday, 6 November 2010

AspComet - High Level Architecture Overview

I have received a few requests to give one of these, and I promised over a year ago that I would do! Being a typical developer I don't like writing documentation, but I think this will help with anyone doing any work with AspComet to understand what's going on under the hood.

I assume you have basic knowledge of the Bayeux protocol before reading this document. If not, then give it a read as it will help to understand what I’m talking about!

Bayeux “Clients”

When a browser first connects to AspComet it will enter a series of negotiation requests. The server is at this point creating a Client object to represent that browser and giving it a unique ID, which is communicated back to the browser and understood and stored by the Bayeux JavaScript implementation. This client ID is used in all subsequent requests between browser and server to marry up the requests.

On the server side, the client class stores, amongst other things, the list of channels to which the browser has subscribed, such that it can send the appropriate messages to that browser when required.

Async Http Handlers

As I mentioned in a previous blog post, we implement async http handler which is provided by the ASP.NET pipeline. These handlers are designed for sites which perform long running operations and wish to avoid starving the ASP.NET thread pool during these operations (by returning threads back to ASP.NET during the time that said operations are being made). AspComet piggybacks off of this and uses it in a slightly different way as probably intended.

When a request comes into the browser, it’s picked up by the BeginProcessRequest method where we create an AsyncResult to return back to ASP.NET. At this point, the thread will be returned to the request pool.

Two things can now happen here – if there is data already there to be sent to the browser (or the incoming messages generated responses which need to be sent straight back) then the AsyncResult will be completed immediately with those messages (raising the callback which ASP.NET gave us to call when we’re ready). Otherwise the AsyncResult gets stored along with the client and will be used at the point a message is ready to be sent.

To illustrate this with an example, client A wants to send a message to client B. Client B connects, and has no pending messages, so his AsyncResult is stored inside his Client instance. At this point client A’s browser sits there waiting for a response to come back. Client A sends a publish message which needs to be sent to B, at that point from within Client A’s handler thread Client B’s callback is called, sending the message straight to Client B’s browser.

Message Handlers

Within Bayeux there are a number of predefined channels which are used for passing specific requests to the server. These are called Meta channels. Within AspComet there is a Message Handler for each one.

The HttpHandler receives an array of messages from the browser and passes them all to the MessageBus. This splits up the array and passes each message to the appropriate handler in turn. Once all messages have been handled, it’s up to the message bus to decide whether to complete the AsyncResult immediately or store it in the client to be called at a later date. I won’t go into detail about each MessageHandler is doing, as it’s basically here that the Bayeux protocol implementation comes into play – the protocol is essentially providing a spec for what each of the handlers should be doing.

Application Interfaces

The primary way for applications to interface with AspComet is via Events and the EventHub. Events are raised by the message handlers for each type of message being handled, which go through the central EventHub static class. This enables pretty much any scenario you can think of!

Applications will typically want to be notified when messages are published to specific channels, when new clients connect and when clients subscribe and unsubscribe to channels. This is all supported via Events. Some events also support cancellation, so an application could subscrivbe to those and set Cancel=True, for example to cancel subscription or block certain messages reaching channels.

For complete control, it’s also possible to use dependency injection to inject alternate implementations of some of the types used by AspComet.

The best place to learn is to look at the Chat sample which comes bundled with AspComet. That shows how to use the Dojo and JQuery JavaScript Comet frameworks to interface with AspComet, and has some good (but contrived) examples of subscribing to events and injecting alternate dependencies.


Olly said...

Neil - Great project and thanks for making your code available. I had a question: How could you send a file as a message into the comet server? My idea was to use a class a bit like your whisperer of badlanguage blocker to 'intercept' and deal with the file so that I could then cancel the return message and instead just queue out a message saying (for example) that a file had been submitted. I'm fine with the server side, but I'm a bit stumped by the client side and was hoping you might have some ideas? Olly

chris said...

Hi Neil,

Thanks for writing this post - it's a great help!

I'm keen to help out on the project - just need to get it working in my own project!

At the moment I've basically ported the Chat example into my ASP.NET MVC project.

I keep getting repeated 'System: Request on channel /meta/connect failed: No message' errors. Often (after 10 attempts or so) this is followed by a 'System: Request on channel /meta/connect failed: clientId not recognised' and then it all starts working!

If you have any ideas, I'd appreciate a pointer!?

Keep up the good work!

Olly said...

Neil -

One further question for you. I have this working locally (within Visual Studio) but when I deploy under IIS 7, the site appears but I get this error:

System: Request on channel /meta/handshake failed: No message
System: Handshake complete. Successful? false

Can you help me here. I've modified the web config as suggested with the comet handler.


Neil Mosafi said...

Guys, thanks for the feedback. I'm not quite too sure how to help from here. My main suggestion is to open the source code and attach a debugger. Set a breakpoint in CometHttpHandler.BeginProcessRequest and see what's going on.

Please let me know and I'll see if I can guide you further. Alternatively send me a project which reproduces the issue.

Neil Mosafi said...

Note there is a google group where you can post up questions too.

chris said...

Hi Neil,

Thanks - I'll check out the group and will get in amongst the sources a bit, too!

Olly - I think there's another place in IIS 7 where you have to register your HTTP handler. The 'modules' section of the 'system.webServer' node, I think.

- Chris

Anonymous said...

Neil, interesting sounding project. I am however having problems getting started. When I try to add "Setup.AspComet.WithTheDefaultServices()" to the application_start section of the global.asax file, I get an error saying "BC30451: Name 'Setup' is not declared."

Please advise.

Neil Mosafi said...

Hard to say.
Have you added a reference to AspComet.dll?

Rocky said...

I want to assign a client id to a session id. How can I solve that? I can't reach the session Id during sugscription.

Pietro said...

Neil thanks for your work, I'm looking into Comet & C right now for the first time, hope the question is not too stupid :)
I wonder if AspComet would work fine on a multi-instances environment. I host the app on AppHarbor (with 2+ web workers as they call them) but I think the situation would be similar on multi-instances on Azure.

Neil Mosafi said...

Thanks for the feedback. That would be possible as asp comet is quite pluggable. It might be quite difficulty though as it wasn't really designed upfront for that scenario.
It's funny you ask now as I just saw something on SignalR which relates to this I would suggest seeing what they are doing as it's providing a similar capability