Bye bye -services!
When you are building an application that uses the Flex FDS or ColdFusion servers as a back-end you need to define in your FlexBuilder project properties an additional compiler argument, -services. Why? FlexBuilder needs this argument/path to your services-config.xml file so it can pull just a few pieces of information out of the xml file. In turn, these pieces of information are what allows your flex client application to know what server to talk to and how. However, this has a few drawbacks.
- You need to be able to access your services-config.xml file. Which can be difficult if you are developing against a remote Flex server. Confusion with the launch url in FlexBuilder.
- By default endpoint urls in the services-config.xml use wildcards to define the host/port of the url "{server.host} and {server.port}". However, the default for FlexBuilder is to launch flex applications with a file://c:\ absolute path, easily corrected but still the default. Now the problem is that if your movie is launched with a file:// path, Flex can't use the {server.host}/{server.port} wildcards (there is no http url to pull the host and port from)
- But, you can't hardcode the url in services-config.xml either. Otherwise your .swf will be compiled with a url that points back to your development server. And you don't want to recompile the .swf for production because then the .swf you tested is NOT the same .swf as the one you deploy. (you always want to deploy the same code you test). Context-root problems, if you develop with one context-root and deploy to production under a different context-root - Watch out!
- Because the context-root needs to be hard coded in the services-config.xml or defined as a compiler argument in FlexBuilder too, it is compiled into the swf as a hard coded value. Which means, you need a different .swf for each platform you run one (a testing nightmare).
Alas, there is a solution! (You knew I was going there didn't you). All of this can be defined at runtime without the -services argument and the services-config.xml. Defining this at runtime also has a few advantages.
- Your context-root can be defined at runtime as a FlashVar and your .cfm or .jsp can easily set that with a simple variable.
- You can switch URL's as needed, for instance if the movie is loaded under an http:// url, use the wildcards. If it's loaded under a file:// or c:\.. path use a hard coded development url.
- Different developer enviroments and configurations don't matter, everyone should be able to drop the code, sorry sync from source control, on their machine and run the application.
There are different ways to do this but how I do it is with 2 files; a ConfigModel class and a standard ServiceLocator class.
ConfigModel.as
package model
{
import mx.core.Application;
import mx.controls.Alert;
import mx.utils.ObjectUtil;
/**
* Used to store Application config settings, and the
* FlashRemoting channel and endpoint url settings, with this you
* won't need define a -services compiler argument for the project.
*
* @author: Mike Nimer
**/
[Bindable(event="configChange")]
public class ConfigModel
{
public static var TIMEZONEOFFSET:Number; //est=4 or 5, depending on DST
// debug setting to switch between live and stub data
public static const USE_STUB_DATA:Boolean = false;
// Flash remoting config issues
public static const CHANNELID:String = "orca-amf";
private static const default_context_root:String = "/"; // adjust as needed for your default server.
/**
* pull the context-root out of the flash var, if it's defined in your <object/> or <embed/> tags.
* ex: contextRoot=/flex
**/
private static function get CONTEXT_ROOT():String
{
var app:Application = Application.application as Application;
if( app.parameters.contextRoot != null )
{
//Alert.show( ObjectUtil.toString(app.parameters) );
return app.parameters.contextRoot;
}
return ConfigModel.default_context_root;
}
// return the correct endpoint, if this is server from an HTTP request
// return the default localhost:8500 for development, if FlexBuilder launches
// the application locally with an absolute path (file://c:\...)
public static function get AMFENDPOINT():String
{
var app:Application = Application.application as Application;
if( app.url != null && app.url.indexOf("http:") != -1 )
{
return "http://{server.name}:{server.port}" + ConfigModel.CONTEXT_ROOT +"/messagebroker/amf";
// for ColdFusion users
// return "http://{server.name}:{server.port}" + ConfigModel.CONTEXT_ROOT +"/flex2gateway";
}
else
{
return "http://localhost:8500" + ConfigModel.CONTEXT_ROOT + "/messagebroker/amf";
// for ColdFusion Users
// return "http://localhost:8500" + ConfigModel.CONTEXT_ROOT + "/flex2gateway";
}
}
}
}
ServiceLocator.as
package services
{
import model.ConfigModel;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.rpc.events.ResultEvent;
import mx.messaging.channels.AMFChannel;
import mx.rpc.events.FaultEvent;
import mx.rpc.AbstractService;
import mx.messaging.config.ServerConfig;
/**
* Create the RemoteObject tags used by the application service delegates
* @author: Mike Nimer
**/
public class ServiceLocator
{
public var service:AbstractService;
public var amfChannelSet:ChannelSet;
public static var serviceLocator:ServiceLocator;
public function ServiceLocator()
{
var amfChannel:Channel = new AMFChannel(ConfigModel.CHANNELID, ConfigModel.AMFENDPOINT);
amfChannelSet = new ChannelSet();
amfChannelSet.addChannel(amfChannel);
}
public static function getInstance():ServiceLocator
{
if( ServiceLocator.serviceLocator == null )
{
ServiceLocator.serviceLocator = new ServiceLocator();
}
return ServiceLocator.serviceLocator;
}
/**
* Individual Services used by the Application.
**/
public function getSomeService():AbstractService
{
if( service != null )
{
return service;
}
service = new RemoteObject('someDestination');
service.channelSet = this.amfChannelSet;
(service as RemoteObject).showBusyCursor = true;
// service.source = "dot.path.to.cfc" // If you are using ColdFusion
//service.setCredentials(username, password); // if you need it
return service;
}
}
}
Set Context-root FlashVar (ColdFusion)
// You'll find this in the default html flex generates, just make the html page a cfm and add this FlashVar.
// note: Use contextRoot=<%=request.getContextPath()%> for JSP pages.
AC_FL_RunContent(
"src", "Orca",
"width", "100%",
"height", "100%",
"align", "middle",
"id", "Orca",
"quality", "high",
"bgcolor", "#869ca7",
"name", "Orca",
"flashvars",'contextRoot=#getContextRoot()#&historyUrl=history.htm%3F&lconid=' + lc_id + '',
"allowScriptAccess","sameDomain",
"type", "application/x-shockwave-flash",
"pluginspage", "http://www.adobe.com/go/getflashplayer"
);

We just have a local services-config.xml stored at the same level as the base 'src' directory, and compile against that. All our devs have local CF installs and setup Eclipse for that, so we can leave the {} place holders in and it Just Works.
I guess I'm really trying to understand where/how the compiled SWF is asking for the path to the "services-config.xml". Using CF7.02 for Flex 3beta3 (company won't purchase till this is deployed).
Many different configurations going from "sandbox" to development, staging/testing, production (we can't be like most shops who have mirrored environment configurations --frustrated sarcasm--)…
and we can not be changing the path to the services-config.xml file for each migration to the next environment. should I try mapping something? couldn't we just put the call to the CGI vars in the
CFM wrapper and pass them in? If we do that, what var is the SWF looking for in the FlashVars to pass in? Would I be using the same var passed in from Application.application.parameters.PASSED IN VAR and use it in my RemoteObject?
RemoteObject endpoint setting. Works like a charm.
we have a shared development server, so I don't need to worry about the "file//c:\" situation.
U can just use relative paths......
If you need to connecto to yout amf endo point and its url is in a secure environment (a.k.a. HTTPS) you need to change the AMFChannel to a SecureAMFChannel. Took me some time to figure it.