Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
2545 connectés 

  FORUM HardWare.fr
  Programmation
  PHP

  Oauth2 token et accès concurrent

 


 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

Oauth2 token et accès concurrent

n°2470951
Tibar
Posté le 04-06-2024 à 14:00:36  profilanswer
 

Bonjour,
 
Voici la situation :  
Je développe une API en PHP (je l'appellerai API Client) afin de simplifier l'appel à une API fournie par un site (qui sera l'API Source). L'API Client s'occupe de réduire le nombre de paramètres nécessaires et gère également la partie Oauth2 de l'API Source.
Le process est assez simple : lors d'un appel à l'API Client, je cherche dans ma base de données si j'ai un token Oauth2 valide (l'API Source fournit un token avec une validité de 599 secondes). Si c'est le cas, j'utilise ce token, sinon j'appelle l'API Source pour obtenir un nouveau token, je supprime le token invalide de ma base, et je stocke le nouveau token avec sa nouvelle date de validité, et je suis à nouveau tranquille pour 599 secondes.
En appelant de manière itérative, tout se passe bien.  
Le problème se pose lorsque j'appelle l'API Client depuis un spreadsheet Google. En effet, j'ai l'impression que les appels sont tellement rapprochés (j'ai fait des tests, lors d'un refresh de la page, j'ai parfois 10 appels dans le même 10ème de seconde) que le premier appel qui arrive n'a pas le temps de supprimer le token et d'en obtenir un nouveau, donc le deuxième appel trouve également un token invalide (s'il a lieu avant la suppression) ou un table vide (s'il a lieu après la suppression), et lance donc l'appel à l'API Source pour obtenir un nouveau token. Dans les cas extrêmes, j'ai eu jusqu'à 10 demandes de renouvellement de token alors que l'objectif de stocker cette donnée dans ma base est de réduire le délai et d'optimiser le nombre d'appels à l'API Source.
 
J'ai trainé un peu hier soir pour chercher une solution, et j'ai quelques pistes :  
- gérer ce comportement avec des sémaphores : pas forcément fan de la solution, surtout que je ne vois pas trop comment gérer les "waits" côté client
- modifier mes requêtes pour locker la table le temps que le premier appel obtienne un token
- ajouter des transactions dans mon code, même objectif que l'idée précédente, c'est à dire empêcher deux accès "proches" de demander un nouveau token au même moment
 
Je pense privilégier la solution 3, est-ce qu'il y a d'autres pistes, est-ce que je rate quelque chose ?
 
Merci

mood
Publicité
Posté le 04-06-2024 à 14:00:36  profilanswer
 

n°2470966
mechkurt
Posté le 04-06-2024 à 18:22:45  profilanswer
 

Je penses que ce que tu veux faire va être compliqué en php / Mysql car y'a un délai induit par chaque requête, ne peux tu pas faire autrement pour le stockage de ton token, une session, du cache en ram genre redis ou un fichier en lecture seul seront peut être plus fiable en terme de parallélisation / accès concurrent.
 
Peut être aussi structurer ton code différemment genre:

Code :
  1. SCRIPT 1 :
  2. Regarder si un Token valide Existe en appelant le SCRIPT 2 qui renvoie le token ou un message d'attente
  3.   Si Oui : Poursuivre avec l'appel à l'API
  4.   Si Non : Attendre quelques Millisecond (temps correspondant grosso merdo a temps qu'il te faut pour créer un token en temps normal) avec usleep
  5. Retour au début
  6. SCRIPT 2 :
  7. Vérifier la présence du token
  8.   Si il ne l'est pas, c'est qu'il est en cours de génération, renvoyer au SCRIPT 1 d'attendre
  9.   Vérifier la validité du token
  10.     Si il est valide, renvoyer le token
  11.     Si il ne l'est pas le créer, renvoyer au SCRIPT 1 d'attendre


Sachant que tu peux aussi t'arranger pour avoir un token toujours valide en mettant le script 2 dans un cron tournant toutes les 9 minutes... :o


Message édité par mechkurt le 04-06-2024 à 18:24:08

---------------
D3
n°2470967
Tibar
Posté le 04-06-2024 à 18:57:42  profilanswer
 

Merci pour la réponse. Concernant les propositions :  
- la session ne semble pas utilisable dans le cadre d'une API puisque la connexion n'est pas maintenue ouverte via un navigateur (j'avais trouvé ça sur SO) : https://stackoverflow.com/questions [...] n-rest-api
- le cache en RAM avec Redis, ça semble prometteur et ça permet même de donner une durée de validité à une paire clé/valeur, je vais regarder comment je peux déployer ça sur mon serveur de préprod (hébergé chez OVH, du coup j'ai un doute)
- un fichier en lecture seule ça pourrait être le plus simple, et si le temps de traitement est plus rapide que les requêtes, ça pourrait être efficace.
 
Ce que je fais concrètement aujourd'hui en pseudo code (je pourrai mettre le vrai code si besoin) :  
 

Code :
  1. function getToken()
  2. {
  3. select token from api_token where expire_on > current_timestamp
  4. si vide
  5. {
  6.   $token = ''
  7. }
  8. sinon
  9. {
  10.   $token = token du select
  11. }
  12. si $token = '' // on n'a pas trouvé de token valide
  13. {
  14.   appel API source pour créer token
  15.   $token = valeur renvoyée par l'API source
  16.   delete from api_token
  17.   insert into api_token (token, expires_on)
  18. }
  19. return $token
  20. }


 
La principale différence que je vois c'est que tu proposes de séparer les tests en 2 fonctions, pour que le premier appel qui passe "lock" les autres appels quelques millisecondes avec le usleep ? Je ne vois pas comment différencier le premier appel du suivant. Si le

Code :
  1. select token from api_token where expire_on > current_timestamp

ne retourne rien, comment je peux savoir qu'un autre appel est déjà passé avant moi ?
 
Merci !
 
PS : pour ton edit, chez OVH, un chron c'est une fois par heure maximum. Et autre point, l'API source est gratuite jusqu'à 1000 appels par jour, si je fais un appel toutes les 9 minutes "pour rien", je consomme 160 appels, soit 16% de ma dispo.


Message édité par Tibar le 04-06-2024 à 19:00:59
n°2470968
Tibar
Posté le 04-06-2024 à 19:16:00  profilanswer
 

Ou alors, j'ajoute un test après le if token = '' pour vérifier si un fichier "bidon" existe. Si oui, je usleep un petit délai et je rappelle getToken, si il n'existe pas, je touch un fichier bidon, je crée mon token et je rm le fichier bidon ?

 

Sinon je fais un fopen dans un try catch plutôt qu'un touch, et si le fichier ne peut pas être créé, c'est qu'un autre appel à déjà lancé le process.


Message édité par Tibar le 04-06-2024 à 19:23:09
n°2470969
mechkurt
Posté le 04-06-2024 à 20:31:22  profilanswer
 

Pas sur que sur du mutualisé tu ai accès à du cache mémoire comme REDIS, mais je penses qu'un fichier sera mieux qu'une requête SQL car tu peux vérifier sa présence et le supprimer avant de le recréer donc tes appelles suivants ont juste à vérifier que le fichier existe puis qu'il est valide, et ne créeront donc pas plusieurs token...
Alors qu'en SQL tu peux avoir trois SELECT qui renvoie que le truc existe et n'est plus valide (alors que les requêtes d'UPDATE ou DELETE ne sont pas encore passé) donc trois "instance" qui demandent un nouveau token simultanément.
 
Tente ton truc avec un fichier mais prend bien en compte le usleep qui justement devrait permettre ne pas interroger 10 fois (pour les 10 requêtes simultané au serveur) le même script au même moment.
 
Pour la tache planifié toute les 9 minutes, si ta limite c'est 1000/jour tu est large avec une requête toutes les 9 minutes (24h*60m = 1440m/9 => 160 appel), par sécurité tu peux même faire un appel toutes les 5 minutes et rester en dessous des 1000 requête, à moins que tes appels de token compte en plus des appel API, auquel cas il faudrait voir combien tu compte en faire chaque jour... ^^


---------------
D3
n°2470970
Tibar
Posté le 04-06-2024 à 20:41:40  profilanswer
 

Oui, en effet, la solution fichier me semble plus simple et moins contraignante côté BDD, qui nécessite la gestion des transactions et des locks.  
Les trois demandes simultanées, c'est exactement ce qui se passe actuellement.
 
Pour le usleep, j'ai déjà quelques centaines d'appels à l'API qui me génère le token, je vais le fixer au temps maximum * 1.5, et de toutes façons, au pire si le premier qui appelle n'a pas eu de réponse, le fichier "lock" devrait toujours être présent, donc retomber dans le usleep.
 
Et oui, d'après ce que j'ai pu voir du peu de métriques disponibles sur l'API Source, les appels de token comptent bien comme un appel à l'API, c'est pour ça que je souhaite mettre en place toute cette mécanique, sinon je générerai un token à chaque appel.
 
Merci pour les infos, je vois si je peux mettre ça en place rapidement et je ferai un petit retour.

n°2470999
Tibar
Posté le 05-06-2024 à 14:17:04  profilanswer
 

Bon, j'ai essayé un peu hier soir mais ça n'est pas concluant pour le moment, je pense qu'il va falloir que je reprenne tout ça à tête reposée et que je découpe plus finement mes tests. J'ai eu des comportements très bizarres après le usleep(3000), mais je pense que c'est lié au fait que j'appelais la méthode getToken depuis getToken. Je ne comprends pas trop, il m'est arrivé de logger 4 créations de token lors de 5 appels simultanés, et le 5è a bien trouvé un token existant :  

Code :
  1. class NomClass
  2. {
  3. private getTokenFromDb()
  4. {
  5.  select token from api_token where mes conditions de validité
  6.  //execution de la requête et affectation du résultat dans la variable $token
  7.  return $token //peut retourner un token valide ou une chaine vide si pas de token valide présent en BDD
  8. }
  9. public getToken()
  10. {
  11.  $lock_file_path = 'chemin vers mon fichier de lock';
  12.  $token = $this->getTokenFromDb();
  13.  if ($token = '') //on n'a pas de token valide en BDD
  14.  {
  15.   if (!file_exists($lock_file_path)) //on est le premier appel à demander un token, création du fichier de lock
  16.   {
  17.    fopen($lock_file_path);
  18.    //appel à l'API pour récupérer un token et sa durée de validité
  19.    $token = APIResult['token'];
  20.    $validity = APIResult['validity'];
  21.    delete from api_token; //pour n'avoir que le dernier token dans la table
  22.    insert into api_token(token, expires_on) values ('$token', current_date + $validity);
  23.    rm($lock_file_path);
  24.   }
  25.   else //un autre appel est déjà en train de générer un nouveau token puisque le fichier est là
  26.   {
  27.    usleep(3000); //on attend 3 secondes
  28.    $this->getToken(); //et on recommence l'appel
  29.   }
  30.  }
  31.  return($token);
  32. }
  33. }


 
J'ai également eu des fichiers lock impossible à supprimer sans couper Wamp server, bref, pas très rassurant.

n°2471039
Tibar
Posté le 06-06-2024 à 03:02:52  profilanswer
 

Bordel ! Ligne 29 de mon code

Code :
  1. $this->getToken();


 
à remplacer par

Code :
  1. $token = $this->getToken();


 
Ca fonctionne tout de suite beaucoup mieux. Le spreadsheet génère quand même 2 appels en 2.4 millièmes de seconde :  
Appel 1 : 2024-06-06 03:13:21.338520
Appel 2 : 2024-06-06 03:13:21.340920
 
et dans ce cas de figure, la création du fichier ne semble pas terminée, du coup on a 2 demandes au lieu d'une.


Message édité par Tibar le 06-06-2024 à 03:25:25
n°2471042
mechkurt
Posté le 06-06-2024 à 07:51:29  profilanswer
 

usleep(3000) ce n'est pas 3 secondes (utilise sleep si tu veux attendre en seconde).

Citation :

Une microseconde est un millionième de seconde.


Tu devrais essayer de mettre au début de ton script un usleep(random_int(1, 999999)) pour que tes appels simultané soit suffisamment désynchronisé pour que le premier script (celui ayant le usleep(random()) le plus petit) ai le temps de créer avant que les autres vérifie la présence...
 
Par contre pourquoi tu gardes ton token en base de donnée, puisque tu manipules déjà un fichier, écrit ton token dedans non ?


Message édité par mechkurt le 06-06-2024 à 16:22:31

---------------
D3
n°2471076
Tibar
Posté le 06-06-2024 à 14:55:48  profilanswer
 

Ah, mais j'avais essayé le rand au début de la procédure, sauf que je limitais entre 100 et 500, mais forcément, en microsecondes ça ne suffisait pas. Je vais augmenter ces valeurs, ça devrait le faire et ça m'apprendra à mieux lire la doc également, et à ne pas toujours croire le premier commentaire sur S/0 : https://stackoverflow.com/questions [...] sh-example
 
Je garde le token en base de données parce que c'est la première idée qui m'est venue quand j'ai voulu réduire le nombre d'appels, mais en effet, maintenant qu'un fichier est créé, ça serait encore plus rapide de passer par une lecture de fichier pour récupérer le token.
 
Merci pour tes réponses, ça m'a bien débloqué, je tenterai ce soir avec un usleep random un peu plus important, ça devrait encore diminuer le nombre d'appel.
 

mood
Publicité
Posté le 06-06-2024 à 14:55:48  profilanswer
 

n°2472027
rufo
Pas me confondre avec Lycos!
Posté le 13-06-2024 à 14:07:43  profilanswer
 

Question bête : si tu as un moyen d'identifier que les x requêtes qui arrivent concerne le même user (ex : via sa clé d'API), pourquoi tu mets pas dans une file t'attente du user concerné les requêtes à exécuter pour les passer séquentiellement. Ainsi, la première va demander l'authentification puis les autres vont être exécutées sans demander l'auth puisque le token valide est dispo les unes à la suite des autres.


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta

Aller à :
Ajouter une réponse
  FORUM HardWare.fr
  Programmation
  PHP

  Oauth2 token et accès concurrent

 

Sujets relatifs
[python]faire une dll ou équivalent pour accès par labviewXML espace dans les noms de noeud, acces en python
Accès LPPR Ameli[PHPSpreadSheet] accès à une cellule nommée depuis version 1.15.0
[Web] Protéger l'accès à des imagesAccès refusé dans UWP
Protéger l'accès d'un site extranetlanguage bas niveau ? accès au matériel
Accès fichier local dans une web extensionsecuriser l'acces à la base mysql
Plus de sujets relatifs à : Oauth2 token et accès concurrent


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR