Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

4déc/1022

AIR pour mobile – Requêtes HTTP qui fonctionnent en Wifi mais pas en 3G (HTTPStatusEvent 403) [Résolu]

Alors voilà sûrement le bug le plus obscur et le plus improbable que j'ai jamais résolu. Entre le diagnostic, le debug peu facile et la résolution, je pense que j'ai bien passé 4 heures sur ce bug venu de nulle part.

Voici quelques explications qui devraient intéresser tout les développeurs d'applications mobile (et c'est presque une "exclusivité" puisque ce bug n'est référencé dans aucune forum, aucune bugbase Adobe).

Diagnostic

Ce bug ce produit uniquement sur les applications mobiles AIR pour Android et AIR pour iOS, selon certaines conditions.

Pour vous présenter rapidement mon application, celle-ci commence par faire des appels à des fichiers XML, puis à des fichiers Perl qui renvoient du XML (en faisant un pont vers Java) et des fichiers image. L'application fonctionnait correctement sur mon HTC Desire sous Android (chez SFR) mais avait des problèmes de connexion sur iPad (chez Bouygues Telecom en 3G). Parfois, l'application "perd la connexion" de manière complètement aléatoire et il faut relancer l'application.

Entre temps, j'ai lâché mon vieil HTC Magic pour un Samsung Galaxy S sous Android 2.2, toujours chez SFR. Pour tester d'autres développements sans devoir attendre la (longue) compilation pour iPad, je décide de compiler un APK pour le transférer sur mon téléphone.

Lancement de l'application en connexion Wifi, aucun problème l'application fonctionne de manière très fluide. Comme je suis un peu loin de ma box Numericable et que le signal Wifi n'est pas très bon, je décide de désactiver le Wifi et de passer en 3G (connexion maximale). Lancement de l'application … le premier chargement s'effectue mais la suite de l'application échoue, comme si les informations que je recevais en XML n'étaient pas les bonnes.

Recherche de la solution

Je décide donc d'aller voir du côté de mon URLLoader. Celui-ci ne renvoyait ni un SecurityErrorEvent, ni un IOErrorEvent mais bien un Event.COMPLETE. Après passage sur la propriété "data" de mon URLLoader censée contenir mon XML, je m'aperçoit que celle-ci est vide.

Après une longue recherche sur le net, je tombe sur un des seuls threads qui parlent d'un problème similaire:

URLLoader & Loader sans wifi

La personne qui a lancé le thread a le même comportement bizarre Wifi / 3G. Celui-ci signale un élément important que je n'avais pas noté, il reçoit un évènement HTTPStatusEvent.HTTP_STATUS avec comme status HTTP 403. Pour ceux qui ne s'en rappellent plus, 403 correspond à un retour serveur Forbidden, la plupart du temps pour un problème de droit d'accès sur un fichier / répertoire. Mais malheureusement, ce thread se termine en queue de poisson car personne n'a trouvé la solution (ils vont être contents de voir ce billet ^^). Je vérifie et je reçoit moi aussi un 403 de la part du serveur.

Comme on dirait que le serveur renvoie un 403, je vais voir logiquement du côté de mon serveur HTTP, Apache en l'occurrence. Un petit tour dans le fichier access.log m'indique qu'aucune trame n'a été reçue! Rien dans les log, aucun code d'erreur HTTP. Je reçois donc un Event.COMPLETE alors que le serveur ne me répond même pas.

Bien inquiété par ce problème (il fallait quand même que je le corrige), je continue mes recherches pendant une petite heure. J'apprends sur certains forums que certains opérateurs téléphoniques renvoient parfois des erreurs HTTP 403 pour protéger leurs systèmes . Cela intervient avec des applications codées avec d'autres langages ou même en navigation mobile classique. Cela correspond bien à mon problème de 3G / Wifi, je continue donc à chercher de ce côté là.

Après de longues recherches, je tombe sur ce forum de BeyondPod for Android Support Forum:

Impossible to update when WiFi is off

Et la lumière vient d'un utilisateur nommé "StefanK" qui nous dit:

This appears to be user agent related issue. SFR does appears to check
the user agent strings and does not allow any connections if the User
Agent is not on the "approved" list.

One of BeyondPod users figured that putting this User Agent (without
the >> and <<):

>>>Mozilla/5.0 (Linux; U; Android 2.2; fr-fr; Desire_A8181 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1<<<

worked for him on SFR.

You can set the user agent in BeyondPod’s advanced settings: Menu >
More > About BeyondPod > Tap and hold on the line that has the version
number > Advanced Settings > User Agent.

Stefan

On dirait que l'on chauffe, le bug est reproduit et en plus chez SFR. Il suffit selon lui de modifier le User-Agent HTTP. Les opérateurs comme SFR doivent vérifier les headers HTTP et interdire les connexions "applicatives" sur les réseaux 3G pour éviter que les PC ne profitent de la connexion 3G d'un portable comme connexion internet (à mon avis).

Je commence à rechercher comment faire cette modification en AS3 dans la documentation et les nouvelles sont plutôt mauvaises, notamment quand je tombe sur la documentation de URLRequestHeader:

In Flash Player and in Adobe AIR content outside of the application security sandbox, the following request headers cannot be used, and the restricted terms are not case-sensitive (for example, Get, get, and GET are all not allowed). Also, hyphenated terms apply if an underscore character is used (for example, both Content-Length and Content_Length are not allowed):

Accept-Charset, Accept-Encoding, Accept-Ranges, Age, Allow, Allowed, Authorization, Charge-To, Connect, Connection, Content-Length, Content-Location, Content-Range, Cookie, Date, Delete, ETag, Expect, Get, Head, Host, If-Modified-Since, Keep-Alive, Last-Modified, Location, Max-Forwards, Options, Origin, Post, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, Public, Put, Range, Referer, Request-Range, Retry-After, Server, TE, Trace, Trailer, Transfer-Encoding, Upgrade, URI, User-Agent, Vary, Via, Warning, WWW-Authenticate, x-flash-version.

Certains trouvent des solutions sur le net en passant par un proxy (un proxy PHP par exemple) qui va modifier les headers HTTP et faire suivre la requête. Ce n'est pas une solution pour moi car cela voudrait dire que je dois modifier tous mes URLLoader et Loader et implémenter un proxy: no way.

La solution

En allant fouiner plus loin dans les documentations Adobe, je trouve que l'on peut en fait modifier le User-Agent HTTP mais seulement dans l'API Adobe AIR:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLRequest.html#userAgent

Cette solution fonctionne et mes XML reviennent correctement. Excellent, mais cela me demande de modifier tous mes Loader et URLLoader et en plus, cela m'oblige à lier certaines de mes librairies au framework AIR alors qu'elles étaient auparavant pures AS3, pas top. La solution ultime vient de la classe URLRequestDefaults:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLRequestDefaults.html#userAgent

Cette variable static permet de donner un userAgent par défaut à tous les URLLoader et Loader, exactement ce qu'il me fallait :D .

Voici donc la solution à mon problème qui tient en fait en une ligne:

URLRequestDefaults.userAgent = "Mozilla/5.0 (Linux; U; Android 2.2; fr-fr; Desire_A8181 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1";

Voilà, si votre application marche très bien chez vous mais ne fonctionne pas chez certains utilisateur (qui ne vont pas manquer de vous le signaler dans les commentaires), la solution est peut-être là !

Articles similaires

Commentaires (22) Trackbacks (0)
  1. Super !

    ça m'a enlevé une belle épine du pied .

    Merci encore

  2. Il est tellement vicieux ce bug :)

    Fabien

  3. je ne comprend pas la méthode es que quelqun peut m expliquer etape par etape parce g exactement ce bug c vraiment génant :( please

  4. Salut,
    il te suffit de mettre ce code à l'initialisation de l'application:
    URLRequestDefaults.userAgent = "Mozilla/5.0 (Linux; U; Android 2.2; fr-fr; Desire_A8181 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1";

    C'est écrit dans l'article
    Fabien

  5. je vais te praitre stupide mais c'est quelle application et es que apres toute les autres aplication marche en 3g??

  6. g un sonny ericsson xperia x8 et plain d appli ne fonctionne pas en 3g

  7. Salut,
    Et bien ton projet mobile est une Application, si tu regarde le MXML principal (MonNomDeProjet.mxml), tu devrais avoir en haut Application ou MobileApplication. Ca c'est ton application. Après, si tu ne sais pas ajouter un évènement pour capter l'initialisation de ton application, je te conseille plutôt de lire le reste des tutoriaux.
    Les applications AIR pour Android qui ont ce code fonctionnent ensuite en 3G. Mais si tu télécharges une application AIR sur Android sur le Market qui n'a pas ce code, elle ne fonctionnera pas en 3G (suivant certaines conditions comme expliqué dans l'article).

    Fabien

  8. Si ces applications ne sont pas des applications basées sur AIR, le problème ne vient pas de là, voit avec ton opérateur.

    Fabien

  9. je ne trouve pas l application projet sur l android market je suis vraiment novice et je ne connait pas tou vos termes :(

  10. Je pense que votre problème n'a aucun lien avec cet article. Si certaines de vos applications Android ne fonctionne pas en 3G, veuillez contacter votre opérateur.

    Merci
    Fabien

  11. Dans le même genre…

    Le code ci-dessous fonctionne en flex, mais pas en Air. Quelqu'un a une idée ?

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
    3.                            xmlns:s="library://ns.adobe.com/flex/spark"
    4.                            xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    5.         <fx:Declarations>
    6.                 <!-- Placer ici les éléments non visuels (services et objets de valeur, par exemple). -->
    7.         </fx:Declarations>
    8.         <fx:Script>
    9.                 <![CDATA[
    10.                        
    11.                         public function say(phrase:String):String {
    12.                                 if (phrase.length> 100) throw new Error("Google currently only supports phrases less than 100 characters in length.");
    13.                                 var language:String = "FR";
    14.                                 var qs:String = "tl=" + language + "&q=";
    15.                                 qs += encodeURI(phrase);
    16.                                 return "http://translate.google.com/translate_tts?&quot; + qs;
    17.                         }
    18.                        
    19.                         private function playSpeech(evt:Event):void {
    20.                                 evt.target.play();
    21.                         }
    22.                        
    23.                         protected function parle(event:MouseEvent):void
    24.                         {
    25.                                 var urlR = new URLRequest(say(texteIn.text));
    26.                                 var sound:Sound = new Sound();
    27.                                 sound.addEventListener(Event.COMPLETE, playSpeech);
    28.                                 sound.load(urlR);
    29.                         }
    30.                 ]]>
    31.         </fx:Script>
    32.        
    33.         <fx:Declarations>
    34.                 <!-- Placer ici les éléments non visuels (services et objets de valeur, par exemple). -->
    35.         </fx:Declarations>
    36.         <s:Label x="10" y="29" text="Le texte à dire :"/>
    37.         <s:TextInput id="texteIn" x="101" y="19" width="548" text="une phrase de test"/>
    38.         <s:Button x="294" y="49" label="Parle" click="parle(event)"/>
    39. </s:Application>

    L'erreur renvoyée en AIR est du au serveur de google qui n'accepte pas la requête (enfin c'est ce que je pense)

  12. Salut,

    Où se situe l'erreur? Si il manque du code, envoie le moi par mail (bas de page) pour que je l'intègre

    Fabien

  13. J'ai envoyé le code complet en répponse à la notification.

    Sinon, l'erreur se produit sur le sound.load(urlR);

    L'event erreur :

    [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2032: Stream Error. URL: http://translate.google.com/translate_tts?tl=FR&q=une%2520phrase%2520de%2520test" errorID=2032]

  14. Salut,
    l'url ne fonctionne pas dans un navigateur chez moi (problème d'encodage?)

    Fabien

  15. Si tu tape cette url ça doit marcher:

    http://translate.google.com/translate_tts?tl=FR&q=une phrase de test

  16. Je pense que cela ne fonctionne pas car ce domaine ne spécifie pas de crossdomain.xml

    Fabien

  17. Pour une application flex ça pourrait se comprendre, mais pour une application AIR pourquoi avoir besoin d'un crossdomain sur le serveur ?

    De plus, ce code fonctionne en Flex (exécuté sur un serveur).

  18. Hello,
    il me semble que j'ai exactement le même problème mais avec des requêtes remoting (AMFPHP) du coup je ne sais pas comment modifier ce fameux userAgent... J'ai bien sûr essayé de modifier URLRequestDefaults.userAgent mais sans succès.

    Une idée ?

  19. Salut Quentin,

    tu as fais la modification au bon moment (avant l'envoi des trames vers AMFPHP)? A part ça, je ne vois pas trop. Regarder sur le serveur les logs d'apache voir s'il a reçu quelque chose ou non.

    Fabien

  20. Le truc c'est qu’apparemment les appels remoting ne prennent pas en compte les URLRequestDefaults ! Pour l'instant je n'ai pas accès aux logs d'Apache donc je suis un peu coincé.

    La suite au prochain épisode !

  21. Un énorme merci pour cette explication et cette solution, j'avais le même souci avec mon appli et la soluce marche nickel !

    J'aurais bien galéré à résoudre ça tout seul, ça c'est sûr.
    Bravo !

  22. C'était tellement tordu que j'ai préféré partager la solution :)
    Merci,
    Fabien


Leave a comment

(required)

Aucun trackbacks pour l'instant