Custom Proxy dans .NET Remoting

(version Imprimable)
Introduction

Le but de cet article est de vous permettre d'intercepter les appels vers des objets distants afin d'effectuer un traitement spécifique. Nous utiliserons pour cela le code du serveur Hello écrit dans un arcticle précédent (.NET Remoting vs RMI).

 

Qu'est-ce qu'un Proxy .NET Remoting ?

Les proxy sont des objets créés lorsque le client active un objet distant. Il agit comme la représentation locale d'un objet distant et assure que tous les appels effectués sur le Proxy sont acheminés vers ce dernier. Lorsque le client active un objet distant, le Framework crée une instance locale de la classe TransparentProxy aussitôt chargée dans la CLR. Tous les appels de méthodes sont interceptés par le Runtime. Ainsi, l'appel est analysé dans le but de déterminer si la méthode est valide et si une instance de l'objet distant réside dans le même domaine d'application en tant que Proxy. Si c'est le cas, un appel direct in-process est effectué vers l'objet en question (correspond aux "client activated objects"). Cela évite d'avoir le surcoût correspondant à un appel distribué alors que l'objet est local. Ce problème est bien connu des développeurs RMI et correspond aux EJB locaux dans la spécification EJB 2.0.

Si l'objet se trouve dans un domaine d'application différent, les paramètres d'appels sur la pile sont emballés dans un objet de type IMessage et transférés à la classe RealProxy en utilisant sa méthode invoke(). Il existe une implémentation par défaut de cette classe consistant à transférer l'appel vers l'objet distant. Le code suivant nous illustre l'opération réalisée dans la méthode : Activator.GetObject()

 

 

Client.cs

 

HelloServer helloProxy = (HelloServer)Activator.GetObject(typeof(HelloServer), 

                                                "tcp://localhost:8085/SayHello");

 

Code situé dans le Framework .NET Remoting

(...)

 

public object Activator.getObject(Type t, string URL) {

   (...)

   MyRealProxy proxy = new MyRealProxy(URL,t);

   Hello obj = (Hello)proxy.GetTransparentProxy();

   return obj ;

   (...)

}

 

 

Implémenter un Custom Proxy 

TransparentProxy et RealProxy sont créés de manière interne lorsque l'objet est activé mais seul le TransparentProxy est renvoyé au client par l'intermédiaire de la méthode GetTransparentProxy() de l'objet RealProxy. L'idée ici, consistera à développer notre propre classe RealProxy et à l'instancier.

 

Le schéma suivant vous illustre les différentes étapes intervenant dans la chaîne d'invocation d'une méthode distante via Remoting.

 

 

La classe TransparentProxy est une classe interne ne pouvant être enrichie ou étendue. La classe RealProxy servira à cet effet. Ainsi, dans le cas où vous souhaiteriez implémenter les fonctionnalités suivantes :

- La répartition de charge : il suffira de tester dans notre Custom Proxy si tel ou tel serveur est chargé dans le but de répartir efficacement les traitements

- L'interception des paramètres pour ajouter certaines informations (traduction linguistique des chaînes de caractères en temps réel ;-) par exemple)

- Etendre les fonctionnalités de sécurité existantes pour acheminer uniquement les appels vers les serveurs auprès desquels le client est authentifié, etc. ...

 

Nous allons donc écrire un MyRealProxy qui dérivera de la classe RealProxy et redéfinir la méthode invoke(). Ensuite, il faudra faire en sorte que le client utilise notre classe de manière aussi transparente que possible. Il existe deux façons de procéder :

- En interceptant l'opération new à l'aide d'un attribut ProxyAttribute

- En réalisant l'opération de construction manuellement dans le code du client à l'aide de l'instruction new RealProxy()

 

Nous choisirons la dernière solution par soucis de simplicité. Voici le code Client :

 

 

 

public class CustomProxy

    {

        public static int Main(String[] args)

        {

            ChannelServices.RegisterChannel(new TcpChannel());

 

            Console.WriteLine("Generate a new MyProxy using the Type");

            Type type = typeof(HelloServer);

            String url = "tcp://localhost:8085/SayHello";

 

            MyRealProxy myProxy = new MyRealProxy(type, url);       

 

            Console.WriteLine("Obtain the transparent proxy from myProxy");

            

            HelloServer helloService =  (HelloServer) myProxy.GetTransparentProxy();

 

            String str = helloService.HelloMethod("coucou bilou");

                  return 0 ;

        }

    }

 

 

