Message populaire Lazer Posté(e) le 21 juin 2018 Message populaire Signaler Posté(e) le 21 juin 2018 (modifié) Utilisation de net.HTTPClient() asynchrone - dans une scène en LUA sur HC2/HC3 - - dans un QuickApp sur HC3 - Dans les scènes, Fibaro ne nous laisse pas le choix, dès que l'on veut faire des appels HTTP, on est obligé d'utiliser la fonction asynchronse net.HTTPClient(). La fonction Net.FHTTP() synchrone utilisée dans les VD n'est pas disponible dans les scènes. Toutefois, l'avantage de net.HTTPClient() est d'accepter les connexions sécurisées HTTPS devenues majoritaires sur Internet. Exemple de code simple pour une requête de type GET local http = net.HTTPClient() http:request("http://1.2.3.4/url", { success = function(response) if response.status == 200 then print('OK, réponse : '.. response.data) else print("Erreur : status=" .. tostring(response.status)) end end, error = function(err) print("Erreur : " .. err) end, options = { method = 'GET' } }) L'exemple suivant effectue une requête de type POST permettant d'envoyer des données vers le site distant. De plus, la fonction success() récupère les données de type JSON en vue d'un traitement ultérieur (notez que les données envoyées vers le site Web et les données reçues depuis le site Web sont différentes, cela dépend de l'application qui tourne sur le site) : -- Les données à envoyer au formulaire local myJson = { "couleurs": { [1] = "bleu", [2] = "blanc", [3] = "rouge" }, "fruits": { [1] = "pomme", [2] = "banane" }, } -- Appel HTTPS local http = net.HTTPClient() http:request("https://www.domaine.com/url", { success = function(response) if response.status == 200 then if response.data and response.data ~= "" then print('Retour : '.. response.data) local jsonTable = json.decode(response.data) -- Parcours de la table JSON local k, v for k, v in pairs(jsonTable) do print("key = " .. k .. " - type(v) = " .. type(v)) end -- Ici la suite du code, exécuté en asynchrone, donc après la fin de l'exécution du code appelant http:request() -- ... else print("Error : empty response data") end else print("Erreur : status=" .. tostring(response.status)) end end, error = function(err) print("Erreur : " .. err) end, options = { method = 'POST', timeout = 5000, checkCertificate = false, headers = { ["content-type"] = 'application/x-www-form-urlencoded;', ["Authorization"] = "Basic YWRtaW46cGFzc3dvcmQ=" -- username:password encodé en Base64 (admin:password) }, data = json.encode(myJson) } }) -- Ici la suite du code, exécuté en synchrone, donc avant l'exécution du contenu de la fonction success() -- ... On remarque dans les options que l'on peut choisir les paramètres suivants : method : obligatoire : GET ou PUT ou POST ou DELETE timeout : facultatif : délai d'attente en millisecondes avant échec de la requête. Peut être utile avec certains serveurs un peu trop lents à répondre. Dans le doute, inutile d'utiliser ce paramètre. checkCertificate : facultatif : true ou false, permet d'ignorer les alertes de sécurité sur les certificats auto-signés (non reconnus pas une autorité de certification approuvée) headers : facultatif : permet de passer le(s) en-tête(s) HTTP de son choix vers le site Web distant. Si vous ne savez pas ce qu'est un Header, c'est que vous n'avez probablement pas besoin d'envoyer de header, donc ignorez ce paramètre. data : facultatif : ce sont les données à envoyer dans les formulaires POST et PUT sous forme de chaine de caractères. Donc si les données sont de type tableau JSON, il faut les encoder avec json.encode(). Asynchronisme net.HTTPClient() est asynchrone, le code dans les fonctions success() et error() appelées en callback s'exécute toujours après la fin de l'exécution du thread principal. Quand on commence à programmer en asynchrone, il ne faut plus jamais utiliser de fonctions synchrones comme sleep(), sous peine de comportement surprenant. Préférer à la place l'emploi de la fonction settimout() qui est elle-même asynchrone (chercher les exemples sur le forum) La bonne pratique quand on programme en asynchrone est la suivante : Après un appel à net.HTTPClient(), le code devrait se terminer le plus rapidement possible afin de laisser la main à la fonction success() appelée en callback de net.HTTPClient(). La suite du code se déroule donc dans la fonction success(). Celle-ci, à sont tour, peut faire d'autres appels à net.HTTPClient() ou settimeout() pour déclencher de nouveaux appels de fonctions en callback asynchrone. Etc... C'est la technique que j'ai employé dans mes scènes Watchdog et Yamaha MusicCast, partagées sur le forum. C'est une certaine gymnastique qui n'est pas évidente au début, et oblige à revoir toute la structure de son code LUA. En complément je vous invite à lire ce sujet sur la protection des requêtes http avec pcall() : Modifié le 13 avril 2021 par Lazer 9 1
BenjyNet Posté(e) le 21 juin 2018 Signaler Posté(e) le 21 juin 2018 Ah c'est cool comme mini tuto ça... je bookmark
pepite Posté(e) le 21 juin 2018 Signaler Posté(e) le 21 juin 2018 Ahah tu ne peux pas parler à Alexa alors hop un petit tuto Merci c est top :-) Envoyé de mon BND-L21 en utilisant Tapatalk
Bloug Posté(e) le 22 juin 2018 Signaler Posté(e) le 22 juin 2018 merci pour le tuto, Petite question , si je récupère une donné avec un GET : local etatAlyssa = jsonTable.zones[1].state pour la modifier dois je utiliser la meme structure que le GET ? local myJson = { jsonTable.zones[1].state = "1" } merci
Lazer Posté(e) le 23 juin 2018 Auteur Signaler Posté(e) le 23 juin 2018 Je ne comprends pas bien ta question ?!
Bloug Posté(e) le 23 juin 2018 Signaler Posté(e) le 23 juin 2018 (modifié) désolé, pour etre plus clair, je souhaite comprendre le fonctionnent, j'utilise pour récupérer des info un GET et un element de la table : local etatAlyssa = jsonTable.zones[1].state Si je souhaite modifier la valeur de l'élément et lui attribuer la valeur 1 ou 0 => state = 1 j'utilise donc la seconde parti de ton code avec POST et la valeur dans le myJson EX : local myJson = { "couleurs": { [1] = "bleu", [2] = "blanc", [3] = "rouge" }, "fruits": { [1] = "pomme", [2] = "banane" }, } donc pour mon cas je dois utiliser la même structure qu'avec GET ? ou il faut adapter comme dans ton ex avec les accolades : jsonTable.zones[1].state jsonTable.zones[1].state modif : {"zones":[{"state":"0"}]} ou peut etre qu'il n' a aucun rapport entre un GET et POST... Modifié le 23 juin 2018 par Bloug
Lazer Posté(e) le 23 juin 2018 Auteur Signaler Posté(e) le 23 juin 2018 Euh... mais en fait tu récupère un JSON via une requête GET, tu veux modifier une valeur, et le réinjecter dans une requête POST, c'est ça ?
Lazer Posté(e) le 23 juin 2018 Auteur Signaler Posté(e) le 23 juin 2018 OK Donc il faut que tu imbriques les 2 fonctions l'une dans l'autre (en vertu de ce qui a été dit... le code DOIT continuer dans la fonction success() Quand tu décodes le tableau dans la première fonction success() : local jsonTable = json.decode(response.data) Juste après tu modifies la valeur : jsonTable.zones[1].state = "1" Puis tu donnes l'ensemble (avec json.encode()) dans les options de ton second appel http : options = { method = 'POST', data = json.encode(jsonTable) } })
Bloug Posté(e) le 23 juin 2018 Signaler Posté(e) le 23 juin 2018 Bon après beaucoup de tests avec GET puis POST puis qu'avec POST et une erreur 401 j'ai ajouter des éléments dans le headers pour bloqué sur du 400 ... Comme le souligne le grand philosophe espagnol : Mas banda ! j'arrêter de polluer le topic ....
Lazer Posté(e) le 23 juin 2018 Auteur Signaler Posté(e) le 23 juin 2018 Partage ton code et un maximum de détail, les logs, etc Tu peux faire ça dans ton topic d'origine. 1
jucom Posté(e) le 1 mars 2021 Signaler Posté(e) le 1 mars 2021 (modifié) Bonsoir, un grand merci pour ce post qui pour moi est d'une grande utilité car je ne pars de 0 sur le code... J'ai passé bcp d'heures a tester de nombreuses possibilités mais j'ai toujours un message d'erreur. Je cherche a envoyer une simple requête HTTP pour contourner un problème de compatibilité avec mon synology (maj DSM7) L'idée c'est de pouvoir déclencher une action préconfigurée dans surveillance station depuis une HC3 via une requête HTTPS En clair la HC3 remonte un mouvement PIR et pousse la requête HTTPS qui déclenche l'action préconfigurée dans surveillance station. le requête est la suivante : https://ADRESSEipSYNOLOGY/webapi/entry.cgi?api=SYNO.SurveillanceStation.ExternalEvent&method="Trigger"&version=1&eventId=1&eventName="This is external event1"&account="{account}"&password="{password}" la requête fonctionne dans Firefox, j'ai le bon retour "success" et l'action préconfigurée fonctionne mais via ma scene lua j'ai trs une erreur. la dernière en date ; [01.03.2021] [22:32:59] [ERROR] [SCENE96]: (load):2: ')' expected near 'Trigger' ma scene lua ; en déclaration j'ai ; { conditions = { { id = 216, isTrigger = true, operator = "==", property = "value", type = "device", value = true } }, operator = "all" } en action j'ai ; local http = net.HTTPClient() http:request("https://ADRESSEipSYNOLOGY/webapi/entry.cgi?api=SYNO.SurveillanceStation.ExternalEvent&method="Trigger"&version=1&eventId=1&eventName="This is external event1"&account="MONLOGIN"&password="MONPASSWORD"", { success = function(response) if response.status == 200 then fibaro:debug('OK, réponse : '.. response.data) else fibaro:debug("Erreur : status=" .. tostring(response.status)) end end, error = function(err) fibaro:debug("Erreur : " .. err) end, options = { method = 'GET' } }) si quelqu'un pouvais m'aider ca serai vraiment cool car je ne m'en sort pas. J'imagine bien que ca doit paraitre hyper simple mais je ne suis vraiment pas doué. Merci à vous ! Modifié le 1 mars 2021 par jucom modif post lua
Lazer Posté(e) le 1 mars 2021 Auteur Signaler Posté(e) le 1 mars 2021 Pour coller ton code dans le forum, il faut utiliser les balises </> dans la barre au dessus de la zone d'édition : Puis choisir LUA dans la liste pour obtenir la coloration du code : Car en l'état, c'est illisible, et il manque les numéros de ligne pour se repérer par rapport aux erreurs qui te sont remontées. Par ailleurs, sans même tenter de lire ton code, il y a un caractère étrange qui me saute aux yeux. Pas sûr que ça soit normal, et en tout cas l'interpréteur LUA n'en voudra pas si tu l'as collé tel quel sur la box :
jucom Posté(e) le 1 mars 2021 Signaler Posté(e) le 1 mars 2021 ok sorry, c'est noté. j'ai modif mon post.
jucom Posté(e) le 2 mars 2021 Signaler Posté(e) le 2 mars 2021 je commence a comprendre que le problème c'est la requête HTTPS en elle même car elle contient des " un peu partout. Elle n'est pas prise en compte dans son intégralité et du coup le reste de la requête génère des erreurs. je continu a chercher mais je ne suis pas certain d’être capable de faire en sorte que net.HTTPClient() puisse répondre à mon besoin si tenté que ça soit possible. je vais créer un post à part à part pour ne pas polluer ici.
henri-allauch Posté(e) le 5 mars 2021 Signaler Posté(e) le 5 mars 2021 Soit un QA contenant x fonctions avec des appel HTTP différents Ces fonctions étant appelées (aléatoire) par d'autres QA Qu'elle est la bonne écriture le type 1 ou le type 2 ( ou aucune des 2 ) Je n'ai pas trouvé le net.HTTPClient() dans NetWork Monitor !! QA Ecriture type 1 ------------------- http = net.HTTPClient() QuickApp:fonction1 () http:request( code... ) end QuickApp:fonction2 () http:request( code... ) end QuickApp:fonction2 () http:request( code... ) end QA Ecriture type 2 --------------------- QuickApp:fonction1 () http = net.HTTPClient() http:request( code ... ) end QuickApp:fonction2 () http = net.HTTPClient() http:request( code... ) end QuickApp:fonction2 () http = net.HTTPClient() http:request( code... ) end
Lazer Posté(e) le 5 mars 2021 Auteur Signaler Posté(e) le 5 mars 2021 Pas sûr de moi, mais à priori utiliser une seule variable HTTPClient pour l'ensemble du QuickApp est valable, donc l'option n°1. Cela dit l'option 2 est valable aussi, mais tu vas redéfinir un nouvel objet à chaque fois, donc ça prend du temps à initialiser, ça consomme plus de RAM, etc. De mémoire il me semble que c'est cette dernières option que j'utilise dans Network Monitor (mais c'est le seul, tous mes autres QuickApps ont un seul objet HTTPClient global) Cependant, attention il y a une (grosse) erreur dans ton code dans l'option 2 Tu définis ta variable ainsi au sein de chaque fonction : http = net.HTTPClient() Donc... http est global ! Si bien qu'à chaque appel de l'une des autres fonctions, tu redéfinies... la même variable ! Bref, du travail inutile. Autre chose, de façon générale, il faut éviter les variables globales : - on ne maitrise pas leur portée, elle peuvent entrer en conflit avec d'autres variables du même nom mais dont la portée serait locale... soit dans une fonction, une boucle imbriquée, ou bien carrément dans un autre fichier (puisque les QA peuvent maintenant accueillir plusieurs fichiers LUA) - cela consomme plus de RAM, car une variable globale n'est jamais libérée par le Garbage Collector (par définition, elle doit rester tout le temps accessible par tout le monde au sein du programme.... sauf si on la détruit explicitement avec http = nil, ce que personne ne pense à faire) - et enfin, l'accès aux variables globales est plus lent (les variables globales sont stockées dans un super tableau _G que LUA doit parcourir pour trouver la bonne entrée) Donc la bonne syntaxe dans chaque fonction pour l'option 2 serait : local http = net.HTTPClient() De façon similaire, dans la première option, tu as aussi défini une variable globale. Il vaudrait mieux la stocker dans le QuickApp, lors de son initialisation : QuickApp:onInit() self.http = net.HTTPClient() end Et on appelle ensuite self.http à chaque fois que l'on veut s'en servir.
henri-allauch Posté(e) le 5 mars 2021 Signaler Posté(e) le 5 mars 2021 Le code c'était un squelete un peu simpliste OUI l'idée c'est de le mettre comme tu le montre et d'utiliser self.http dans les fonctions QuickApp:onInit() self.http = net.HTTPClient() end mais j'avais un doute avec des appels concurrents et aléatoires sur les fonctions du QA Le = nil a l'époque des pertes de memoire en HC2 j'en avait mis de partout mais si on declare dans le init cela ne me semble plus utile dans un QA Ce qui m'a fait poser la question c'est la lecture de l'exemple de la doc fibaro QA ou il disent : -- An example of a GET inquiry -- self.http must have been previously created by net.HTTP function QuickApp:getQuote() Le net.HTTP est bien en dehors de la fonction alors que moi je le m'étais dans chaque fonction
jjacques68 Posté(e) le 5 mars 2021 Signaler Posté(e) le 5 mars 2021 toujours aussi clair tes explications @Lazer
jjacques68 Posté(e) le 5 mars 2021 Signaler Posté(e) le 5 mars 2021 (modifié) mais que ce passerait-il si 2 méthodes de ce QA sont appelées "simultanément" avec la variable http déclarée dans le onInit() ? Modifié le 5 mars 2021 par jjacques68
henri-allauch Posté(e) le 6 mars 2021 Signaler Posté(e) le 6 mars 2021 (modifié) Je me pose la même question sur des appels simultanés Sur la HC2, j'avais une scene avec http=net.HTTPClient dans chacune des fonctions et http=nill en fin de fonction La scène est activée à chaque appel et pour des appels simultanés il y avait donc plusieurs occurrences Cela fonctionne depuis plusieurs années ( sans soucis de mémoire ) Mais avec un QA ?? Je fais faire des essais Modifié le 6 mars 2021 par henri-allauch
henri-allauch Posté(e) le 6 mars 2021 Signaler Posté(e) le 6 mars 2021 (modifié) L'avis de @Lazer est le bon J'ai fait un QA-1 avec net.HTTPClient dans le Init et trois fonctions d'appel HTTP ( le serveur php immédiatement le renvoi le texte émis ) un bouton de test lance 5 fois les 3 fonctions - > OK J'ai fait un deuxième QA-2 avec un bouton de TEST il appelle 10 fois les trois fonctions du QA-1 - > OK Rien perdu pas d'erreur Meme essais avec une réponse décalée de 4s ( pour simuler un delais de traitement du serveur ) Rien de perdu pas d'erreur Je pense que c'est bon j'ai mis les QA le php et les résultats en fichier zip joint Test.zip Modifié le 6 mars 2021 par henri-allauch
Lazer Posté(e) le 6 mars 2021 Auteur Signaler Posté(e) le 6 mars 2021 Merci pour ton test qui confirme que l'option n°1, c'est à dire la variable self.http unique dans tout le QuickApp est la meilleure. On ne maitrise pas ce qui se passe à l'intérieur de la librairie net.HTTPClient, codée en C, et visiblement elle gère parfaitement les requêtes simultanées C'était un peu le doute que j'avais eu en portant Network Monitor sur HC3, et par précaution j'ai choisi de créer une variable http pour chaque serveur à monitorer. Visiblement c'est inutile, et je pourrais utiliser une variable self.http unique. Bon après c'est pas pour le peu de mémoire que consomme ce QuickApp que ça va changer grand chose... Il y a 17 heures, henri-allauch a dit : Le = nil a l'époque des pertes de memoire en HC2 j'en avait mis de partout mais si on declare dans le init cela ne me semble plus utile dans un QA Disons que c'est toujours utile car ça permet de libérer la mémoire... mais les QuickApps étant stables par nature, ce xx = nil n'est pas indispensable comme il l'était pour les VD sur HC2 (cela dit on n'a jamais trop su pourquoi ils plantaient, jusqu'à ce que Fibaro corrige le bug) Plus en détail : Pour une variable locale, il est inutile de forcer sa libération avec xxx = nil à la fin du bloc de code dans laquelle elle a été déclarée, car le Garbage collector se chargera de faire la ménage tout seul Pour une variable du QuickApp self.xxx, la question ne se pose pas, car si on crée une variable dans le QA, c'est qu'on veut la garder pendant toute la durée de vie du QA, donc aucune raison de la supprimer (sinon on aurait choisit une variable locale) Pour une variable globale, dans ce cas, je vois 2 cas de figure : soit on a sciemment choisi qu'elle soit globale pour être accessible par tout le monde (le QuickApp et les autres objets dans d'éventuels autres fichiers LUA.... mais c'est une mauvaise pratique soit on a involontairement défini la variable en local pour un usage ponctuel, et dans ce cas il faut manuellement penser à la supprimer avec xxx = nil, sinon elle restera allouée en mémoire jusqu'à la fin de l'exécution du programme (dans le cas d'un QuickApp => jamais (ou bien redémarrage du QA, reboot de la box)) et le garbage collector ne pourra jamais la supprimer Enfin, pensez que quand on veut passer une variable entre 2 fonctions, la meilleure solution est souvent le passage par paramètre. A ce sujet, quand on passe une string ou un number en paramètre, c'est une copie. Alors que quand on passe une table, c'est une référence... si bien que la fonction peut modifier la table originale en ne pensant modifier que sa propre table... méfiance, parfois le comportement est voulu, parfois pas ! Il y a 17 heures, jjacques68 a dit : mais que ce passerait-il si 2 méthodes de ce QA sont appelées "simultanément" avec la variable http déclarée dans le onInit() ? Impossible, comme dit hier sur un autre topic, les QA sont mono-threadés, le code ne peut jamais s'exécuter simultanément. Il y a une file d'attente, et à chaque fois qu'on appelle un QA (un bouton, une fonction, une action, un événement, un settimout dans le cas d'une boucle, etc), tout cela est mis en file d'attente, et exécuté en série.... cf la discussion sur l'asynchronisme Et franchement, je trouve ça mieux ainsi. Pour avoir déjà programmé en multi-thread, c'est vraiment chaud, le contenu d'une variable peut être modifié entre 2 lignes consécutives de code, si un autre thread se déroule au même moment. Il faut gérer des mutex, etc, une gymnastique pas évidente. Il y a 7 heures, henri-allauch a dit : Sur la HC2, j'avais une scene avec http=net.HTTPClient dans chacune des fonctions et http=nill en fin de fonction La scène est activée à chaque appel et pour des appels simultanés il y avait donc plusieurs occurrences En fait c'est différent, sur HC2 chaque instance de scène s'exécutait dans un processus (au sens Linux du terme) différent, c'est à dire un programme totalement séparé, avec son propre espace mémoire, avec sa propre copie du code, des variables, de la pile, etc. Du coup on avait une étanchéité totale entre les instances de scènes, il était impossible pour elles de partager les mêmes variables (sauf à passer par des variables globales) Sur HC3, chaque QuickApp est un processus (mono-threadé donc), et chaque scène est un processus aussi (toujours mono-threadé) Et Fibaro, au moins jusqu'au firrmware actuel, interdit le démarrage de plusieurs "instances", c'est à dire plusieurs processus Linux. 1
Messages recommandés