Les Web Services en ActionScript 3.0

Très souvent lorsque l’on fait du jeu Facebook, on se retrouve à devoir gérer des communication client-serveur régulièrement. Au lieu d’écrire chaque envoie de requête à plusieurs reprises dans le code, j’ai créé une classe WebService qui me simplifie la tâche. Cette classe n’a pas pour but de réinventer la roue mais de faciliter l’intégration des web services.

Le but est de créer une requête HTML avec une syntaxe la plus simple possible :

webService.createRequest("url_relative_de_la_requete", {var0:val0 ,var1:val1, ...});

Une pile de requêtes?

Une autre problématique, que ma classe gère, est la gestion d’une pile de requêtes. Il m’est déjà arrivé d’avoir affaire à un serveur relativement lent et qui crash car il doit gérer trop de requête simultanément. Pour cette raison, j’ai fait en sorte que ma classe WebService gère une pile de requêtes. De plus, j’ai indiqué l’URL du serveur en variable de classe, car je considère qu’il n’y a qu’un seul serveur par projet (ce qui est vrai dans quasiment tous les cas). Mais dans le cas où ce n’est pas le cas, il suffit d’initialiser urlBase à une chaîne de caractère vide et de passer une url absolue en paramètre à createRequest.

package multiFreid.net
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.net.navigateToURL;

	/**
	 * @author MultiFreid
	 **/
	public class WebService
	{

		public static var urlBase:String;
		protected var pile:Array;
		protected var current:URLRequest;

		/**
		 * Constructor
		 * @param	plateformURL	the plateform url : "http://my.domain.net/"
		 */
		public function WebService(plateformURL:String=null)
		{
			if(plateformURL)
				urlBase = plateformURL;
			pile = new Array();
		}

Pour cela je crée une classe RequestStruct qui me permettra de gérer ma pile. Dans un autre langage de programmation j’aurai géré cela avec une structure de donnée (struct) mais l’AS3 ne gère pas cela. Cette classe n’est pas public et donc est rajouté à la fin de la classe WebService.

//...
}
import flash.net.URLLoader;
import flash.net.URLRequest;
class RequestStruct{

	private var _loader:URLLoader;
	private var _params:Object;
	private var _request:URLRequest;

	public function RequestStruct(urlLoader:URLLoader, r:URLRequest, params:Object=null) {
		_loader = urlLoader;
		_params = params?params:{};
		_request = r;
	}
	public function get request():URLRequest { return _request; }
	public function get loader():URLLoader { return _loader; }
	public function get callbackComplete():Function { return _params.onComplete as Function;}
	public function get callbackError():Function { return _params.onError as Function;
	//d'autres getters pour personnaliser vos paramètres
}

une fois que vous avez cette « structure » on va pouvoir gerer notre pile de requete.

Dans la classe WebService, on crée les getters stackSize et currentRequest qui renvoie respectivement la taille de la pile (int) et l’URLRequest en cours de traitement.

Puis on crée la fonction createRequest qui est la fonction qui va gérer la création des requête.

public function createRequest(relativeURL:String, param:Object=null){
	var loader:URLLoader = new URLLoader();
	var request:URLRequest;
	var url:String;
	url = urlBase?urlBase + relativeURL:relativeURL;
	request = new URLRequest(url);
	//...

Il va falloir maintenant ajouter les URLVariables à l’URLRequest pour cela on doit déterminer si les propriétés passées dans params sont à envoyer avec la requête ou bien s’il s’agit d’un mot clé ou non. Pour cela on crée la fonction isKeyWord (voir fonctions annexes) qui prend en paramètre le nom de la propriété (string) et renvoie un booléen.

	//...
	if (param){
		var variables:URLVariables = new URLVariables();
		for (var prop:String in param) {
			if (!isKeyWord(prop)){
				variables[prop] = param[prop];
			}
		}
		if (param.method && (param.method == URLRequestMethod.GET)){
			request.method = URLRequestMethod.GET;
		}
		else{
			request.method = URLRequestMethod.POST;
		}
		request.data = variables;
	}
	//...

Enfin on ajoute la requête à la pile et on crée les EventListeners (voir fonctions annexes).

	//...
	loader.addEventListener(Event.COMPLETE, onComplete);
	loader.addEventListener(IOErrorEvent.IO_ERROR, onFail);
	if(pile.push(new RequestStruct(loader, request, param))== 1){
		loader.load(request);
		current = request;
	}
}//end of createRequest

Utilisation de WebService

Un petit test avec notre cher ami google :

import multiFreid.net.WebService;
import flash.events.Event;
import flash.net.URLVariables;
import flash.net.URLRequestMethod;
import flash.net.navigateToURL;

WebService.urlBase="http://www.google.fr/";

var ws:WebService = new WebService();
ws.createRequest("search", {q:"test", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test1", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test2", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test3", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test4", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test5", onComplete:onComplete, method:URLRequestMethod.GET});
ws.createRequest("search", {q:"test6", onComplete:onComplete, method:URLRequestMethod.GET});

function onComplete(evt:Event):void
{
	trace("completed request :: ", ws.currentRequest.url, (ws.currentRequest.data as URLVariables).toString());
	navigateToURL(ws.currentRequest, "_blank")
}

vous pouvez admirer les fenêtres qui s’ouvre les unes à la suite !

N.B. : La fonction onComplete sera toujours exécutée avant le changement de requête.

Les fonctions annexes

/**
 * Test whether the string is a key word or not
 * @param	st	string to test
 * @return	true if 'st' is a keyword
 */
protected function isKeyWord(st:String):Boolean
{
	return st == "onComplete" || st == "onError" || st == "method"; //To complete when keyword are added
}
/**
 * the callback function executed when the current RequestStruct dispatch Event.COMPLETE
 * @param	evt		the Event.
 */
protected function onComplete(evt:Event) {
	var lo:RequestStruct = pile.shift() as RequestStruct;
	trace("Network ::", "REQUETES RESTANTES :", pile.length);
	if (lo.callbackComplete != null) {
		lo.callbackComplete(evt);
	}
	try
	{
		var next:RequestStruct = pile[0] as RequestStruct;
		current = next.request;
		next.loader.load(next.request);
	}
	catch (err:Error)
	{
		current=null;
	}
}
/**
 * the callback function executed when the current RequestStruct dispatch IOErrorEvent.IO_ERROR
 * @param	evt		the Event to dispatch
 */
protected function onFail(evt:IOErrorEvent) {
	var lo:RequestStruct = pile.shift() as RequestStruct;
	if (lo.callbackError != null) {
		lo.callbackError(evt);
	}
	try
	{
		var next:RequestStruct = pile[0] as RequestStruct;
		current = next.request;
		next.loader.load(next.request);
	}
	catch (err:Error)
	{
		current=null;
	}
}
/**
* Get the URL of the current RequestStruct
**/
public function get currentRequest():URLRequest{return current;}
/**
 * Get the size of the stack of request
**/
public function get stackSize():uint{return pile.length;}

Vous pouvez trouver le fichier .as complet et fonctionnel ici