Wednesday, April 12, 2006

[java, ajax] polling in DWR 2.0

Feature ที่น่าสนใจใน DWR 2.0 ก็คือ Reverse Ajax
ที่ทำให้ฝั่ง java server สามารถ call ไปยัง javascript ที่อยู่ใน browser ได้
(จริงๆแล้วมันคือ polling นั่นแหล่ะ แต่ api มันออกแบบมาให้ดูเหมือน
เป็นการ call function โดยตรง)

ลองแกะ demo ของเขาดูกัน
เริ่มด้วย browser ที่ต้องการใช้ feature นี้ต้องสั่งดังนี้
DWREngine.setPolling(true);

มาดู input form ในฝั่ง browser ก่อน
<p>
Your Message:
<input id="text" onkeypress="DWRUtil.onReturn(event, sendMessage)"/>
<input type="button" value="Send" onclick="sendMessage()"/>
</p>

<p>Messages:</p>
<div id="chatlog"></div>

จะเห็นว่าถ้ามีการกด enter หรือ click button เมื่อไร
ก็จะมีการเรียกใช้ function ที่ชื่อ sendMessage

สิ่งที่ sendMessage ทำ ก็คือ เรียกใช้ method addMessage
(สำหรับคนที่ไม่คุ้นกับ DWR, Chat object เป็น object ที่อยู่บน server,
DWR ช่วยทำให้มันเหมือนกับอยู่บน space เดียวกัน)
function sendMessage() {
var text = DWRUtil.getValue("text");
DWRUtil.setValue("text", "");
Chat.addMessage(text);
}


ตามไปดู method addMessage ที่อยู่บนฝั่ง server บ้าง
เริ่มด้วยการ validate message ก่อน
จากนั้นก็ add เข้า LinkList ที่ทำหน้าที่ maintain message history
public class Chat
{

public void addMessage(String text)
{

if (text != null && text.trim().length() > 0)
{
messages.addFirst(new Message(text));
while (messages.size() > 10)
{
messages.removeLast();
}
}


ขั้นถัดไปก็คือการ send message ไป update ที่ browser
หัวใจหลักของขั้นนี้ ก็คือ Class WebContext
Object WebContext จะคอย maintain ว่ามี browser ไหน กำลัง online อยู่บ้าง,
และกำลังใช้ page ไหนอยู่
(ความหมายของ online ก็คือ browser ยังมีการ polling มาที่ server เป็นระยะๆ)
        WebContext wctx = WebContextFactory.get();
String currentPage = wctx.getCurrentPage();

การ call กลับไปยังฝั่ง browser จะใช้ class OutboundVariable มาช่วยทำ serialize
        try
{
OutboundVariable ov = wctx.toJavascript(messages);
String eval = ov.getInitCode() + "receiveMessages(" + ov.getAssignCode() + ");"; //

สำหรับผู้ที่สงสัยว่า getInitCode ใน code ข้างบน มันทำอะไร
สิ่งที่ method getInitCode ของ OutboundVariable generate ออกมา
มีหน้าตาดังนี้
var s0=[];var s1={id:1144813303647,text:"xxxx"};
var s2={id:1144813193425,text:"hello"};
var s3={id:1144813140215,text:"I know"};
var s4={id:1144813133617,text:"yes"};
var s5={id:1144813128413,text:"from camino"};
var s6={id:1144813122412,text:"hello"};
var s7={id:1144813118411,text:"yyy"};
var s8={id:1144813116211,text:"xxx"};
var s9={id:1144813095408,text:"zzzz"};
var s10={id:1144813092406,text:"yyyy"};
s0=s0.concat([s1,s2,s3,s4,s5,s6,s7,s8,s9,s10]);

ส่วน script ที่ ov.getAssigneCode gen ออกมา ก็มีหน้าตาดังนี้
receiveMessages(s0);


จากนั้นก็ loop เพื่อ call ไปยัง page ทุก page ที่ online อยู่
พระเอกในตอนนี้ก็คือ ScriptSession

// Loop over all the users on the current page
Collection pages = wctx.getScriptSessionsByPage(currentPage);
for (Iterator it = pages.iterator(); it.hasNext();)
{
ScriptSession otherSession = (ScriptSession) it.next();
otherSession.addScript(eval);
}
}

message ก็จะวิ่งต่อกลับมายัง javascript
ให้สังเกตว่า code ข้างบน ทำการ call javascript function ที่ชื่อ receiveMessages
โดยมี parameter คือ javascript code ที่ generate จาก method ข้างบน
จากนั้นที่ฝั่ง javascript ก็จะทำการ update หน้าจอ
function receiveMessages(messages) {
var chatlog = "";
for (var data in messages) {
chatlog = "<div>" + messages[data].text + "</div>" + chatlog;
}
DWRUtil.setValue("chatlog", chatlog);
}


ถ้าเราลองไปดู http protocol ที่วิ่งระหว่าง browser กับ server
จะเห็นว่ามีการส่ง polling ออกมาเป็นระยะ
หน้าตาของ polling packet มีหน้าตาดังนี้
POST /dwr/dwr/plainjs/DWRSystem.poll.dwr HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
....

callCount=1
c0-scriptName=DWRSystem
c0-methodName=poll
c0-id=2235_1144813311449
httpSessionId=3h2qek9hq3utl
scriptSessionId=0EADE3F10D560BBF374C1EDB48193BDA


การใช้ polling จะทำให้เกิดประเด็นตามมา ก็คือเรื่อง load ที่มากขึ้น (long lived HTTP Request)
มี solution หนึ่งที่ถูกเสนอขึ้นมา ก็คือ
Jetty 6 continuations
ซึ่งตอนนี้อยู่ในขั้น beta แล้ว

Related link from Roti

No comments: