Writing a Delphi WebSocket server and Smart Mobile Client in 15 minutes
Websocket is all the rage these days. In essence its just an extra layer built on top of ordinary HTTP (available as a snap-in for IIS and “mod” for apache). But Delphi developers like to build their solutions from the ground up! So what could possibly be better than to roll your own?
Indy to the rescue
Think of client-server programming and Delphi and chances are “indy” will be the first word to pop into your mind. It’s been there for ages, it’s rock solid, it supports every known RFC known to mankind – and it’s tried and tested by time. It may not provide the same speed as Microsoft Internet Explorer or Apache, but there are hundreds (if not thousands) of products out there built with the Indy library, so it’s pretty damn awesome!
But what about websocket? As far as standards go it’s the new kid on the block – invented more or less purely for secure HTML5/JavaScript development. Does Indy have that yet? Well, no. I’m sure it will be included at one point in a future update, but thankfully Indy is easy to extend and mold due to it’s purely object oriented nature.
A while back mr. Andre Mucche took the time to implement just that, extending an ordinary Indy HTTP server with the required plumbing – turning a bog standard, multi-threaded, multi-context HTTP server into a websocket nerdvana.
Why is this important?
If all you do is write old-school stuff in Delphi then you probably don’t need it, but if you want to keep up with the way technology is moving – then WebSockets is bound (pun intended) to cross your path sooner or later. If you havent already been asked by your customers, it’s only a matter of time before you are approached with the question “Can we poll data from our Delphi solution and use that on our website from JavaScript?”.
Well, there are many ways to deal with getting data from a Delphi centric solution (read: server) onto your website. You can spend weeks and months writing the JavaScript yourself, you can publish a few DataSnap API’s — or go for RemObjects SDK which IMHO is a much better alternative to DataSnap.
But Smart Mobile Studio offers an alternative route. The benefits should be fairly obvious:
- You write object pascal (Delphi / FreePascal)
- You don’t have to learn much JavaScript
- All the low-level stuff is already wrapped and ready
- Smart Mobile supports both RemObjects, DataSnap and Websocket (and a few more)
So how hard is it to create a Delphi websocket server and a Smart Mobile Studio client?
The Delphi side
Right, first start by creating a folder for your project. In my example I just named it “WebSocket”. Then create a fresh Delphi project (VCL) and save that into the folder as “SocketServer.dpr”.
Next, download Andre’s WebSocket extension units, these can be found here: https://github.com/andremussche/DelphiWebsockets. It’s Github so just download the zip archive. Once downloaded, unzip the files into your project folder. Your folder should look something like this by now:
With the files in place, add all the units to your project inside Delphi (including the superobject files). You dont really have to do this, you can unzip the files wherever you like — but for this quick demonstration I just stuff it all into the same project to avoid setting a path (it’s late, what can I say). Your Delphi project should now look like this:
With that in place, let’s add a TMemo component, a couple of buttons to control the server (start and stop) and isolate that in TActions. If you havent used actions before then please read up on that before you continue. It’s super simple and one of Delphi’s biggest strength’s over other RAD platforms out there. My form looks like this (just slap-dash 2 second stuff):
Now let’s write some code!
unit mainform; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdComponent, IdContext, IdCustomHTTPServer, IdServerWebsocketContext, IdServerSocketIOHandling, IdWebsocketServer, Vcl.StdCtrls, System.Actions, Vcl.ActnList; type TForm1 = class(TForm) Memo1: TMemo; Button1: TButton; Button2: TButton; ActionList1: TActionList; acStart: TAction; acStop: TAction; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure acStartExecute(Sender: TObject); procedure acStopExecute(Sender: TObject); procedure acStartUpdate(Sender: TObject); procedure acStopUpdate(Sender: TObject); private { Private declarations } FServer: TIdWebsocketServer; procedure HandleServerStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); procedure HandleTextMessage(const AContext: TIdServerWSContext; const aText: string); procedure HandleCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.acStartExecute(Sender: TObject); begin FServer.Active:=True; end; procedure TForm1.acStartUpdate(Sender: TObject); begin TAction(sender).Enabled := not FServer.Active; end; procedure TForm1.acStopExecute(Sender: TObject); begin FServer.Active := false; end; procedure TForm1.acStopUpdate(Sender: TObject); begin TAction(sender).Enabled := FServer.Active; end; procedure TForm1.FormCreate(Sender: TObject); begin FServer := TIdWebsocketServer.Create(NIL); FServer.OnStatus := HandleServerStatus; FServer.OnMessageText := HandleTextMessage; FServer.OnCommandGet := HandleCommandGet; FServer.KeepAlive := True; FServer.DefaultPort := 8080; end; procedure TForm1.FormDestroy(Sender: TObject); begin FServer.free; end; procedure TForm1.HandleCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin aResponseInfo.ContentText:='Hello world'; end; procedure TForm1.HandleTextMessage(const AContext: TIdServerWSContext; const aText: string); begin memo1.Lines.Add(aText); end; procedure TForm1.HandleServerStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); begin memo1.Lines.Add(aStatusText); end; end.
That’s basically it! The most bare-bone WebSocket server you will ever see. It just accepts a connection and dumps whatever text a client writes to the memo control on the form.
Right, now let’s look at the Smart Mobile Studio side of things.
The HTML5 Client
Fire up Smart Mobile Studio (im using the latest beta here) and create a new project. Remember to save the project before you start coding.
We will be adding a single button for connecting to the websocket server, and then a textbox for message input — and finally a “send” button to ship the next to the server.
With some components in place we move on to the WebSocket client code, which under the Smart Mobile RTL is a piece of cake:
unit Form1; interface uses SmartCL.inet, SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button, SmartCL.Controls.Memo, SmartCL.Controls.EditBox; type TForm1 = class(TW3Form) procedure W3Button2Click(Sender: TObject); procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} FSocket: TW3WebSocket; protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end; implementation { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin try FSocket.Connect('ws://192.168.10.106:8080',[]); except on e: exception do showmessage(e.message); end; end; procedure TForm1.W3Button2Click(Sender: TObject); var mText:String; begin mText :=trim(w3Editbox1.text); if mtext.length > 0 then FSocket.Write(mText); w3Editbox1.text := ''; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components FSocket := TW3WebSocket.Create; FSocket.OnOpen := procedure (sender:TW3WebSocket) begin w3memo1.text := w3memo1.text + "WebSocket open" + #13; end; end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.
The final result
Now fire up your Delphi project, click the “start” button to initialize the server (note: the firewall may ask you to allow the server to use the port, remember to check “local networks” and just click “ok”). Your Delphi server should now run at port 8080 — use your favorite browser to check that it works. It should return “hello world” (see “HandleCommandGet” event handler in the Delphi code).
Next, fire up your Smart Mobile Studio project. Hit the “Connect” button, type something in the text-field and click “send” to ship it off to the server. Now watch the memo on the server and voila — you have just written your first websocket client/server system in less than 15 minutes!
Note: Remember to use your local IP. The IP listed in the SMS example above is just a local address on my local network. If you are running Delphi and SMS on the same machine, just use 127.0.0.1 and bob’s your uncle.
Enjoy!
Leave a comment Cancel reply
Recent
The vatican vault
- September 2023
- August 2023
- March 2023
- February 2023
- December 2022
- October 2022
- January 2022
- October 2021
- March 2021
- November 2020
- September 2020
- July 2020
- June 2020
- April 2020
- March 2020
- February 2020
- January 2020
- November 2019
- October 2019
- September 2019
- August 2019
- July 2019
- June 2019
- May 2019
- April 2019
- March 2019
- February 2019
- January 2019
- December 2018
- November 2018
- October 2018
- September 2018
- August 2018
- July 2018
- June 2018
- May 2018
- April 2018
- March 2018
- February 2018
- January 2018
- December 2017
- November 2017
- October 2017
- August 2017
- July 2017
- June 2017
- May 2017
- April 2017
- March 2017
- February 2017
- January 2017
- December 2016
- November 2016
- October 2016
- September 2016
- August 2016
- July 2016
- June 2016
- May 2016
- April 2016
- March 2016
- January 2016
- December 2015
- November 2015
- October 2015
- September 2015
- August 2015
- June 2015
- May 2015
- April 2015
- March 2015
- February 2015
- January 2015
- December 2014
- November 2014
- October 2014
- September 2014
- August 2014
- July 2014
- June 2014
- May 2014
- April 2014
- March 2014
- February 2014
- January 2014
- December 2013
- November 2013
- October 2013
- September 2013
- August 2013
- July 2013
- June 2013
- May 2013
- February 2013
- August 2012
- June 2012
- May 2012
- April 2012
One step further of using WebSockets is to include them as SOA asynchronous callbacks.
This is what our Open Source mORMot framework offers, also with SmartMobileStudio support. See http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_149
Hi, I’ve created a simple websocket application using mORMot and D7 that send/receive binary data (upload images to server, broadcasting videos to clients) to HTML5 clients.
I think the current Smart Mobile Studio websocket implementation, you can not receive binary data for instance.
On the client side, we expect to use the attribute binaryType, f.i.
FSocket.binaryType = ‘arraybuffer’;
Setting the binaryType attribute of WebSocket right after connection initialization it allows both text and binary data to be transfered. I think the current Smart implementation does not include this property.
It also would be nice if we have some smart pascal similar like this onMessage event, to handle both blob and texts:
fsocket.onmessage := lambda(event)
begin
if (event.data instanceof Blob)
begin
end;
if (event.data instanceof ArrayBuffer)
begin
var myBlob := new Blob([event.data]);
end else
if (typeof event.data = “string”)
begin
end;
end;
Ops! You are right! Consider it fixed!
Now implemented as such:
TWebSocketMessageData = Record
mdType: TWebSocketMessageType;
mdBinary: TStream;
mdText: String;
end;
TWebSocketMessageEvent = Procedure (Sender:TW3WebSocket;Message:TWebSocketMessageData);
I have now updated the inet unit with full support for blob, arraybuffer and text messages. It defaults to arraybuffer and you get it as a TMemoryStream in the event-handler. It will be pushed to the beta repo later 🙂
By the way, websockets itself are very basic, but with socket.io you can easily create some cool event driven async stuff! https://github.com/andremussche/DelphiWebsockets/blob/master/README.md
Hi, there’s an wrapper (smart pascal) around socket.io
https://drive.google.com/folderview?id=0B5AcQp14Q5FaX2xiSGV5ejJHVHM
Hi. You can see realisation of the real HTML5 client with help of the pure Indy library here:
http://www.makhaon.com/index.php?lng=en&p=products&id=viewer
We just made some chenges in the library and it works fine for us.
I see that Andre has stopped supporting his library “Unfortunately I don’t have time to support this project anymore. Also the websocket protocol has changed in the meantime, so it won’t work with browser and other modern implementations. ” If that is the case what suggested library to use websockets in Delphi?
Synapse + bauglidir is solid