Section 15.6. Components and Component Frameworks

15.6. Components and Component Frameworks

Perhaps the best piece of advice this book can provide is to recommend you use, extend, and write communication components. Components provide a way to build complex applications in manageable chunks and can be designed following something resembling the classic Model-View-Controller (MVC) design pattern.

Each component:

  • Will generate events that a controller-like object can preprocess

  • Will have a modeloften a shared objectand the code that enforces business rules on it

  • Will have a view that consists of subcomponents that respond to changes in the model

By developing components with both client- and server-side parts , components can leverage the resources of both.

However, components cannot be designed ad hoc. They should have naming conventions, a way to instantiate them on both the client and server, and a standard way to hook them together. In fact, the word "component" normally refers to a piece of software designed to work without alteration within a component container of some type. The container is sometimes referred to as a component framework . A component specification and framework may define:

  • A naming convention designed to avoid collisions between components and their resources. Namespace rules avoid collisions between different types and instances of the same type of component.

  • A message-passing or event system for intercomponent messaging and life cycle managementfor example, notifying a component when a user connects to an application.

  • Component base classes that simplify writing new components and that can already perform common tasks . In the Flash v2 UI component framework, the UIComponent class is a good example.

  • A framework or context that components work within and can rely on to perform certain services. Often it is the component framework that will deliver application-level events that components must respond to.

Along with FlashCom, Macromedia released a set of components and a component framework that are the subject of Chapter 14. The design of the communication components and component framework were driven by one overriding goal: adding a component to an application had to be simple. All that is required to have a component work is to drag an instance to the Stage in Flash, give it a name , and connect it to a SimpleConnect component. In other words, little or no server-side coding is required.

As a consequence, the server-side part of each component is automatically created whenever a client-side component connects. For some applications, automatic generation of server-side objects (so-called lazy instantiation) is a great feature. But in other applications, it represents a potential security problem. Another consequence of Macromedia's design choices is that all the server-side component class files are loaded by the framework in case they are neededwhether they are used or not.

No authentication mechanism or hooks were provided with the component system and little in the way of user management and control. See Chapter 14 for how to authenticate users and Chapter 18 for some ways to implement role-based access controls. And unfortunately , the namespace design of the components was not extended to define a URI namespace for component resources. Component resources are created in the root folder of the application instance, making it impossible to protect other resources in subfolders using the client.readAccess and client.writeAccess properties.

15.6.1. Yet Another Component Framework?

Any developer faced with the need to create any but the simplest FlashCom application has to decide whether to use Macromedia's components and component framework or strike out in a different direction. Chapter 13 presented a set of FlashCom components that used little more than the v2 UI components provided with Flash. If you read through Chapter 13, you will have seen a number of redundancies in some of the components and other things that could have been improved. The remainder of this chapter describes a very simple and lightweight component framework that was designed as an outgrowth of the work shown in Chapter 13. Naturally, the complete framework is available as a download from the book's web site.

The design goals of this PFCS framework are:

  • Remove redundancy from the Chapter 13 components and move common functions into a base class ( PFCSComponent ) that can be used to develop new component objects on the client and server.

  • Provide a foundation for user authentication, user management, and better server-side security on the client and server that is leveraged in later chapters of the book.

  • Keep it simpleadd to the base classes and framework only features that are needed to reduce redundancy and improve user management and security.

  • Assume a developer using the framework is fully capable of server-side coding, including loading only the class files that are needed and writing some code to name and instantiate component instances on the server.

15.6.2. The PFCSComponent Base Classes

The PFCSComponent base classes are designed to simplify creating the client- and server-side objects that work together to create a communication component. The PFCSComponent.as file can be found in the /com/oreilly/pfcs/framework directory in any Zip archive on the book's web site that uses the PFCS framework; the PFCSComponent.asc file can be found in the scriptlib/pfcs directory. Both classes are designed to support the naming conventions of the components and the management of each component's remote methods . On the server, the PFCSComponent class also provides a simple storage mechanism for client-specific data that the component needs to manage.

The client-side PFCSComponent base class is the simpler of the two PFCSComponent classes, so let's look at it first. It implements the namespace- related methods that set and get a component's name and update its path . For example, the default name for a component is main . The full path of the component will be in the pfcs/ ClassName /main format. If a component is named something other than main , setting its name will also update its full path. For example, to name a TextChat component instance breakout1 , set the name this way:

 myTextChat.name = breakout1; 

The internal path will be updated to pfcs/TextChat/breakout1 .

