Section 15.2. Moving Code to the Server


15.2. Moving Code to the Server

We all want to build scalable, efficient, and easy-to-maintain applications. In this section, we try to convince you that moving application logic to the server-side is the best way to accomplish that goal.

Flash programmers who first start working with FlashCom may not immediately see the value for splitting up codeespecially objectsso that part runs in the client and part on the server. In fact, the FlashCom API gives so much power to a Flash client that it may appear there isn't any reason to write Server-Side ActionScript beyond controlling connections. However, the longer you work with FlashCom, the more you recognize the importance of server-side code. Server-side scripts are often safer from a security standpoint and help to simplify designs and code; you should consider them the backbone of your applications.

A few classic examples of things to do on the server side are:

  • Increment a counter (for example, to assign a unique ID to each user ).

  • Keep track of the number of users connected.

  • Write to shared objects.

  • Set shared objects' default values.

  • Secure your application from rogue clients .

  • Side-step the Flash Player security sandbox.

Here is an example server-side script that keeps track of the number of users connected:

 application.onAppStart = function (  ) {   this.numUsers_so = SharedObject.get("numUsers", false);   this.numUsers_so.setProperty("users", 0);   this.numUsers_so.setProperty("guests", 0);   this.nextID = 0; }; application.onConnect = function (client, isGuest) {   client.id = "user_" + this.nextID++;   client.isGuest = isGuest;   trace("accepting client " + client.id);   application.acceptConnection(client);   client.call("setID", null, client.id);   if (isGuest)     this.numUsers_so.setProperty("guests",              this.numUsers_so.getProperty("guests") + 1);   else     this.numUsers_so.setProperty("users",              this.numUsers_so.getProperty("users") + 1); }; application.onDisconnect = function (client) {   trace("client " + client.id + " left");   if (client.isGuest)     this.numUsers_so.setProperty("guests",              this.numUsers_so.getProperty("guests")-1);   else     this.numUsers_so.setProperty("users",              this.numUsers_so.getProperty("users")-1); }; 

And here is the simple, companion script on the client side:

 nc = new NetConnection(  ); nc.setID = function (myID) {   _global.myID = myID;   setUp( ); }; function connectAsUser ( ) {   nc.connect("rtmp:/test", false); } function connectAsGuest ( ) {   nc.connect("rtmp:/test", true); } function setUp ( ) {   so = SharedObject.getRemote("numUsers", nc.uri, false);   so.onSync = function (list) {     trace("There are " + this.data.users +          " users and " + this.data.guests + " guests connected");   };   so.connect(nc); } 

Notice how the client side never makes changes to the numUsers shared object; it just listens to it via the onSync( ) handler. All the changes are done on the server side, where onConnect( ) and onDisconnect( ) methods get called for you at the appropriate times.

There are other cases in which having code on the server side is the right thing to do.

Any time you are about to write code that makes changes to a shared object on the client side, ask yourself: are multiple clients going to run this same code? Could there be a race condition , a situation in which one client's update interferes with another's update? (Concurrency and synchronization problems are covered in more detail in Chapter 17.) For example, say you have an Edit button that allows users to gain unique control over a shared whiteboard or note. A typical implementation is to have a property in a shared object called inUseBy , which is null if nobody's editing the whiteboard or contains the user ID of a user who clicked on the Edit button. You could do this on the client side:

 // Called by the Edit button on click. function doEdit (  ) {   this.so.data.inUseBy = _global.myID; } // With something like this in the   onSync( )   definition. this.so.data.onSync = function (p_list) {   for (var i in p_list) {     if (p_list[i].name == "inUseBy") {       if (this.data.inUseBy == undefined) {         edit_pb.setEnabled(true);         status_txt.text = "Click Edit to edit the Whiteboard ";       } else if (this.data.inUseBy == _global.myID) {         edit_pb.setLabel("done");         status_txt.text = "Click Done when finished";         startEditMode( );       } else {         edit_pb.setEnabled(false);         status_txt.text = "Whiteboard being edited by " + this.data.inUseBy;       }     }   } }; 

