donderdag 12 juli 2012

Indy 10 Websockets, RemObjects and realtime duplex events!

In a previous blog, I demonstrated a proof of concept of Websockets for RemObjects. The downside of the used implementation is that it is "websocket only", whereas websockets actually starts as a http connection. So it should be possible to have a webserver (in Delphi ofcourse :) ) which supports both normal http and websockets.
I also played with websockets for duplex communication (bi-directional), so a server can send something to a client at any time the server wants, without some kind polling needed by the client. I wanted to use this for RemObjects events too, which use normally polling (the normal tcp RO channels and the javascript http channel; RO also has "super" channels which are duplex too but also much "heavier" and complicated). Websockets are designed for "real-time" duplex communication, so it would be very nice to use it with RemObjects!

Result

Well, it took some time, but I made it! Because Indy has already a nice http server + client implementation (that is used by RO too) and because I could not find a good http implementation for synapse, I decided to make it with Indy 10. Unfortunately there was no code yet of websockets for Indy 10, so I took the official specification (protocol version 13) and started "hacking" :).
In short the result is:
  • an Indy IOHandler (TIdIOHandlerWebsocket), which reads and writes websocket frames (used by both server and client)
  • a Indy http + ws client (TIdHTTPWebsocketClient), which has a "TryUpgradeToWebsocket" function that can be used to try to upgrade to a websocket connection when the webserver supports it.
  • a RemObjects http + websocket channel (TROIndyHTTPWebsocketChannel), which uses the above http + ws channel.
  • a RemObjects http + websocket server (TROIndyHTTPWebsocketServer) (unfortunately "TROIndyHTTPServer" creates directly a "TROIdHTTPServer" without a virtual function, so I could not make a special Indy http + ws server), which support both http and websocket clients (!).
  • support for duplex RO events, both in Delphi and in Javascript(!)

Implementation

The IOHandler is not very complicated: it reads the websocket header and concatenates several frames (if it is fragmented) to a single message. And it writes the ws header and the raw data after it. One note: clients must send all data with a mask. This is needed because otherwise the http cache can be poisoned by a hacker via a special composed websocket message (all ws data is send via a http port), or at least something like this is stated in the specification :).

The Indy http client contains the logic for a special upgrade request, and the handling and checks for the response of the server. The upgrade request is a set of extra http headers, together with a SHA1 key. In theory the websocket connection can support some extensions (like gzip compression) and subprotocols (like SIP for VOIP) but this is not implemented yet.

The RemObjects channel tries to upgrade (once) to a websocket connection, but can also operate as an "old" http channel. The special addition in this channel is a lightweight background read thread. This in contrast to the RO super tcp channel, which has a dedicated read thread per connection. Because my customer has Windows RO services with connection/channel pools (multi-threaded, to support multiple concurrent client calls), I did not want to double the already high count of threads! However some kind of continuous reading of the client connections is needed because of the duplex character of websockets, like sending ping/pong frames by the server, sending/pushing data from server to a client at any time, etc. 
After some research I made a single background thread, which uses the tcp "select" winsock api call, which can wait for incoming data for up to 64 connection! This thread only handles unexpected data from the server (ping + close frames and RO events), normal RO dispatching of calls is done by the caller thread itself. This is enough for my customer because of the (probably) low traffic of RO events, but additional or dedicated threads can be made of course in case of higher loads in the future. 
A nice trick I have to mention is: I use dummy connect to 0.0.0.0 to stop the "select" wait in case a new connection is added or when the wait thread has to terminate. I don't know if there is a better way, but at least it works :). By the way: I could not use "WSAAsyncSelect" because Indy needs blocking sockets.

The Indy + RemObjects http server contains both the logic for (possible) upgrade requests and some extra RO specific handling. It has a seperate "execute" function for websocket contexts, otherwise the normal Indy http handling would give errors :).  

To support duplex RemObjects events (without polling!), I had to add a special message number at the end of each message. Positive numbers mean normal RO RPC calls, negative numbers mean RO events send by the server to the client. I did not "invented" this myself, but something similar uses RO in their super tcp channels too :). I also made a small javascript extension for RemObjects, to be able to use websockets channels and duplex events. Note: only binary messages can be used for RO events!

Quality

I already spent some fair amount of time to make it stable (like killing client + server while sending and receiving etc), so it should be stable enough to use. We are busy with a pilot project to see how this stuff in combination with Smart Mobile Studio will work (backend RO services, frontend html5 clients) and if it can be used in real-life production situations. Anyway, my customer allowed me to make it open source (hopefully it will be integrated in Indy 10 itself), so please test it, fix some bugs, add new features, etc!  


Demo

All right, enough text and theory, time for a demo :). For the demo I made a simple RO function, which will send a lot of "progress" events (during the call!) back to the client each millisecond (to stress test the communication :) ). I used the progressbar example of Lennart to demonstrate the high update speed of websockets in javascript using SmartMS. This works in both the html and the Delphi client:


For the Delphi client, you need to connect to the server by clicking for example the "Sum" button. If you have done this for both Delphi clients, you can press the "Progress" button in the web page (served by the same server!). Then you will see all 3 progress bars running at the same speed :).

Note: even Android 4.0 does not support websockets! You need Opera Mobile (and enable websockets) although my demo still did not work with it (haven't time yet to debug it). Or you need Chrome for Android (Android 4.0 and higher) and then my demo works fine. Probably I have to use long polling over http if I want to use it with older Android mobile phones... :(

Note 2: an RO event can also be send at any other time, for example when an other user logs in etc. To test this I added the button "Send event" on the server form (note: only for this demo :), normally you will make a Windows service for the server instead of a GUI app). Press the button and in both clients a popup will be shown:


(note: I only added a handler for this event in Delphi but it also works in javascript)


Download source and binaries

The source code of the Indy 10 websocket implementation, as well for the RemObjects extensions for Delphi and Javascript can be downloaded here (thanks to my customer: www.rbk.nl!). The source code of the demo can be downloaded here.


Conclusion

With this websocket for Indy 10 implementation, you have to use only 1 port for "everything": normal http file handling (via TROHTTPFileDispatcher), RemObjects communication via http (for webclients with no websocket support like Android :( ), tcp-like low overhead communication via websockets and realtime RO events! And you can of course also use it for normal communication between Delphi RO servers and Delphi RO clients, it is not only for web clients.