With some of the recent changes to the Services module in Drupal and without a great Flex integration example I was pressed to work my way through putting the pieces together. There are some good starter examples but I hope to take this start to finish including the use of api keys and session ids in amfphp service calls. I used this example as my starting point: A really, really simple Services + AMFPHP example using Flex, but I do repeat many of these steps in this tutorial as well.
Some assumptions:
- Drupal 6 installed
- Views module installed
- Services module installed
- AMFPHP module installed – with AMFPHP 1.9 Beta 2 – this needs to be in the amfphp module directory
Starting with setting up Drupal, we are going to enable the services module, amfphp module, and for this example node service, system service, user service, and views service. After enabled go to the site building/services and make sure that in settings “use keys” and “use sessid” is checked (this is done by default). Now go to the keys tab and create a key with the title of the site you are wanting the key to be used for (just a label for your purposes) and the domain which it will reside on (this will be checked by the service call). This process will produce a key we will use later in our flex application. Note this or have this page available.
Now to the Flex side. Create a new project and include in the src directory the services-config.xml that comes with amfphp (in the browser directory). Modify the uri in this line to point to your amfphp service:
<endpoint uri="http://flashservices/gateway.php" class="flex.messaging.endpoints.AMFEndpoint"/>
<endpoint uri="/services/amfphp" class="flex.messaging.endpoints.AMFEndpoint"/>
This may be different if you have your drupal install inside a directory and not at the root (i.e. /subdir/services/amfphp)
In your project properties/compiler arguments append this line to the existing arguments:
-locale en_US -services “services-config.xml”
We also want to grab the as3corlib for its crypto functions. We’ll use this to hash our api key before passing it to Druapl. Include this in your libs directory.
Now we need to add some remote objects to our Flex project and for this tutorial a couple of forms and a data grid. In this example we will create a new user, automatically log them in, create some node data and view that node data in the data grid (what you do with the information is up to you, this is simply to display different functionality).
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script source="ServicesTutorial.as"/>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="system" id="system">
<mx:method name="connect" result="onSystemConnect(event)" fault="onFault(event)" />
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="node" id="node">
<mx:method name="save" result="onNodeSaved(event)" fault="onFault(event)"/>
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="user" id="user">
<mx:method name="save" result="onUserSaved(event)" fault="onFault(event)"/>
<mx:method name="login" result="onUserLogin(event)" fault="onFault(event)"/>
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="views" id="views">
<mx:method name="get" result="onViewsResult(event)" fault="onFault(event)" />
</mx:RemoteObject>
<mx:Form>
<mx:FormItem label="Username">
<mx:TextInput id="usernameInput"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="passwordInput" displayAsPassword="true"/>
</mx:FormItem>
<mx:FormItem label="Email">
<mx:TextInput id="emailInput"/>
</mx:FormItem>
<mx:FormItem >
<mx:Button label="Register" click="registerUser();"/>
</mx:FormItem>
</mx:Form>
<mx:Form>
<mx:FormItem label="Title">
<mx:TextInput id="titleInput" text="{selectedStories.selectedItem.node_title}"/>
</mx:FormItem>
<mx:FormItem label="Body">
<mx:TextArea id="bodyInput" text="{selectedStories.selectedItem.node_revisions_body}"/>
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Submit" click="saveStory();"/>
</mx:FormItem>
</mx:Form>
<mx:DataGrid id="selectedStories" dataProvider="{theStories}">
<mx:columns>
<mx:DataGridColumn headerText="NID" dataField="nid"/>
<mx:DataGridColumn headerText="Title" dataField="node_title"/>
<mx:DataGridColumn headerText="Body" dataField="node_revisions_body"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>
For the remote object the source property is the type of service (node, system, user, views), the destination matches the the destination ID in the services-config.xml, the method name is the service method we want to call (get, save, etc).
These remote objects need their corresponding result handlers so we will create another ActionScript file to hold our script. In this we will create all of the result handlers for our service calls. This including the single fault handler which would ideally would have different messages/actions for each type of service call.
import com.adobe.crypto.HMAC;
import com.adobe.crypto.SHA256;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ArrayUtil;
private var apiKey:String = "your api key from drupal services";
private var apiDomain:String = "your api domain from drupal services";
public function onSystemConnect(event:ResultEvent):void{
}
public function onUserSaved(event:ResultEvent):void{
}
public function onUserLogin(event:ResultEvent):void{
}
public function onNodeSaved(event:ResultEvent):void{
}
public function onViewsResult(event:ResultEvent):void{
}
public function onFault(event:FaultEvent):void{
Alert.show("there was a problem");
}
public function hashKey(serviceMethod:String):Array{
var captureTime:String = (Math.round((new Date().getTime())/1000)).toString();
var captureRandom:String = randomString(10);
var hashString:String = captureTime + ";";
hashString += apiDomain + ";";
hashString += captureRandom +";";
hashString += serviceMethod;
return [HMAC.hash(apiKey,hashString,SHA256),apiDomain,captureTime,captureRandom];
}
private function randomString(Stringlength:Number):String{
var allowedChar:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var allowedArray:Array = allowedChar.split("");
var randomChars:String = "";
for (var i:Number = 0; i < Stringlength; i++){
randomChars += allowedArray[Math.floor(Math.random() * allowedArray.length)];
}
return randomChars;
}
At the end of this script is the hashing function which will return an array of the information we need and a helper random string generator. You’ll notice also that there are imports for the crypto functions in the as3corelib and we have set strings containing our api key and the api domain, fill these in with the information you generated with the services module.
Now we can start calling our services and telling flex what to do with the results. First we will connect to the system to get a session ID from drupal as well as check if there is a user currently logged in. We will call the system.connect method from an init function run in creation complete. Add the creationComplete event to the application and the corresponding function.
mxml:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">
as:
public function init():void{
system.connect();
}
When we get a response from system.connect it returns an object with the session id and a logged in user. We want to retreive this information and store it for subsequent calls. In the onSystemConnect method we’ll handle the results and store these in appropriate varaibles, we’ll put these at the top to keep them in scope with the rest of the functions.
private var sessionID:String;
private var currentUser:String;
public function onSystemConnect(event:ResultEvent):void{
sessionID = event.result.sessid;
currentUser = event.result.user.name;
}
Also for this call to work as an anonymous user we need to open up the permissions allowing anonymous users to connect to services (user/permissions/service module/access services). You’ll notice that I am storing the user’s name rather than uid. This is because Flex uses the uid internally as a “unique identifier.” When we save the user name with other data Drupal will complete the Drupal uid when it sees the name variable.
After we connect to services we want to do something. I have chosen to give the option to register a new user in Drupal. This next call is the user.save service. We are using the hashKey function and using the returned array as our first arguments for the user.save. We are passing the “user.save” string to the hashKey function because we are calling the system.connect method. The session id we retrieved from system.connect will be passed in and a username, password and email which are also required fields for a new user. I have created a new function to handle the registration data called registerUser. You may want some validation on these fields but I am omitting that for length.
public function registerUser():void{
var hashedArray:Array = hashKey("user.save");
var userCreate:Object = new Object();
userCreate.name = usernameInput.text;
userCreate.pass = passwordInput.text;
userCreate.mail = emailInput.text;
userCreate.status = 1;
user.save(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, userCreate);
}
I have also set the status on the userCreate so that the user is immediatly set to active. If this is not done admin approval would be required. And like system.connect for anonnymous users to create a new users, permissions will need to be changed (user_service module/create new users). I am choosing to log the user in right after registration but this could be done in a seperate action. Additional requirements for login are the username and password.
public function onUserSaved(event:ResultEvent):void{
var hashedArray:Array = hashKey("user.login");
user.login(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, usernameInput.text, passwordInput.text);
}
After we log in I’m going to grab all of the stories that have been created with a view that I have created. This view retrieves the body, title and nid fields from stories.
[Bindable]
private var theStories:Array;
public function onUserLogin(event:ResultEvent):void{
var hashedArray:Array = hashKey("views.get");
views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, "getUserStories");
}
public function onViewsResult(event:ResultEvent):void{
theStories = ArrayUtil.toArray(event.result);
}
Again make sure that you have permissions to access views in the user permissions. Make sure the array we are populating is bindable because this array will populate our datagrid. You should now be able to register a new user, auto login and see the all stories in the data grid. If you wanted to log in an existing user you would have to set up another action to do just the login. The last thing we will do with services is change the content of one of the stories or create new stories by doing a node.save. Using the views that pulled in we can modify that information and send it back to Drupal.
public function saveStory():void{
var edit:Object;
if(selectedStories.selectedItem){
edit = selectedStories.selectedItem;
}
else{
edit = new Object;
}
edit.type = "story";
edit.title = titleInput.text;
edit.body = bodyInput.text;
var hashedArray:Array = hashKey("node.save");
node.save(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, edit);
}
public function onNodeSaved(event:ResultEvent):void{
var hashedArray:Array = hashKey("views.get");
views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, "getUserStories");
}
If there is no story selected a new one will be created and saved to the view. After the node is saved it updates the views list. Give all appropriate permissions for these actions (create and edit).
I hope that this is starting point for expanding and using api keys/sessid in service calls from Flex to Drupal. Comments are welcome and I have included a full copy of the mxml and the as files below.
Source zip