![]() ![]() |
Send Instant Web or Window Messages through SocketPro HTTP Server Push UDAParts Contents
Browser-based instant message is a HTTP advance topic. It is particularly useful to get a team of people or web clients through browsers for cooperation in real-time fashion. There are many such examples like stock market data distribution, online web chat, online auction, online betting, online gaming, and so on. All these samples require data pushed from a HTTP server onto browsers in a very short time. However, it is considerably difficult to get web instant messages through HTTP protocol because the protocol is not designed with server push in mind at all. Therefore, you can not easily see real-time web notification with a publish/subscribe mode. Recently, we have upgraded our SocketPro with not only basic HTTP service but also HTTP server push support, and wish HTTP server push become much easier for you. This short article guides you to quickly develop a message push system through SocketPro, which enables you to send messages among web browsers, devices, window desktop and server applications. SocketPro Implementation to HTTP Server Push After having investigated many technologies, UDAParts selects long-time polling approach to implement SocketPro HTTP server push. With long polling, a client initially sends a request to a SocketPro server enabled with HTTP service in a similar way to a normal polling. However, the server wait until an available message or a long time timeout. Once a message or time out comes from a SocketPro server, a client will re-send a new request until the next message or timeout. By this way, a SocketPro server will almost always have an available waiting request so that it can immediately deliver a message from server to client in response to an event. Using this way is also able to make a callback from a SocketPro server to a client through HTTP service. SocketPro supports both the same-domain and cross-domain HTTP server pushes. SocketPro is relied on AJAX for the same-domain HTTP server push and JavaScript tag for cross-domain server push. For details, you can use the tutorial sample code and step through the code inside the file ujsonxml.js (UDAParts SocketPro adapter for JavaScript) using the debug tool FireBug in the browser FireFox. On server side, the core library (usktpror.dll) has implemented a set of HTTP functions for HTTP server push. UDAParts has successfully integrated HTTP server push with our original built-in chat service so that you can send a message from a desktop or device application to a web application, and vice versa. We'll use some old sample applications to show you this integration. Pre-requirements and Preparation As described in the previous tutorial, you should have a basic knowledge about HTTP although you are not required to know all of HTTP details. If you don't know HTTP, please use Google or Yahoo search engine to search for key words like 'HTTP protocol tutorial'. This tutorial (chat) tells you how to use SocketPro HTTP server push. After compiling one of the tutorial samples inside the directories ..\udaparts\socketpro\tutorial\csharp\chat\server\ and ..\udaparts\socketpro\tutorial\vb.net\chat\server\ for C# and vb.net respectively, you need to copy the files inside the directory ..\udaparts\socketpro\tutorial\HTTPChatSampleFiles into this directory of your compiled exe application. These files are used for testing. Note that there is no client sample application provided for this HTTP tutorial, but the client test application is your browser, FireFox, IE, Safari, Opera, Flock or else. We would like you to use FireFox plus free plug-in FireBug. FireBug is the best free web browser debug tool available at this writing time. You can use the tool to debug HTTP transaction between browser and sample SocketPro on client side so that you can see how UDAParts SocketPro adapter (ujsonxml.js) works. It may require you to have some basic knowledge about JavaScript. This tutorial requires you a simple understanding about JSON. In case you don't know JSON, please go to the site www.json.org. By default, SocketPro adapter for JavaScript uses JSON for serialization. The site contains all of free open source JSON parsers for all types of development language. In this tutorial, we'll use Newtonsoft json parser. You can use your own parser or others to replace the tutorial json parser. After successfully compiling the tutorial and running the SocketPro server, you can play it from a browser. See the below sample picture. You should put debug break points everywhere on both JavaScript and .NET server codes and see how codes execute.
The First Step to SocketPro HTTP/Binary Push To enable SocketPro HTTP/binary push is to make a few calls for configuration so that SocketPro knows how to handle various chat requests. First of all, you need to create a set of chat groups. Within SocketPro, you can push a message onto one or multiple groups of clients. To do so, create a few chat groups using the below code snippet. bool bSuc;bSuc = AddAChatGroup(1, "Group for SOne");bSuc = AddAChatGroup(2, "DB Service");bSuc = AddAChatGroup(4, "Management Department");bSuc = AddAChatGroup(8, "IT Department");bSuc = AddAChatGroup(16, "Sales Department");The above code snippet creates five chat groups with group identification numbers, 1, 2, 4, 8, and 16. You can create 32 groups at most. The next two calls is to tell SocketPro how to deal with the situation when a client joins or exits one or more chat groups. See the below code. //Other clients will be notified when a new client join the groups 1, 4, 8
GroupsNotifiedWhenEntering = 13;
GroupsNotifiedWhenExiting = 13; An existing client will get a message whenever another client joins or exists the three groups 1, 4, or 8. If you set the above values to -1, an existing client will get a message whenever another client joins or exists. After you complete the above calls, it is ready for SocketPro to handle various chat calls from either client or server side without using HTTP or independent chat service. Note that all of other services internally inherits the chat service. You can think the chat service as a base class. However, we'll use both the chat and HTTP services in this tutorial so that we have to enable them in SocketPro. Note that the two services are SocketPro built-in services. To enable them, you'd make below calls: private CSocketProService<CHttpPeer> m_HttpSvs = new CSocketProService<CHttpPeer>(); private CNotificationService m_ChatSvs = new CNotificationService();
// ...... //sidHTTP = 266 bSuc = m_HttpSvs.AddMe((int)USOCKETLib.tagServiceID.sidHTTP, 0, tagThreadApartment.taNone); //enable HTTP service bSuc = m_HttpSvs.AddSlowRequest((short)USOCKETLib.tagHttpRequestID.idGet);
bSuc = m_HttpSvs.AddSlowRequest((short)USOCKETLib.tagHttpRequestID.idPost); //sidChat = 257 bSuc = m_ChatSvs.AddMe((int)USOCKETLib.tagServiceID.sidChat, 0, tagThreadApartment.taNone); //enable independent chat service In the above we create two instances of services first, one for HTTP service and the other for independent chat service. Afterwards, we enable HTTP service, and use a worker thread in pool to process HTTP GET and POST commands. Finally, we make a call to enable independent chat service. Coding for HTTP Push on Server Side HTTP push code on server side: You don't have to make any code for independent chat service, but you do need to deal with HTTP chat-related methods on both web browser and server sides. We are going to do server side first. case "/UCHAT":{ CRet ret; string strMethod = null;SetResponseHeader( "Connection", "close");//UJS_DATA comes from UDAParts JavaScript SocketPro adapter for json and XML RPC library (ujsonxml.js) string str = this.Params["UJS_DATA"]; try { JavaScriptObject json; JavaScriptObject parameters;json = ( JavaScriptObject)JavaScriptConvert.DeserializeObject(str);strMethod = ( string)(json["method"]);parameters = ( JavaScriptObject)(json["params"]);ret = HandleJSCall(strMethod, parameters); } catch (Exception err){ ret = new CRet();ret.method = strMethod; ret.ret = 0;
} { int result = 0; string strJSCallback = null; //UJS_CB comes from UDAParts JavaScript SocketPro adapter for json and XML RPC library (ujsonxml.js) if (Params.ContainsKey("UJS_CB")) //cross-domain callbackstrJSCallback = Params[ "UJS_CB"];str = JavaScriptConvert.SerializeObject(ret);m_UQueue.SetSize(0); if (strJSCallback != null)m_UQueue.Push(enc.GetBytes(strJSCallback)); m_UQueue.Push(enc.GetBytes(str)); if (strJSCallback != null)m_UQueue.Push(enc.GetBytes( ");"));result = SendReturnData(sRequestID, m_UQueue); } } break;
private CRet HandleJSCall(string strRequest, JavaScriptObject jsoParameters){ CRet ret = new CRet();ret.method = strRequest; string strAgent = "";//UJS_CB comes from UDAParts JavaScript SocketPro adapter for json and XML RPC library (ujsonxml.js) bool bCrossSite = Params.ContainsKey("UJS_CB"); if (bCrossSite && Headers.ContainsKey("User-Agent")) strAgent = Headers[ "User-Agent"].ToLower(); bool bSelfMessage = (strAgent.IndexOf("firefox") != -1 || strAgent.IndexOf("opera") != -1);switch (strRequest) { case "enter":{ object obj = jsoParameters["groups"]; long nGroups = (long)obj; string strUserId = (string)(jsoParameters["userId"]);//it returns a unique push session id ret.ret = Enter(( int)nGroups, strUserId, 100000); //100 seconds for lease time} break; case "exit":{ string strSession = (string)(jsoParameters["chatId"]);ret.ret = Exit(strSession); } break; case "speak":{ string strSession = (string)(jsoParameters["chatId"]); string strGroups = (string)(jsoParameters["groups"]); object msg = jsoParameters["message"]; if (bSelfMessage) //cross-domain, and browsers firefox, flock and operaSendSelfMessage(strSession); ret.ret = Speak(strSession, msg, int.Parse(strGroups));} break; case "sendUserMessage":{ string strSession = (string)(jsoParameters["chatId"]); string strUID = (string)(jsoParameters["userId"]); object msg = jsoParameters["message"]; if (bSelfMessage) //cross-domain, and browsers firefox, flock and operaSendSelfMessage(strSession); ret.ret = SendUserMessage(strSession, strUID, msg); } break; case "listen":{ bool bSuc; string strSession = (string)(jsoParameters["chatId"]); string strJSCallback = null; if (bCrossSite)strJSCallback = Params[ "UJS_CB"];//60 seconds for timeout; and 0 for polling bSuc = HTTPSubscribe(strSession, 60000, strJSCallback); ret.ret = bSuc; if (!bSuc){ //if failed, we need to tell the browser Encoding enc = System.Text.UTF8Encoding.UTF8; string str = JavaScriptConvert.SerializeObject(ret);m_UQueue.SetSize(0); if (strJSCallback != null)m_UQueue.Push(enc.GetBytes(strJSCallback)); m_UQueue.Push(enc.GetBytes(str)); if (strJSCallback != null)m_UQueue.Push(enc.GetBytes( ");")); int result = SendReturnData((short)USOCKETLib.tagHttpRequestID.idGet, m_UQueue);result = 0; } } break; default: break;} return ret;} After looking through the above two snippets of code, you may get lost if you don't have any background with AJAX and JavaScript tag remoting on browser side. In short, the above two pieces of code are able to handle all of requests by either AJAX or JavaScript tag for all of major browsers. In regards to JavaScript tag requests, browser implementations are different. Therefore, we must process them differently, especially if there are two requests in pending. To understand the above HTTP transaction between browser and SocketPro server, you'd start the browser FireFox, and use the debug tool FireBug as shown in the below picture.
Referring from the above first piece of code, we de-serialize a UJS_DATA string into json object, which contains a request name and an array of parameters. Afterwards, we pass the two data into the function HandleJSCall (see the above 2nd piece of data). The function returns a simple structure. If the method does not have the name listen, we'll return HTTP response immediately after serializing the structure into a json string. Two key HTTP methods: There are two key methods you have to call for HTTP push. The first one is the method Enter, which returns a unique chat (or push) session id. Later, We use the id to make various notification or other method calls from the same machine. Note that the chat id is not valid on different machines. Calling this method with a specified lease time is to join one or a set of chat groups as we do for chat service. If there is no message for a chat session more than lease time, the chat session will be removed from SocketPro server automatically. The second call is the method HTTPSubscribe. When calling this method, you subscribe messages from either a web browser or a normal desktop application through chat service. It is important to point out that you can not use your code to return any data back to a browser if the method returns successfully. SocketPro will return either an available message or a timeout message for you. However, you must return a message if the method HTTPScribe returns false as shown in the above second piece of code. Other chat methods supported for HTTP: At this writing, HTTP service fully supports three other chat methods, Exit, Speak and SendUserMessage in addition to the methods Enter and HTTPSubscribe as shown in this tutorial sample. Whenever you call them, you need to pass in a unique chat session id from the method Enter. Also, you need to return an HTTP response from your code. See the above first piece of code. You are able to call other chat service methods to notify messages onto non-HTTP client as usual, but you can not use them to notify an HTTP client. They are not fully supported. One hack for cross-domain HTTP push on FireFox, Flock and Opera: As you may know, AJAX does not support cross-domain HTTP requests from a browser for the sake of security at this time, although there is a strong proposal to add support of cross-domain request through AJAX. The real situation is that there is no browser fully supporting cross-domain request from AJAX at this writing time yet. Therefore, we use JavaScript tag for cross-domain HTTP push on a browser instead. It works across all of browsers with some pitfalls for some browsers. It seems to us that FireFox, Flock and Opera do not allow two or more cross-domain requests in pending at any time, possibly for the sake of security. The others browsers look fine with two cross-domain requests in pending through JavaScript tag. To get HTTP push for cross-domain for these browsers, we need an extra call SendSelfMessage first if there are two requests in pending. See the below picture and code snippet.
private bool SendSelfMessage(string strChatId){ string strUserID = null; string strIpAddr = null; int nLeaseTimeOut = 0; int nMessages = 0; int nGroupIds = 0; int nTimeout = 0; if(!CSocketProServer.GetHTTPChatContext(strChatId, ref strUserID, ref strIpAddr, ref nLeaseTimeOut, ref nGroupIds, ref nTimeout, ref nMessages)) return false; string strSelfMessage = "CrossSite Self-activation Message for FireFox, Flock and Opera!"; return SendUserMessage(strChatId, strUserID, strSelfMessage);} The above function simply send its self a message so that a previous listening HTTP request is finished for the next cycle. By this way, the coming HTTP request can be processed and its HTTP response can be returned. Again, this is an work around for browsers FireFox, Flock and Opera. HTTP callback: We like callback. It does make our application more elegant and smarter. However, it is considerably difficult to get callback to work for HTTP. As you may know, many popular communication frameworks can not have such a basic feature for HTTP! It is really simple to make a callback from SocketPro server to a browser or other applications using HTTP protocol. To do so, simply use the above code snippet, and send a client self a message exactly like the above piece of code. Note that you can pass 0 for no chat group to the method Enter. It is purely for callback. SocketPro Adapter for Web Browser To make HTTP push easier on web browser side, we provide an adapter for web browsers through JavaScript. At this time, it well supports JSON and simple XML remoting procedure calls through HTTP protocol. By default, it uses json format. It supports both same- and cross-domain requests. Now, it is time to have a close look at the sample html file httppush.htm, and see how UDAParts uses two HTTP requests to complete HTTP push on client side. Configuration for pointing to HTTP server for processing: The first step on web browser side is to point to a HTTP server for processing requests by configuring a JavaScript object. See the below code from the file httppush.htm. //set an address pointer for processing requests UHTTP.defaultConfig.pathname = '/UCHAT';UHTTP.defaultConfig.protocol = 'http'; //https, or otherUHTTP.defaultConfig.port = 20901; //80, 443 or otherUHTTP.defaultConfig.hostname = 'localhost'; //'www.somedomain.com', '111.222.212.121', or 'some_host_name';Look at a typical HTTP request: SocketPro adapter for web browsers makes your HTTP requests simpler. Here is a sample code. function myCallback(req, data){ var ctrl = document.getElementById('txtMsgIn'); var obj = data; if (typeof data == 'string')obj = JSON.parse(data); switch(obj.method){ case 'exit':document.getElementById( "btnSendUserMessage").disabled = 'disabled';document.getElementById( "btnSpeak").disabled = 'disabled';chatId = '';ctrl.value = 'exit: ' + obj.ret;document.getElementById( "btnSubscribe").disabled = ''; break; case 'enter':chatId = obj.ret; if (chatId) {document.getElementById( "btnSendUserMessage").disabled = '';document.getElementById( "btnSpeak").disabled = '';} ctrl.value = 'enter: ' + obj.ret;listen(); break; case 'speak':ctrl.value = 'speak: ' + obj.ret; break; case 'sendUserMessage':ctrl.value = 'sendUserMessage: ' + obj.ret; break; default: break;} obj = null;}
function btnSpeak_onclick() { var req = UHTTP.createRequest( 'speak', //required, request name0); //optional, 0, false (default) json, 1 -- xml var ctrl = document.getElementById('txtGroups');//add the 1st parameter and its value req.add( 'groups', ctrl.value);
ctrl = document.getElementById( 'txtMsgOut');//add the 2nd parameter and its value req.add( "message", ctrl.value);//add the 3rd parameter and its value req.add( "chatId", chatId);
req.invoke( myCallback, //required, a callback function to handle returning data1, //optional, 0, false (default) POST, 1 -- GET0 //optional, 0, false (default) async, 1 -- sync); } When you click the button Speak, it causes the above second piece of code called. Inside the function, we create a HTTP request for the method speak. Afterwards, we add three input parameters for groups, message and chatId, respectively. Keep in mind that the chatId is chat session id from the method Enter on server side. At the end, we send the request by calling its method invoke with setting a callback. We process HTTP response using the callback as shown in the above first piece of code. All of the requests are processed under the same pattern. As you can see, we normally process HTTP requests asynchronously from a browser. In some cases, you are able to process requests synchronously through AJAX call for the same-domain only, but not for cross-domain. SocketPro adapter processes in the same way for both same- and cross-domain requests. If finding a request is same-domain one, it will use AJAX internally. Otherwise, it will select JavaScript tag to complete a cross-domain request. The second request for incoming message: As described previously, SocketPro uses the second long-time request for messages from other clients. To do so, simply uses the below code. function onMessage(req, messages) { var ctrl = document.getElementById('txtMsgIn'); var obj = messages; if (typeof messages == 'string'){ if(messages.length == 0) return;obj = JSON.parse(messages); } else if(typeof messages == 'object'){ var str = JSON.stringify(obj);messages = str; } if(!obj) return;ctrl.value = messages; if(chatId.length == 0) return; listen(); }
function listen() { var req = UHTTP.createRequest('listen');req.add( "chatId", chatId);req.invoke(onMessage, 1); } After having glanced the above two pieces of code, it is really simple. All you need to do is to create a new request listen with a chat session id, and send it by calling the method invoke with a new callback onMessage. When a message comes from itself or others, the callback will be invoked. Again, SocketPro adapter hides cross-domain callback for you. Note that you need to call the method listen again for the next cycle right after one or message messages come. Explicitly turn off HTTP server push from browser: At the end, it is better to explicitly turn off HTTP server push by calling the method exit as shown in the below. function btnUnsubscribe_onclick() { var req = UHTTP.createRequest( 'exit',0); //optional, 0, false (default) json, 1 -- xml//add the 1st parameter and its value req.add( "chatId", chatId);
req.invoke( myCallback, //required, a callback function to handle returning data1, //optional, 0, false (default) POST, 1 -- GET0 //optional, 0, false (default) async, 1 -- sync);
} function onMyUnload() //close socket connection{ if(chatId.length > 0){ //tell the server to unsubscribe listeningbtnUnsubscribe_onclick(); } } <body onunload="return onMyUnload()"> This step is not required, but highly preferred for fast cleaning chat session contexts on SocketPro server side and fast notification of exit onto other clients. SocketPro adapter HTTP request object: See the below picture for SocketPro HTTP request object.
As shown from the picture, SocketPro adapter HTTP request object clearly has two properties, method and config. The method abort is used for aborting for a HTTP response. The method add, as shown previously, is used for constructing an object dynamically. In case you like to re-use an existing request object, you can call the method clear to remove previous parameters and their values. The method getCallIndex is used to retrieve a unique integer call index. The method getCallback is used to retrieve callback set previously. The method getScript is used to retrieve either an AJAX object for same-domain or a JavaScript tag object for cross-domain. The method invoke is used to send a real HTTP request onto a SocketPro server enabled with HTTP service. See the below code from the file ujsonxml.js. /* invoke a request from browser to a web server.
1. cb -- required callback handler. 2. isGet -- default to POST. 1 or true for GET 3. sync -- default to asynchronous processing, and 1 or true for synchronous processing. */ me.invoke = function(cb, isGet, sync) { if (!me.method && typeof (me.method) != 'string') throw new Error(500, "HTTP: request name not specified properly!"); if (typeof (cb) != 'function') throw new Error(500, "HTTP: callback function not specified properly!"); if (!me.isCompleted()) throw new Error(500, "HTTP: previous request not completed yet!");cIndex = ++callIndex; callback = cb; doCall(isGet, sync); return callIndex;}; The method isCompleted is used to check if a request is processed and its HTTP response has come. The method isCrossSite is used to check if a request is cross-domain one. All of these methods should not be too difficult for you to understand and use. We have tested libraries against multiple browsers and should work with all of major browsers on cross platforms. Integration HTTP Server Push with Devices and Normal Desktop/Server Applications We have integrated HTTP server push with device and normal desktop/server applications. See the below picture for device from the project DevTest inside the directory of ..\udaparts\SocketPro\samples\device\samplesCE\DevTest, which is written from Visual C++ 4 for smart devices.
It is also simple to communicate with other desktop or server application using push through SocketPro chat service. See the below sample application which was written from old VB6 long long time ago, which can be found inside the directory of ..\udaparts\SocketPro\samples\others\client\vb6\chatTest.
You can use the tutorial two to test integration with HTTP server push. Separating HTTP
Server from Comet Server
|