Űrlapok véletlen újraküldésének megakadályozása

A bejegyzés címe már sejteti a témát, de azért nézzük, miről is akarok én itt beszélni. Adott egy weboldal, és azon egy űrlap. Például egy üzenőfal. Valamint adott egy felhasználó, aki bátran kattint az "elküld" gombra. Az üzenete megjelenik, majd támad egy ingere, hogy frissítse a weblapot, mert mondjuk kíváncsi, hogy válaszoltak-e az üzenetére már. Persze ekkor az ő üzenete is újra el lesz küldve. Na most van olyan fejlesztő, aki erre nem gondol, és van olyan, aki igen. Utóbbiak megoldják, hogy frissítéskor az űrlapok ne legyenek újra elküldve. Vagy esetleg az üzenet tartalmát hasonlítják össze az előzőleg elküldöttel, és ha egyezik, akkor nem lesz újra bejegyezve az adatbázisba, avagy fájlba.

Most a példa kedvéért teremtsünk egy mesterséges problémát. Legyen csak egy egyszerű űrlap egyetlen szövegmezővel. Amit beleírunk, és elküldünk, az bekerül egy teszt.txt nevű fájlba. Nosza, hozz létre te is egy index.php és egy teszt.txt nevű fájlt. Ezeket használjuk a példában.

Nézzünk egy hibás megoldást

<?php
if (isset($_POST['teszt']))
{
        file_put_contents('teszt.txt', $_POST['teszt'].PHP_EOL,FILE_APPEND);
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
    </head>
    <body>
                <form action="index.php" method="post">
                        <input type="text" name="teszt" value="" />
                        <input type="submit" value="Küld" />
                </form>
    </body>
</html>

Ha itt ezzel elküldöd az "ABC" szöveget az űrlapon, akkor bekerül a fájlba az "ABC". Majd ha frissítesz, akkor bekerül újra, és újra és... Ahányszor frissítesz. Ez így nem lesz jó. Viszont megoldható egy egyszerű header használatával, amivel visszairányítunk az aktuális oldalra. Ezzel elveszítve a POST adatokat.

<?php
if (isset($_POST['teszt']))
{
        file_put_contents('teszt.txt', $_POST['teszt'].PHP_EOL,FILE_APPEND);
        header('Location: '.$_SERVER['REQUEST_URI']);
        exit;
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
    </head>
    <body>
                <form action="index.php" method="post">
                        <input type="text" name="teszt" value="" />
                        <input type="submit" value="Küld" />
                </form>
    </body>
</html>

Az exit utasítás használata opcionális. De általában nem kell, hogy lefusson az ilyen header utáni php kód. Tehát praktikus kitenni. Most bátran lehet frissíteni.

Na jó, de mi van, ha ki akarom írni, hogy "Hozzászólásod elküldve!"? Ezt persze már nem tudom megtenni, hiszen nincs információm arról, hogy történt-e üzenet küldés. Ezért lesz szükség a session használatára. Az alapötlet az, hogy tegyük a kiírandó üzenetet egy $_SESSION['tmpmsg'] változóba, és kiírás után rögtön töröljük is a tartalmát. Valahogy így:

<?php
session_start();
$msg = "";
if (isset($_POST['teszt']))
{
        file_put_contents('teszt.txt', $_POST['teszt'].PHP_EOL,FILE_APPEND);
        $_SESSION['tmpmsg'] = "Üzeneted elküldve!";
        header('Location: '.$_SERVER['REQUEST_URI']);
        exit;
}
if (!empty($_SESSION['tmpmsg']))
{
        $msg = $_SESSION['tmpmsg'];
        unset($_SESSION['tmpmsg']);
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
    </head>
    <body>
                <?php print $msg ?> <br />
                <form action="index.php" method="post">
                        <input type="text" name="teszt" value="" />
                        <input type="submit" value="Küld" />
                </form>
    </body>
</html>

Itt jegyezném meg, hogy a session_start() függvényt nem szabad elfelejteni meghívni. Illetve bizonyos ingyenes tárhelyeken a tárhelygazda kénytelen maga létrehozni egy "tmp" nevű mappát a session-öknek. Erről a tárhelyen kell informálódni a Gyakori kérdések ( GY.I.K. ) menüpontban például.

Nem meglepő módon, nem csak egy megoldás van. Mondjuk, nem akarok üzenetet kiírni és valamilyen okból kifolyólag átirányítani sem vagyok hajlandó. A helyzet az, hogy session ekkor is kell. De most az ötlet az, hogy valmailyen módon az elküldött űrlapot azonosítsuk. Először is bevezetek egy új rejtett mezőt az űrlapon. A neve most _postid_ lesz. És ezt is be fogom írni a fájlba az üzenet mellé. A mező értéke egyedi kell legyen. Erre remek megoldás a time() vagy microtime() függvény. A time() csak másodpercenként egyedi, de aki egy másodpercen belül többször akar küldeni valamit, az már nagyon fürge, vagy támadó. Én a microtime() -ot választottam az űrlapra, de válasszon mindenki ahogy tetszik. Tehát az új program:

<?php
if (isset($_POST['teszt']))
{
        file_put_contents('teszt.txt', $_POST['teszt'].' - '.$_POST['_postid_'].PHP_EOL,FILE_APPEND);
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
    </head>
    <body>
                <form action="index.php" method="post">
                        <input type="text" name="teszt" value="" />
                        <input type="hidden" name="_postid_" value="<?php print microtime() ?>" />
                        <input type="submit" value="Küld" />
                </form>
    </body>
</html>

Ezt az űrlapot elküldve, majd frissítve párszor, látható, hogy a txt-be frissítéskor ugyanaz az idő kerül. Ez persze nyilván nem csoda, de azért az érthetőség kedvéért jobb, ha az ember saját szemével látja. Tehát nincs más dolgunk, mint elmenteni az elküldött _postid_ mező értékét egy session változóba. És feldolgozáskor azt is kikötni a feltételben, hogy nem lehet azonos az előző és az új _postid_.

<?php
session_start();
$id = !empty($_SESSION['_postid_']) ? $_SESSION['_postid_'] : '';
if (isset($_POST['teszt']) and $id != $_POST['_postid_'])
{
        file_put_contents('teszt.txt', $_POST['teszt'].' - '.$_POST['_postid_'].PHP_EOL,FILE_APPEND);
        $_SESSION['_postid_'] = $_POST['_postid_'];
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
    </head>
    <body>
                <form action="index.php" method="post">
                        <input type="text" name="teszt" value="" />
                        <input type="hidden" name="_postid_" value="<?php print microtime() ?>" />
                        <input type="submit" value="Küld" />
                </form>
    </body>
</html>

Most már lehet frissíteni. Akár a $_POST értékeit kiírni, visszahelyezni űrlapra, de nem lesz elküldve, azaz feldolgozva az üzenet.

Kategóriák: 
Megosztás/Mentés

Hozzászólások

Tibi képe

Hello!

Ugyanezt az űrlapújraküldést hogy lehet megakadályozni olyan űrlapnál ami adatbázisba ment, nem pedig fájlba?

Rimelek képe

Üdv.

Ugyanígy. Csak adatbázisba mentesz, nem pedig fájlba. Nem tudok erre mást mondani. Azt csinálsz a feltételben, amelyikben én a példa kedvéért fájlba mentettem, amit csak akarsz.