HTTP verb pour "POST mais ROLLBACK"?
#1
Salutations,

dans la plupart de nos (mes? au moins) formulaires HTML, le serveur doit faire des vérifications sur les champs des formulaires, qui sont soit triviales ("est-ce que 'quantité de ressources' est un entier entre 1 et 1.000.000.000?") soit nettement plus complexes ("est-ce que 'date de déplacement des ressources' est entre 'date actuelle du pays dans la BDD' et 'date actuelle du jeu'?") voire carrément tordues ("est-ce que la 'date 1' du formulaire est comprise entre 'date 2' du formulaire et 'date 3' + 6 mois si le champ 'type' du formulaire est 'A' et 'date 3 ' + 1 an si le champ 'type' du formulaire est 'B'?").

L'un des moyens pour faire ces checks, c'est de le faire côté client en JS (ou via les attributs des input pour les cas les plus triviaux). Seulement, cela pousse à dupliquer des logiques, ou en tous cas, à faire en sorte que ce que le serveur autorise soit strictement ce que l'UI autorise.

Parfois, c'est assez complexe et tordu à faire. Du coup, j'avais pensé à une autre idée: et si, au lieu de chercher à valider les choses côté client en copiant des logiques, je laissait le serveur valider le formulaire, mais sans faire l'action correspondante?

