vícenásobného odeslání formuláře

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
TeeBee87
Člen | 14
+
0
-

Zdravím,
narazil jsem na problém vícenásobného odeslání formuláře. Nemohlo by to řeštit Nette\Forms?
Alespoň jednoduchým javascriptem (zneaktivnění odesílacího tlačítka
nebo něco podobného), čistě jako ochrana proti BFU.

Jak by se to dalo řešit na straně serveru?

Ondřej Mirtes
Člen | 1536
+
0
-

Stručně: Odeslání formuláře mění stav serveru, po jeho úspěšném odeslání má následovat redirect na tvar URL, která již formulář neodesílá – např. $this->redirect('this').

phx
Člen | 651
+
0
-

TeeBee87 napsal(a):
narazil jsem na problém vícenásobného odeslání formuláře.

TeeBee87 myslel naprikald situaci kdy server reaguje pomalu a uzivatel netrpelive klikne znovu. Tz ze prohlizec posle 2. pozadavek a uz mame data na serveru 2×.

Jedine co me napada, ze aby kazdy formular mel v hidden policku nejaky unikatni klic overovany vuci session. Prvni pozadavek klic ze session zdusi a druhe kliknuti jiz data neulozi. Bohuzel uzivatel by asi dostal hlasku o nejakem zabezpeceni a znovu by formular odeslal a tetokrat by to proslo. (novy unikatni identifikator formulare). Tak neivm…

mancze
Člen | 58
+
0
-

Myslím, že tohle je skutečně věcná připomínka. Násobné odesílání formulářů se děje a to poměrně často (divili byste se, kolik lidí odesílá formulář dvojklikem).

phx napsal(a):

Jedine co me napada, ze aby kazdy formular mel v hidden policku nejaky unikatni klic overovany vuci session. Prvni pozadavek klic ze session zdusi a druhe kliknuti jiz data neulozi. Bohuzel uzivatel by asi dostal hlasku o nejakem zabezpeceni a znovu by formular odeslal a tetokrat by to proslo. (novy unikatni identifikator formulare). Tak neivm…

Tohle se mi zdá jako pěkné řešení – každý formulář má svůj token, takže je možné jen jedno odeslání na token.

EDIT: No a přidat nějaký přepínač, aby pro vývoj by bylo možné formuláře odesílat násobně (kvůli laďění).

Editoval mancze (1. 4. 2009 19:21)

phx
Člen | 651
+
0
-

Ha uz jsem to domyslel. Puvodne mjsem videl problem v tom, ze prvni odeslani ulozi data a druhe vypise chybu. Uzivatel vidi chybu odesle znovu (znovu ulozeno).

Proto je nutne ne zaznam ze session smazat, ale oznacit jako jiz provedeny a misto chyby normalne presmerovat jako ze vse OK.

Ale nemyslim si, ze by to melo resit neco v AppForm nebo v Nette. Maximalne napsat nejaky sikovny formularovy prvek, ktery se do formulare vlozi a pred samotnym ukladamin se onoho prvku zeptam zda mohu ulozit:) Ujme se toho nekdo? Ja osobne to zatim nepotrebuji tak se k tomu moc brzi nedostanu.

TeeBee87
Člen | 14
+
0
-

phx napsal(a):

Ha uz jsem to domyslel. Puvodne mjsem videl problem v tom, ze prvni odeslani ulozi data a druhe vypise chybu. Uzivatel vidi chybu odesle znovu (znovu ulozeno).

Proto je nutne ne zaznam ze session smazat, ale oznacit jako jiz provedeny a misto chyby normalne presmerovat jako ze vse OK.

Ale nemyslim si, ze by to melo resit neco v AppForm nebo v Nette. Maximalne napsat nejaky sikovny formularovy prvek, ktery se do formulare vlozi a pred samotnym ukladamin se onoho prvku zeptam zda mohu ulozit:) Ujme se toho nekdo? Ja osobne to zatim nepotrebuji tak se k tomu moc brzi nedostanu.

To se mi líbí – momentálně si s tím hraju, ale nedaří se mi nasimulovat microclick nebo jak to nazvat :)

Honza Marek
Člen | 1664
+
0
-