Le client se charge de créer l'objet MyProxy et d'effectuer l'appel à l'aide du TransparentProxy. Regardons maintenant le code tant attendu de la classe MyRealProxy.

 

 

using System;

using System.Collections;

using System.Threading;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.Runtime.Remoting.Proxies;

using System.Runtime.Remoting.Messaging;

using NETRemotingSamples;

 

    //

    // MyRealProxy hérite de RealProxy : classe utilisée par défaut

      //

    public class MyRealProxy : RealProxy

    {

            // URL du serveur distribué

            String _url;

           

            // Correspond au chaineur par défaut du HttpSender

            IMessageSink _messageSink;

 

        public MyRealProxy(Type type, String url): base(type)

        {

            // Le constructeur fait appel au constructeur de la classe mère

            // qui a besoin du type de l'objet

 

            _url = url;

 

            // On parcours la liste des channels courants pour rechercher le nôtre

            // Espérons que Microsoft fournira plus tard une méthode

            // permettant de faire : ChannelServices.getCurrentChannel()?

                 

            IChannel[] registeredChannels = ChannelServices.RegisteredChannels;

 

            foreach (IChannel channel in registeredChannels )

                  {

                    if (channel is IChannelSender)

                    {

                        IChannelSender channelSender = (IChannelSender)channel;

                        // Un chaineur a été trouvé sur notre channel, on l'utilise

                        string objectURI;

                        _messageSink = channelSender.CreateMessageSink(

                                                        _url, null, out objectURI);

                        if (_messageSink != null)

                             break;

                    }

                  }

        }

 

 

 

        // Méthode appelé par le Framework avant chaque appel du client

        public override IMessage Invoke(IMessage msg)

        {

            // Positionne l'url du serveur dans l'objet IMessage

            // Pas terrible comme démarche, espérons que dans les prochaines versions

            // cela sera masqué par des méthodes plus appropriées : setURL(..)

 

            IDictionary d = msg.Properties;

            IDictionaryEnumerator e = (IDictionaryEnumerator) d.GetEnumerator();

            d["__Uri"] = _url;

                 

            // Je mets un message personnalisé histoire de prouver que j'y passe

            Console.WriteLine("Coucou l'appel est intercepté ! ");

 

            // Réalise l'appel distant en passant le paramètre              

            IMessage retMsg = _messageSink.SyncProcessMessage(msg);

                 

            return retMsg;

        }

    }

 

Explications

Quelques explications sont cependant nécessaires même si seule la méthode invoke() est pertinente dans cet exemple. Lors de la construction du Proxy, nous nous chargeons d'envoyer le type de l'objet distant à la classe mère (base(t)). Cette dernière a besoin de cette information capitale afin d'implémenter correctement la méthode GetTransparentProxy()) pour renvoyer un proxy correspondant au bon type. Ensuite, un chaineur de message (MessageSink) correspondant  à notre channel est recherché et créé. Un chaîneur est un objet permettant de déléguer l'appel au Framework en utilisant plusieurs classes chaînées entre-elles. Cela vous permet de redéfinir vous même votre chaîneur afin d'effectuer des appels spécifiques (asynchrones, ...). Vous trouverez ici un exemple de chaîneur pertinent redéfinit pour permettre de débogguer les appels de méthode. Une fois le chaîneur récupéré, il ne nous reste plus qu'à invoquer la méthode distante avec SyncProcessMessage(msg).

 

Lors de l'exécution du Client, vous obtenez à l'écran un joli message "Coucou l'appel est intercepté" émis par votre Custom Proxy.

 

Conclusion

.NET Remoting propose un mécanisme très élaboré permettant d'effectuer des opérations d'interception de messages correspondant à l'implémentation d'un Custom Dynamic Proxy en Java. Vous savez maintenant comment faire, à vous de trouver des idées d'implémentation pertinente !    

Auteur : Sami Jaber