What happens if inUseBy is null and two users click on the Edit button at around the same time? Both users will try to set the value of inUseBy to their ID, but one of the changes will be rejected. For the user who got rejected, it will look as if clicking on the button didn't do anything. Depending on timing, both changes might be accepted! In that case, it will look as if a client is stealing control of the whiteboard from the other, which makes your application look untrustworthy.

A much better approach is to move the shared object update to the server side. Now the client-side code calls the server-side editWB( ) method instead of performing the update directly:

 // Called by the Edit button on click function doEdit (  ) {   nc.call("editWB", null, _global.myID); } // with the same   onSync( )   as in the previous example. 

Here is the implementation for the editWB( ) method on the server, which updates the shared object:

 Client.prototype.editWB = function (p_id) {   var isBeingUsed = (this.so.getProperty("inUseBy") != undefined);   if (isBeingUsed)     return;   this.so.setProperty("inUseBy", p_id); }; 

The setProperty( ) call triggers the onSync( ) handler on all clients, just like before.

Because the server side is single-threaded, we now know that only one execution of editWB( ) will be run at a time, with other requests being queued up by the server. So we can safely add a check to avoid the "stealing" that can occur inadvertently if the clients are allowed to update the shared object directly.

Here is another example to demonstrate why and when to move a shared object update to the server. Sometimes you want to make changes to a shared object based on the result object received by another shared object's onSync( ) method. For example, suppose your application has different layouts, and each layout can have different components visible or hidden. The current layout ID is saved in a layouts_so shared object, and all the data about your components on the Stage is saved in a components_so shared object.

It's tempting, at first, to update the components_so on the client side:

 function changeLayout (p_newLayoutID) {   layouts_so.data.layoutID = p_newLayoutID; } layouts_so.onSync = function (p_list) {   switch (this.data.layoutID) {     case "discussion":       components_so.data.chat = true;       components_so.data.video = true;       break;     case "video-only":       components_so.data.chat = false;       components_so.data.video = true;       break;     case "chat-only":       components_so.data.chat = true;       components_so.data.video = false;       break;   } }; components_so.onSync = function (p_list) {   for (var i in p_list) {     _root[p_list[i].name+"_mc"]._visible = this.data[i];   } }; 

On second thought, it means that all the clients will try to make the same changes to the components_so shared object whenever the layout is changed. This is a waste of resources; we'd prefer to change the components_so contents only once.

Once again, the server side comes to the rescue. You can choose between two ways to perform the update on the server side. You could add an onSync( ) handler to layouts_so on the server side as well and make changes to components_so from there. Unfortunately, server-side onSync( ) handlers don't get triggered by server-side setProperty( ) calls, so you'd have to manually generate an onSync event every time you change layouts_so in your server-side code. The other option (preferred and simpler) is to move the change to the layouts_so shared object to the server side as well, just as we did in the previous example. Here's what the revised version look likes on the client side:

 function changeLayout (p_newLayoutID) {   nc.call("changeLayout", null, p_newLayoutID); } layouts_so.onSync = function (p_list) {   trace("Now in layout " + this.data.layoutID); }; components_so.onSync = function (p_list) {   for (var i in p_list) {     _root[p_list[i].name+"_mc"]._visible = this.data[i];   } }; 

And here's the revised server-side code:

 Client.prototype.changeLayout = function (p_id) {   application.layouts_so.setProperty("layoutID", p_id);   application.components_so.lock(  );   switch (p_id) {     case "discussion":       application.components_so.setProperty("chat", true);       application.components_so.setProperty("video", true);       break;     case "video-only":       application.components_so.setProperty("chat", false);       application.components_so.setProperty("video", true);       break;     case "chat-only":       application.components_so.setProperty("chat", true);       application.components_so.setProperty("video", false);       break;   }   application.components_so.unlock( ); }; 

Did you notice the code follows the fa §ade design pattern? (If not, don't fret; it's covered in the next section.) One call makes changes to two shared objects, which triggers two different onSync( ) handlers on all clients.

The important part is that all the changes to components_so are done only once.

As you have seen, the server side is a centralized location in which to put the most critical parts of your scripts. The more you write client-to-server code in FlashCom, the more you'll get a feel for what should go where. You'll probably find yourself writing code on the client first, only to realize later that it should really go on the server. The good thing is that the language is very similar, and porting is pretty quick.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net