Whenever the server-side part of a component must call a client-side remote method, the method must be attached to the NetConnection object the client is using to communicate to the server. To avoid collisions between remote methods belonging to different components, each method follows the namespace of the components. For example, if a ConnectionTester component named main had to attach an echoRequest( ) method to the NetConnection object it was using, it would attach it as follows:

 var proxy = getProxy(nc); proxy.echoRequest() = function (  ) {   return; }; 

The effect is the same as if it had done the following:

 addObjectPath(__nc); __nc.pfcs.ConnectionTester.main.echoRequest() = function (  ) {   return; }; 

The main object in the object path __ nc.pfcs.ConnectionTester.main is the component's proxy object. The component places whatever remote methods it must define on its proxy. The getProxy( ) method takes care of making sure the proxy object exists and returning it to the component. Example 15-6 lists the code for the client-side PFCSComponent class, stored in PFCSComponent.as .

Example 15-6. The code for the client-side PFCSComponent class
 class com.oreilly.pfcs.framework.PFCSComponent extends mx.core.UIComponent {   // Connect component class and symbol.   var className:String = "PFCSComponent";   static var symbolName:String = "PFCSComponent";   static var symbolOwner:Object = PFCSComponent;   // Variables.   var __user:Object;   var __nc:NetConnection;   var instance;   // Default path and name.   var path:String;   var __name:String = "main";   // Constructor--call the superclass constructor.   function PFCSComponent( ) {     super( );   }   function init( ) {     instance = this;     super.init( );   }   function set name (name:String) {     if (!name) name = "main";     __name = name;     path = "pfcs/" + className + "/" + __name + "/";   }   function get name( ) {     return __name;   }   public function set nc (nc:NetConnection) {     __nc = nc;   }   public function set user (user:Object) {     __user = user;   }   /*   addObjectPath( )   adds an object path to the NetConnection object based on    * the   path   property and returns the last object added. If the path is    *   pfcs.PFCSComponent.main   , the   nc   will have the nested object    *   nc.pfcs.PFCSComponent.main   , which will be returned.    * The   main   object is a proxy for the component    * where remote methods arrive and are passed on to the component.    */   function addObjectPath (nc) {     var pathArray = path.split("/");     var obj = nc;     var name;     for (var i = 0; i < pathArray.length; i++) {       name = pathArray[i];       if (!name) continue;       if (!obj[name]) {         obj[name] = {};       }       obj = obj[name];     }     return obj;   }   //   removeObjectPath( )   removes the proxy object in the object path   // and the class name object if it has no other subobjects.   function removeObjectPath (nc) {     var classObject = nc.pfcs[this.className];     delete classObject[this.name];     var deleteClassObject = true;     for (var p in classObject) {       deleteClassObject = false;       break;     }     if (deleteClassObject) {       delete nc.pfcs[this.className];     }   }   function getProxy (nc) {     var proxy = nc.pfcs[className][name];     if (!proxy) {       // trace("No proxy, so making one.");       proxy = addObjectPath(nc);     }     return proxy;   } } 

The PeopleCursors component listed in Example 15-2 extends PFCSComponent and so does not need to replicate the set name( ) method to create a path. The ConnectionTester class shown in Example 17-1 provides an example of using the getProxy( ) method.

The server-side PFCSComponent does a little more work. In addition to creating the path string when its name is set and providing a getProxy( ) method, it also provides methods that can be used to update each client's access control strings. Chapter 18 describes how each component can control access to its SharedObject and Stream resources using the addAccessPath( ) and removeAccessPath( ) methods. Unlike the client-side part of each component, the server-side component's name is passed into its constructor and the getProxy( ) method is passed a client object instead of a NetConnection . Example 15-7 lists the complete code for the server-side PFCSComponent class, stored in PFCSComponent.asc .

Example 15-7. The code for the server-side PFCSComponent class
 // Base class for the server-side object part of a communication component. function PFCSComponent (name) {   if (this.className == "PFCSComponent") {     return; // Return when overwriting a subclass's prototype.   }   if (!name) name = "main";   this.name = name;   this.path = "pfcs/" + this.className + "/" + name;   trace("PFCSComponent(" + this.name + ")> className: " + this.className); } p = PFCSComponent.prototype; p.className = "PFCSComponent"; /*   addObjectPath( )   adds an object path to the client object based on  * the   path   property and returns the last object added. If the path is  *   pfcs.PFCSComponent.main   , the client will have objects:  *   client.pfcs.PFCSComponent.main   and   main   will be returned.  * The   main   object is a proxy for the component  * where remote methods arrive and are passed on to the component.  */ p.addObjectPath = function (client) {   var pathArray = this.path.split("/");   var obj = client;   var name;   for (var i = 0; i < pathArray.length; i++) {     name = pathArray[i];     if (!obj[name]) {       obj[name] = {};     }     obj = obj[name];   }   return obj; }; //   removeObjectPath( )   removes the proxy object in the object path // and the class name object if it has no other subobjects. p.removeObjectPath = function (client) {   var classObject = client.pfcs[this.className];   delete classObject[this.name];   var deleteClassObject = true;   for (var p in classObject) {     deleteClassObject = true;     break;   }   if (deleteClassObject) {     delete client.pfcs[this.className];   } }; //   getAccessArray( )   returns the access string broken down into elements // within an array. Used by   addAccessPath( )   and   removeAccessPath( )   . p.getAccessArray = function (client, accessStringName) {   var accessString = client[accessStringName];   if (accessString.length > 0)     return accessString.split(";");   else     return []; }; //   addAccessPath( )   adds a path to the semicolon-delimited string in the   client   . // Valid values for   accessStringName   are "readAccess" and "writeAccess". p.addAccessPath = function (client, accessStringName, path) {   var pathArray = this.getAccessArray(client, accessStringName);   pathArray.push(path);   client[accessStringName] = pathArray.join(";"); }; //   removeAccessPath( )   deletes a path from the semicolon-delimited string in the //   client   . Valid values for   accessStringName   are "readAccess" and "writeAccess". p.removeAccessPath = function (client, accessStringName, path) {   var pathArray = this.getAccessArray(client, accessStringName);   var len = pathArray.length;   for (var i = 0; i < len; i++) {     if (pathArray[i] == path) {        pathArray.splice(i, 1); // remove the element        break;     }   }   client[accessStringName] = pathArray.join(";"); }; 

Inheriting from the PFCSComponent base class on the server requires a little more work in Server-Side ActionScript than in client-side ActionScript 2.0 because the former uses prototype-based inheritance and does not support the more formal class and extends keywords. Example 15-8 shows the complete listing of the server-side part of the PeopleCursors.asc file.

Example 15-8. The server-side part of the PeopleCursors.asc file
 function PeopleCursors (name) {   PFCSComponent.apply(this, [name]);   this.so = SharedObject.get(this.path + "/cursors"); } // PeopleCursors inherits from (is a subclass of) PFCSComponent. PeopleCursors.prototype = new PFCSComponent( ); PeopleCursors.prototype.constructor = PeopleCursors; PeopleCursors.prototype.className = "PeopleCursors"; PeopleCursors.prototype.addClient = function (client) {   // Let all clients read and write the shared object.   this.addAccessPath(client, "readAccess", this.path);   this.addAccessPath(client, "writeAccess", this.path); }; PeopleCursors.prototype.removeClient = function (client) {   // Make sure this user's cursor is deleted when the client   // disconnects or no longer is using this component.   var user = pfcs.getUser(client);   this.so.setProperty(user.userName, null);   // Remove all the access strings that were added for this component.   this.removeAccessPath(client, "readAccess", this.path);   this.removeAccessPath(client, "writeAccess", this.path); }; PeopleCursors.prototype.close = function ( ) {   this.so.close( ); }; 

Since there is no super operator in Server-Side ActionScript, the PFCSComponent constructor function is called directly and applied to the object being instantiated in the PeopleCursors( ) constructor in place of calling super( ) . In place of the class declaration and extends keyword in AS 2.0, the prototype object is overwritten with a PFCSComponent object to establish inheritance:

 PeopleCursors.prototype = new PFCSComponent(  ); 

When the prototype is replaced , its constructor property is automatically set to PFCSComponent . To set it back to the correct constructor function, the constructor property has to be updated as follows:

 PeopleCursors.prototype.constructor = PeopleCursors; 

Otherwise, the code is much like what was shown in Chapter 13. Every server-side component is expected to implement an addClient( ) , removeClient( ) , and close( ) method. The addClient( ) method is the component's opportunity to make the component work for the client. If necessary, a slot for the client in a shared object can be created, remote methods can be added to the client, and client-specific data can be added to the component's proxy on the client. The addClient( ) method is also where the client's access rights to component resources are determined and limited access is granted. The removeClient( ) method removes anything added to the client including access to component resources. The close( ) method is used to clean up component resources before the component is disposed of.

I've spent a fair amount of time explaining the PFCSComponent base classes and how to extend them, because most of the work that goes into developing a component is done in the process of extending the base classes. But components don't live in a vacuum . The PFCSFramework classes provide some essential services you'll need to use, as described in the next section.

15.6.3. The PFCSFramework Classes

On the client side, the PFCSFramework and Auth classes are responsible for managing the login and connection events on behalf of the application and providing information about the user to any component that needs it. The actual login and authentication process is done by an Auth object. Example 15-9 is a small snippet of code from the main timeline of a test movie that uses the PFCS framework. It imports the two classes required by the frameworkthe PFCSFramework class and an authentication class.

Example 15-9. Timeline code of a test movie that uses the PFCS framework
 import com.oreilly.pfcs.framework.PFCSFramework; import com.oreilly.pfcs.framework.auth.TicketAuth; _global.pfcs = new PFCSFramework(this); auth:TicketAuth = new TicketAuth("http://localhost:8500/flashservices/gateway",                                  "pfcs.conference.user"); pfcs.setAuth(auth); pfcs.setURI("rtmp:/conference/room1"); gotoAndStop("Login"); 

The preceding code initializes the framework, sets its authentication object and the URI of the application instance, and then moves the playhead to a Login frame. From now on, any component or form that needs to use the framework will call a method on the global pfcs object. Example 15-9 uses an authentication object that uses a web application to get a ticket before using the ticket to log in to FlashCom. Other authentication objects can also be used. Ticketing is discussed in Chapter 18.

When the login form is filled in and the user is ready to log in, the form will call:

 pfcs.doLogin(this, userName, password); 

which will connect to a web application to get a ticket and then to a FlashCom instance (using the ticket for authentication). The entire PFCSFramework.as file is not listed here, but here is a list of often-used methods:


pfcs.getUser( )

Returns the current user object that includes any personal information about the user returned from the authentication database


pfcs.getUserName( )

Returns the user's username


pfcs.getUserRoles( )

Returns an object; each property name is a user role


pfcs.inRole("roleName")

Returns true if the user is in the role


pfcs. inRoles(role_1, role_2 [, role_n])

Returns TRue if the user is in any of the named roles


pfcs.doLogin(loginForm, userName, password)

Starts the login process


pfcs.doLogout( )

Disconnects from the web application (if there is one) and FlashCom


pfcs.getNC( )

Gets a NetConnection object that components can use to connect with

On the server, all the framework files are found in the scriptlib/pfcs or scriptlib/pfcs/components directories. Example 15-10 shows the beginning of a main.asc file that uses the framework and abbreviated parts of the rest of the file.

Example 15-10. Part of a main.asc file that uses the framework
 load("pfcs/PFCSFramework.asc"); load("pfcs/Auth.asc"); load("pfcs/components/PeopleGrid.asc"); load("pfcs/components/SharedText.asc"); load("pfcs/components/TextChat.asc"); load("pfcs/components/PeopleCursors.asc"); application.onAppStart = function ( ) {   // Create a global pfcs object to manage clients.   pfcs = new PFCSFramework( );   // Create an authentication object to authenticate clients.   auth = new Auth('conference');   // Create a main component manager and tell it what components to create.   cManager = new PFCSComponentManager("main");   cManager.addComponent(PeopleGrid);   cManager.addComponent(SharedText);   cManager.addComponent(TextChat);   cManager.addComponent(PeopleCursors); }; application.onConnect = function (client, userName, password) {   return auth.authenticate(client, userName, password); }; application.onAuthenticate = function (client, result) {   // Code removed...   // Control access and bandwidth based on one of the user's roles.   client.readAccess = "";    // Deny all readAccess to start.   client.writeAccess = "";   // Deny all writeAccess to start.   // Store the client in the   pfcs.clients   object by username.   pfcs.addClient(client, result);   cManager.addClient(client);   // Accept the client's connection request.   application.acceptConnection(client); }; application.onDisconnect = function (client) {   cManager.removeClient(client);   pfcs.removeClient(client) }; application.onAppStop = function ( ) {   cManager.close( ); }; 

The authentication part of the code is explained in Chapter 18. Instead of passing each component's addClient( ) method a client reference, as in Chapter 13, a component manager does the work of setting up and initializing each component it manages . Component managers allow you to group components together logically. The idea is that sets of components for things like a breakout session can be handled separately by another component manager. Every component can call any of the following methods on the global pfcs object:


pfcs.addClient(client, user)

Allows the framework to manage the client and associate the user object's information with the client


pfcs.getUser(client)

Returns the current user object associated with the client


pfcs.getUserName(client)

Returns the username for the user associated with the client


pfcs.getUserRoles(client)

Returns an object where the user's role names are property names


pfcs.getClient(userName)

Returns a user's client


pfcs.clientInRole(client, "roleName")

Returns true if the client matches the specified role


pfcs.clientInRoles = function (client, role_1, role_2, [role_n])

Returns TRue if the client matches any of the specified roles

The complete source files for the framework are available from the book's web site. It is designed to be a minimal framework that you can experiment with, adapt, and extend to suit your own needs. It is not cast in stone, so please experiment with it! We'll make extensive use of it in the following chapters.



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