Proti dvojkliku by možná mohlo pomoct v jQuery něco takového:

$("form").submit(function () {
	$(this).find(":submit").attr("disabled", "disabled");
});

Nezkoušel jsem to.

David Grudl
Nette Core | 8107
+
0
-

Někdy se internet courá a člověk to řeší odesláním formuláře znovu. To je normální a kolikrát užitečné. Zablokování odesílacího tlačítka mu to účinně znemožníme, ale to není dobře – jak má potom odeslat formulář podruhé, když se očividně „něco kouslo“. Možná by nebylo špatné tlačítko zablokovat s časovačem na 5–10 sekund a poté je odblokovat.

$("form").submit(function() {
        var el = $(this).find(":submit, :image");
        el.attr("disabled", "disabled");
        setTimeout(function() {
        	el.attr("disabled", "");
        }, 5000);
});

U pomalu odesílaného formuláře hrozí ještě jedna věc: uživatel si všimne, že něco špatně vyplnil, položku opraví a dá odeslat znovu. Tomu by se dalo zabránit úpravou na var el = $(this).find(":input");

Dále je tu případ, kdy formulář znovuodešle pomocí tlačítka Obnovit stránku. Tohle už je potřeba řešit na straně serveru. Třeba pohledem do databáze, jestli tam už stejný záznam není, nebo obecněji, kdy obsah formuláře (nebo jeho hash) uložíme do session s rozumným timeoutem a s tou porovnáváme. Zároveň je potřeba připojit informaci o tom, kam po odeslání přesměrovat.

if ($form->isSubmitted() && $form->isValid()) {
	$values = $form->getValues();
	$session = Environment::getSession('App/Forms/CommentForm/Check');
	if ($session->values == $values) { // dvojite odeslani
		$id = $session->id;
	} else {
		$id = $model->process($values); // predpokladejme, ze v pripade chyby vyhodi vyjimku
		$session->values = $values;
		$session->id = $id;
		$session->setExpiration(60);
	}
	$this->redirect('view', $id);
}
mancze
Člen | 58
+
0
-

David Grudl napsal(a):

Dále je tu případ, kdy formulář znovuodešle pomocí tlačítka Obnovit stránku. Tohle už je potřeba řešit na straně serveru. Třeba pohledem do databáze, jestli tam už stejný záznam není, nebo obecněji, kdy obsah formuláře (nebo jeho hash) uložíme do session s rozumným timeoutem a s tou porovnáváme. Zároveň je potřeba připojit informaci o tom, kam po odeslání přesměrovat.

Nějak se mi zdá, že tlačítko Obnovit stránku je ekvivalentní s double-postem. Tedy přesněji řešeno – obnovit stránku považuji za hodně nepravděpodobný příklad. Přece pokud bych obnovil stránku se zaseknutým formulářem, tak docílím jen toho, že se mi refreshne stránka s formulářem (a jisté prohlížeče při té příležitosti i resetnou políčka).

Abych znovu-odeslal formulář pomocí Obnovit stránku, tak by muselo být splněno:

  • nedodržuju best practice a po správně vyplněném formuláři neredirectuju (nebo)
  • browser již dostal odpověď od serveru (přehodí se aktuální stránka na tu s post daty), ale je natolik pomalý, že než zpracuje hlavičku „Location“, tak uživatel stihne klepnout na F5 (nebo)
  • je vypnutý redirect

