Oauth2 token et accès concurrent - PHP - Programmation
Marsh Posté le 04-06-2024 à 18:22:45
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 :
|
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...
Marsh Posté le 04-06-2024 à 18:57:42
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 :
|
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 :
|
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.
Marsh Posté le 04-06-2024 à 19:16:00
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.
Marsh Posté le 04-06-2024 à 20:31:22
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... ^^
Marsh Posté le 04-06-2024 à 20:41:40
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.
Marsh Posté le 05-06-2024 à 14:17:04
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 :
|
J'ai également eu des fichiers lock impossible à supprimer sans couper Wamp server, bref, pas très rassurant.
Marsh Posté le 06-06-2024 à 03:02:52
Bordel ! Ligne 29 de mon code
Code :
|
à remplacer par
Code :
|
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.
Marsh Posté le 06-06-2024 à 07:51:29
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 ?
Marsh Posté le 06-06-2024 à 14:55:48
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.
Marsh Posté le 13-06-2024 à 14:07:43
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.
Marsh Posté le 04-06-2024 à 14:00:36
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