Cela correspondrait donc au process suivant:
- L'utilisateur modifie des valeurs dans son formulaire
- Du code JS envoie une requête au serveur avec le contenu du formulaire (on va supposer qu'il n'y a pas d'upload de fichier, histoire de rester léger!), en indiquant au serveur "valide juste que ces data sont OK"
- Le serveur fait tous les check simples ou complexes qu'on peut imaginer, et répond soit "HTTP 200 OK" si le formulaire est accepté, soit un message d'erreur indiquant quel champ ne va pas et pourquoi.

J'ai déjà la mécanique du serveur: mes endpoint font déjà les vérifications des input du client, et retournent déjà une réponse type HTTP 400 {"fieldnames":["date1", "date2"], message: "Date 1 must be grater than Date 2"}

Du coup, tout ce qu'il me suffit maintenant de faire, c'est un bout de JS qui va automatiquement soumettre le formulaire, en disant au serveur "ne fait pas réellement les opérations: valide juste les inputs".

Est-ce que vous connaissez des moyens "standards" de décrire ce comportement? Actuellement, je partirai sur un HTTP verb custom type "CHECK", qui serait considéré comme un "POST" mais sans faire les actions réelles derrière (pas de modif dans la BDD [ie: rollback ou juste pas d'appel à la procédure stockée de ma page]). Mais s'il y a d'autres méthodes plus "classiques" de décrire cela (un paramètre POST précis? un header HTTP custom?), je les prendrai plutôt que d'en monter une perso.
Répondre
#2
Tu peux juste poster à une url différente, soit une autre route soit en rajoutant ?checkOnly=1 dans l'URL. C'est ce qu'on fait nous.
Répondre
#3
Oui, en place d'un paramètre POST custom (ou d'un HTTP verb), je pourrai mettre un paramètre GET. "checkOnly", vous l'avez choisi sans raison particulière? C'est pas genre "c'est ce que Symfony/CodeIgniter/trucchose.js utilise"?

PS: L'URL différente en revanche, non, ça m'embête: cela implique que cette autre URL ne doit *pas* être utilisée, et ça, c'est chaud/chiant à vérifier
ie: si je dis "POST /map/world/start/" contient le traitement du formulaire, et que "POST /map/world/start/check/" contient le check du même formulaire, alors il ne faut pas que je mettre de endpoint sur "/map/world/start/check/", et ça, je suis sûr que ça arrivera :/
Une seule URL avec un paramètre GET ou POST ou (mieux car inutilisé jusqu'ici chez moi) un verbe HTTP spécifique ou un header HTTP spécifique, cela évite (ou réduis drastiquement) ce genre de problème.
Répondre
#4
Plus précisément nous c'est "validateOnly" et je l'ai choisi comme ça, sans trop réfléchir.

Citation :alors il ne faut pas que je mettre de endpoint sur "/map/world/start/check/", et ça, je suis sûr que ça arrivera :/

J'ai pas compris. Si tu écoutes sur une route ben par définition ça atteint ton endpoint. Mais bon il suffit que tes deux routes soient mappées sur la même fonction de controller. ça revient au même que le paramètre en query string d'url.
Répondre
#5
Si je dis "la route xxx/check renvoie sur le endpoint xxx en lui disant de juste checker le formulaire", alors il ne faut pas que j'ai un autre endpoint dont la route serait "xxx/check", faute de quoi les deux se marcheront dessus.
Ou dit autrement, si le endpoint écoute les routes "xxx" (parce que c'est "le endpoint xxx") et la route "xxx/check" (parce qu'il s'agit d'un endpoint de formulaire qui doit pouvoir être requêté pour "validateOnly") alors un autre endpoint "yyy" ne doit pas écouter cette route "xxx/check". Comme mes routes sont automatiques, le endpoint "xxx" écoute tout seul "xxx/", et "yyy" n'écoutera que "yyy/" mais si jamais j'ai yyy = "xxx/check", parce que c'est le nom du endpoint, alors j'ai 1 route écoutée par 2 endpoints: une fois parce qu'il s'agit du check de xxx et une autre fois parce qu'il existe un endpoint "xxx/check"...

Bon, après, avec un paramètre GET/POST, j'ai un soucis du même type: si jamais le endpoint voulait utiliser le paramètre GET "validateOnly" (pour une raison idiote : ) ), alors ce paramètre issu du endpoint empiètera sur le paramètre général servant à dire "fait juste la validation".

Ca me pousse à préférer, finalement, un header HTTP custom: y'a peu de risques que cela marche sur autre chose (et cela colle à l'aspect "global" de la chose: les headers HTTP de la requête me servent beaucoup à gérer des "trucs globaux", comme le format à retourner ou la langue de la page, donc, rajouter le comportement "check ou submit normal?" à ce niveau là, c'est pas déconnant).

Ok, je pense que je vais donc faire pareil, et piocher un nom de header bateau genre "X-Form-Action: validate" (histoire d'être extensible)
Répondre
#6
Je pense que le header est un bon choix. Tu pourrais aussi le nommer "dry-run", c'est souvent le nom employé dans ce genre de cas (commandes UNIX par exemple).
Répondre
#7
Je ne sais pas comment sont mappées tes routes mais ça m'a pas l'air super pratique si tu ne peux pas réserver un path pour un controller …

Citation :Bon, après, avec un paramètre GET/POST, j'ai un soucis du même type: si jamais le endpoint voulait utiliser le paramètre GET "validateOnly" (pour une raison idiote : ) ), alors ce paramètre issu du endpoint empiètera sur le paramètre général servant à dire "fait juste la validation".

Et c'est valable dans tout les contextes ; si tu utilises le même nom pour désigner deux choses différentes, forcément à un moment donné ça bloque. C'est un peu comme dire si je nomme ma variable i alors je ne peux plus nommer comme ça une autre variable dans la même fonction. Ben oui …

Bref, effectivement il reste un header. Mais si jamais une route utilise ce même nom de header pour autre chose, comment tu fais ? :p Je te taquine.

Edit, autre solution qui reste totalement dans le scope du formulaire : une valeur du formulaire (booléene) qui indique qu'on veut simplement faire le check. Si elle vaut true, alors le formulaire est rejetté quoi qu'il arrive. Mais si c'est la seule erreur renvoyée ça signifie que le reste du formulaire est bon. Ça permet de laisser la gestion du truc en javascript uniquement : JS affiche toutes les erreurs dans la page sauf cette erreur spécifique, les formulaires pur HTML n'ont tout simplement pas le champ. Et côté serveur tu vas plus facilement centraliser la validation de champs communs à tous les formulaires (genre player_id, session_id, datetime, etc. et donc "dry_run" ou "force_reject") plutôt que de devoir faire une branche de code (genre un "if") pour détecter le header/paramètre de route et appeler ou pas le reste du traitement. Mais bon ça change pas grand chose.

Re-Edit : en termes de sémantique, selon moi on ne veut pas faire la même chose : d'un côté valider des données, d'un autre envoyer ces données au jeu pour effectivement changer l'état du jeu. Et ça pour moi ça veut dire que l'URL doit changer. Ou le verbe, ta première idée.
Répondre
#8
Je peux réserver 1 path par controller (ou même plusieurs si j'en avais envie), mais ce qui m'embête, c'est que si je réserve, disons, 2 path pour un controller, alors je n'ai pas de moyen ("compile-time") de savoir que le 2nd path est déjà réservé par ce controller. Et j'ai moyennement envie d'implémenter un check comme ça dans le plugin intellij que j'ai... bref, c'est pas hyper-pratique je trouve d'avoir 2 routes pointant vers le même endroit.

Ouep, c'est exactement le même soucis qu'un variable déjà nommée, mais là, l'IDE te le dit. Avec la route "/check" magiquement en plus, l'IDE ne le dit pas : ) Je n'ai aps ce soucis avec un header car je les réserve au code "global": un endpoint ne peut pas (n'est pas censé, parce qu'en vrai, il le peut totalement mais c'est "à tes risques et périls mon petit endpoint!") utiliser un header HTTP comme entrée. Le header Accept par exemple n'est pas traité par le endpoint, mais par le code générique que ce endpoint appel. Donc, j'ai juste à m'assurer que mes codes génériques n'utilisent pas 2x le même nom de header pour 2 trucs différents (et ça, c'est facile : ) )

Pourquoi pas pour l'utilisation d'un paramètre de formulaire, mais tu l'as justifié juste en dessous, je trouve que ce n'est pas tout à fait sa place. Le HTTP verbe est bien, le header HTTP pourrait aller aussi... Je verrai bien lequel des deux je choisis au final 2 Pour le moment, je coince plutôt sur la façon simple, élégante et fiable de basculer le endpoint en "read only"... ROLLBACK au lieu de COMMIT en fin de transaction, c'est une solution (bon, déjà, il faudra que je trouve quand même comment passer l'info jusqu'à PDO mais c'est pas le plus bloquant), sauf que j'ai un doute sur les AUTO_INCREMENT: je me demande s'ils sont rollbackés :/

Edit: Ah ben vi, l'AUTO_INCREMENT n'est pas réinitialisé... Erf, ça va me faire ch*er ça. Bon, c'est pas pressant du tout comme feature, donc je creuserai plus tard, j'ai Dracca à avancer !
Répondre
#9
Quand je parlai de réserver un path, je me suis mal exprimé, je pensais à réserver un préfixe, par exemple "/a/b/c/*". Dans ce cas, tout ce qui commence par ça est géré par le même controller. (Tu sembles utiliser le mot "endpoint" pour parler de controller). Perso je n'utilise pas d'IDE donc ça revient au même qu'une variable pour moi. C'est le compiler ou les tests qui gueulent 2

Effectivement sur Postgresql aussi les auto increment des serial ne sont pas annulés quand on travaille en mode sandbox. Ça m'avait fait galérer une fois. Mais une fois qu'on le sait, ça ne devrait pas être gênant du point de vue du code. Le code ne devrait jamais se baser sur la continuité des ID.

Et enfin, pourquoi tu as besoin de faire un ROLLBACK ? Tu fais la validation en SQL ?
Répondre
#10
Oui, car l'intérêt aussi de renvoyer la balle au serveur pour la validation du formulaire sans sa sauvegarde, c'est de pouvoir utiliser les data de la BDD: par exemple, afficher directement "ce pseudo est déjà pris" sur le formulaire d'inscription sans devoir le valider, ça peut être un plus (mais après, ce check ne se ferait que si le formulaire d'inscription est entièrement valide, aka si tous les champs sont remplis, donc pas sûr qu'on y gagne au final...)

Après, c'est une idée qui m'est surtout venue pour le boulot, dans le cas d'un formulaire où l'utilisateur indique la date à laquelle une soudure a été faite. Or, pour que la soudure soit valide, il faut que le soudeur soit considéré comme "qualifié" à cette date là (=il a passé une certif et celle-ci est encore valide à la date de la soudure). L'idée était donc d'envoyer le formulaire au serveur, avec l'option "validate only", pour que le serveur aille voir dans sa BDD (et avec toutes les règles métiers complexes liées au monde de la soudure [je vous les passe sous silence]) histoire de dire à l'utilisateur si la date entrée est valide ou non, sans qu'il n'ait besoin de valider le formulaire explicitement (et sans sauver cette date en BDD).

L'autre cas qui me venait à l'esprit pour un jeu web, c'était celui de l'inscription, pour indiquer au joueur "ce pseudo est déjà pris". C'est pas une donnée traitable dans un mécanisme de validation de formulaire JS classique (ie: on ne peut pas faire ça facilement avec du @required/@pattern ou du setCustomValidity).
Mais dans les deux cas, je me rends compte qu'en fait, ce qui est vraiment intéressant, c'est la validation d'un champ spécifique assez complexe. Ce serait peut-être donc plus simple et cohérent de mettre en place une archi pour valider juste le (voire les) quelques champs un peu complexes...

Auquel cas, je pourrai fixer une classe CSS (disons "prevalidate"), à poser sur les input/select/textarea d'un form, pour indiquer que ces champs peuvent être validés en envoyant une requête à un autre endpoint spécifique (que le formulaire pourrait également réutiliser histoire d'éviter les redites).

Je parle de endpoint car dans la pratique, sur mon archi, 1 route = 1 path vers 1 fichier php qui contient le controller, le modèle mappé sur la procédure SQL associée à ce endpoint, et le formatter de la vue.
Le contenu a été masqué


Répondre




Utilisateur(s) parcourant ce sujet : 1 visiteur(s)