Ani jedno se mi nezdá příliš pravděpodobně (hlavně porovnám-li to četnosti „double-postů“.

phx napsal(a):

Ale nemyslim si, ze by to melo resit neco v AppForm nebo v Nette.

A proč ne? Myslím si, že by toto Nette mělo řešit – hodí se to přece u prakticky každého formuláře, tak proč to všude explicitně ošetřovat? Jdeme přece směrem convention over exception.

Nemyslím si tedy, že by Nette mělo řešit ty opravy ve formulářích, jak naznačil David, ale čistě problém samotných násobných odeslání.

Editoval mancze (2. 4. 2009 17:24)

David Grudl
Nette Core | 8107
+
0
-

mancze napsal(a):

  • browser již dostal odpověď od serveru (přehodí se aktuální stránka na tu s post daty), ale je natolik pomalý, že než zpracuje hlavičku „Location“, tak uživatel stihne klepnout na F5 (nebo)
  • je vypnutý redirect

Ani jedno se mi nezdá příliš pravděpodobně (hlavně porovnám-li to četnosti „double-postů“.

Pak máš štěstí na kvalitní internet a servery s bleskovou odezvou, mně se to stává naprosto běžně.

mancze
Člen | 58
+
0
-

David Grudl napsal(a):

Pak máš štěstí na kvalitní internet a servery s bleskovou odezvou, mně se to stává naprosto běžně.

Chceš říct, že když klepneš na tlačítko pro odeslání formuláře a posléze na „Obnovit stránku“, že se formulář odešle podruhé? (jasně, je to hloupst, ale zajímalo by mne to)

David Grudl
Nette Core | 8107
+
0
-

mancze napsal(a):

David Grudl napsal(a):

Pak máš štěstí na kvalitní internet a servery s bleskovou odezvou, mně se to stává naprosto běžně.

Chceš říct, že když klepneš na tlačítko pro odeslání formuláře a posléze na „Obnovit stránku“, že se formulář odešle podruhé? (jasně, je to hloupst, ale zajímalo by mne to)

Ne hned, samozřejmě, ale když je to ve fázi, že se prohlížeč snaží načítat už další stránku.

phx
Člen | 651
+
0
-

mancze napsal(a):

phx napsal(a):

Ale nemyslim si, ze by to melo resit neco v AppForm nebo v Nette.

A proč ne? Myslím si, že by toto Nette mělo řešit – hodí se to přece u prakticky každého formuláře, tak proč to všude explicitně ošetřovat? Jdeme přece směrem convention over exception.

Nemyslím si tedy, že by Nette mělo řešit ty opravy ve formulářích, jak naznačil David, ale čistě problém samotných násobných odeslání.

Ja jsem to myslel tak, ze si nedovedu predstavit jak by to Nette resilo. Tak jako tak musim JA rozhodnout co udelat kdyz mi dorazi data 2×. Osobne bych to resil pres vlastni FormControl, ktereho bych se ptal zda muzu s klidem ulozit. Mozna by to slo zakomponovat do AppForm (Form), ale nejak si to nedovedu moc predstavit.

Jinak s disable tlacitka souhlasim, ale jak psal David tak max na x sekund.

A posledni vec. Prijde mi zbytecne provnavat data s DB zda tam jiz nahodou nejsou. Bohate by mel stacit onen unikatni identifikator, ktery vuci session budu overovat zda jib bylo/nebylo ulozeno. Kzdy formular odesle znovu data tak odesle i ten samy identifikator. A pokud po ulozeni presmeruji tak by to melo byt OK. Nebo se pletu?

nAS
Člen | 277
+
0
-

Co takhle k formulářům přidat něco ve stylu:

$form->onDuplicateSubmit[] = array($this, 'FormDuplicateSubmitted');

a v tom případě by se ve formuláři nastavilo nějaké hidden políčko s vygenerovaným tokenem. A při odeslání formuláře by se ten token uložil do SESSION, takže by se dalo zjistit, zda byl formulář odeslán poprvé, nebo znova. Má to tu výhodu, že si může člověk sám určit, jestli u tohoto konkrétního formuláře chce řešit vícenásobné odeslání nebo ne, a může si sám určit, jak s ním naložit.

Roman Ožana
Člen | 52
+
0
-

Tohle není špatné, mě se líbí:

<?php
$form->onMultiSubmit[] = array($this, 'FormMultiSubmitted');
?>

včetně toho hidden parametru

Honza Kuchař
Člen | 1662
+
0
-

Jsem pro extra form control na který si budu moct navěsit vlastní fci pomcí callbacku.

DocX
Člen | 154
+
0
-

Nestačí pro ten první požadavek, tedy zamezit vícenásobnému odeslání, použít

<?php
	$form->addProtection(...);
?>

nebo ten při přijmutí formuláře token nemaže?

Honza Kuchař
Člen | 1662
+
0
-

Teď jsem to zkoušel a jde odeslat několikrát. (odešlu form a potom dám F5)

Ondřej Mirtes
Člen | 1536
+
0
-

honzakuchar napsal(a):

Teď jsem to zkoušel a jde odeslat několikrát. (odešlu form a potom dám F5)

Takhle to nezjistíš. Po tom F5 už má formulář nový token, ne?

Honza Kuchař
Člen | 1662
+
0
-

Po tom F5 už má formulář nový token, ne?

Nemá, token je platný po nastavenou dobu. (zkoušel jsem se přihlásit, odhlásit a já nevím co, ale token ve formuláři byl pořád stejný)

Ondřej Mirtes
Člen | 1536
+
0
-

David Grudl napsal(a):

Někdy se internet courá a člověk to řeší odesláním formuláře znovu…

Díky za skript, ale nedaří se mi ho upravit, aby se tlačítko nedisablovalo, pokud na něj uživatel sice klikne, ale před odesláním ještě zasáhne Nette validace s nějakým tím alertem.

Může mě někdo pošťouchnout správným směrem? Díky.

Ondřej Mirtes
Člen | 1536
+
0
-

Tak jsem se po X měsících hrabal v tom javascriptovém disablování submit tlačítka a musel jsem sáhnout do zdrojáků Nette. Nedařilo se mi v mém kódu dostat se k výsledku už proběhlé „onsubmit“ události, kde by mohl být uložen výsledek validace – tlačítko bych pak disabloval jen v případě, že validace proběhla správně.

Upravil jsem si tedy Nette, obohatil jsem funkci validateForm o druhý parametr, který říká, jestli má vyskočit alert okno se zprávou pro uživatele – a při submitování si tuto metodu zkrátka zavolám znova a zjistím, jestli mám tlačítko disablovat. Kód:

--- a/libs/Nette/Forms/Renderers/InstantClientScript.phtml
+++ b/libs/Nette/Forms/Renderers/InstantClientScript.phtml
@@ -75,7 +75,7 @@ nette.validateControl = function(control) {
 }


-nette.validateForm = function(sender) {
+nette.validateForm = function(sender, alerts) {
+	if ((typeof(alerts) == "undefined")) var alerts = true;
+
 	var form = sender.form || sender;
 	var validators = this.getFormValidators(form);
 	for (var name in validators) {
@@ -84,7 +84,7 @@ nette.validateForm = function(sender) {
 			if (form[name].focus) {
 				form[name].focus();
 			}
-			alert(error);
+			if (alerts) alert(error);
 			return false;
 		}
 	}

A jQuery kód:

$("form").submit(function() {
	if (nette.validateForm(this, false)) {
		var el = $(this).find(":submit");
		el.attr("disabled", "disabled");
		setTimeout(function() {
			el.attr("disabled", "");
		}, 1650);
	}
});

Pokud by někoho napadlo systematičtější řešení, sem s ním :)

EDIT: Trochu jsem to zelegantnil, stačí z Nette upravit jen jeden soubor :)

Editoval Ondřej Mirtes (15. 3. 2010 1:02)

David Grudl
Nette Core | 8107
+
0
-

Jestli to dobře chápu, problém je v nastavení attributu onsubmit v HTML kódu formuláře?

Ondřej Mirtes
Člen | 1536
+
0
-

Abych nedisabloval tlačítko i v případě, že vyskočí alert() s validační chybou, tak uvnitř té metody potřebuji kontrolovat, jestli je formulář validní. A neumím to udělat jinak, než si podruhé zavolat metodu validateForm() :) Což způsobí, že ten alert() vyskočí podruhé. Proto jsem si do té metody validateForm přidal nepovinný atribut alerts, který by se měl jmenovat spíš showAlerts :)

Honza Kuchař
Člen | 1662
+
0
-

problém s onSubmit řeší tento doplněk (kvůli tomu vznikl…): https://componette.org/search/?…

dakota
Člen | 148
+
0
-

Nešlo by ochranu proti viacnásobnému odoslaniu formulára implementovať priamo do Nette?

Tomáš Votruba
Moderator | 1114
+
0
-

Přimlouvám se, bylo by to fajn.