За последние 24 часа нас посетил 18651 программист и 1620 роботов. Сейчас ищут 1105 программистов ...

Скрип тестирования: как усовершенствовать алгоритм

Тема в разделе "Решения, алгоритмы", создана пользователем Vladd55, 6 ноя 2024.

  1. Vladd55

    Vladd55 Новичок

    С нами с:
    11 дек 2021
    Сообщения:
    88
    Симпатии:
    1
    Для тестирования посетителю поочередно показывают вопросы, записывая его ответы в базу. Но есть нюанс – надо сделать так, что бы при обновлении страницы или при клику «Назад» скрипт останавливался и выводилось сообщение об ошибке.

    Я сделал так.

    На стартовой странице, где еще нет вопросов, вырабатывается случайное число $randomNumber, которое заносится в базу. Потом человек кликает «Начали» и переходит на страницу с адресом test.php?id=" . $randomNumber.
    На этой странице извлекается число из динамического адреса и из базы, и сравнивают их. Если числа совпадают, то все хорошо.

    Вырабатывается другое случайное число и в форме создается action обработчика с динамическим адресом, содержащим это число, которое также заносится в базу вместо прежнего.

    Посетителю показывается вопрос.

    Когда посетитель кликает «Готово», происходит переход на обработчика, которого посетитель не видит.

    В обработчике из адреса берется число и из базы, аналогичным образом они сравниваются. Если совпадают, то результат тестирования заносится в базу. Если не совпадают, то выводится сообщение об ошибке.

    Генерируется новое случайное число, которое тоже заносится в базу, и оно же добавляется в адрес страницы тестирования, где, после проверки, появляется следующий вопрос.

    Все это работает очень хорошо, успешно детектируя и обновления, и попытки Назад. Но бывают случаи, когда скрипт останавливается с диагностикой ошибки, которой на самом деле нет. То есть, что-то не успевает синхронизироваться. Может, браузер $_GET долго отдает или что-то в этом роде.

    Как бы это исправить?

    (Сессии не стал использовать, поскольку вариант с базой позволяет прерваться и потом продолжить с этого же места).
     
  2. Vladimir Kheifets

    Vladimir Kheifets Новичок

    С нами с:
    23 сен 2023
    Сообщения:
    425
    Симпатии:
    79
    Адрес:
    Бавария, Германия
    Добрый день!
    Правильно ли я понял, что одно случайное число каждый раз сохраняется в БД только для сравнения с введённым числом?
    Напоминает задачку отгадай число для JS - генерируйте случайное число и сранивайте с ответом.
    Ну если очень хочется PHP за уши притянуть, отправляйте введённое число из JS через fetch без перезагрузки страницы.
    Если есть какой-то потаённый смысл, то покажите, пожалуйста, Ваш код.
    Удачи!
     
  3. Vladd55

    Vladd55 Новичок

    С нами с:
    11 дек 2021
    Сообщения:
    88
    Симпатии:
    1
    Приведу некоторые коды, относящиеся к проблеме.
    На странице с тестом.

    PHP:
    1.  // Динамический адрес для формы
    2.         // Случайная часть динамического адреса
    3.     $randomNumber = random_int(105000, 995000);
    4.     // Создание подготовленного SQL запроса для обновления столбца
    5.     $sql = "UPDATE user SET address_dynamic = ? WHERE token = ?";
    6.     // Подготовка и выполнение SQL запроса
    7.     $stmt = mysqli_prepare($db, $sql);
    8.     mysqli_stmt_bind_param($stmt, "is", $randomNumber, $token);
    9.     if (mysqli_stmt_execute($stmt)) {
    10.             //echo "Data updated successfully";
    11.     }
    Форма тестирования

    HTML:
    1.  <form action="green-handler.php?d=<?php echo $randomNumber; ?>" method="POST">
    2.        
    3. <div class="radiobutton-group">
    4.     <input type="radio" id="button1" name="ball" value="100">
    5.     <label for="button1">0</label>
    6.     <input type="radio" id="button2" name="ball" value="1">
    7.     <label for="button2">1</label>
    8.     <input type="radio" id="button3" name="ball" value="2">
    9.     <label for="button3">2</label>
    10.     <input type="radio" id="button4" name="ball" value="3">
    11.     <label for="button4">3</label>
    12.     <input type="radio" id="button5" name="ball" value="4">
    13.     <label for="button5">4</label>
    14.     <input type="radio" id="button6" name="ball" value="5">
    15.     <label for="button6">5</label>
    16.     <input type="radio" id="button7" name="ball" value="6">
    17.     <label for="button7">6</label>
    18.     <input type="radio" id="button8" name="ball" value="7">
    19.     <label for="button8">7</label>
    20.     <input type="radio" id="button9" name="ball" value="8">
    21.     <label for="button9">8</label>
    22.     <input type="radio" id="button10" name="ball" value="9">
    23.     <label for="button10">9</label>
    24.     <input type="radio" id="button11" name="ball" value="10">
    25.     <label for="button11">10</label>
    26. </div>      
    27.   <button class="start" type="submit">Далее</button>
    28. </form>
    В обработчике green-handler.php


    Вот так, вместе с другими элементами, мы извлекаем из базы динамическую часть адреса (переменная $address_dynamic)

    PHP:
    1. $sql = "SELECT step, new_topics, old_topics, limit_seans, address_dynamic FROM user WHERE token = ?";
    2.     // Подготовка и выполнение SQL запроса
    3.     $stmt = mysqli_prepare($db, $sql);
    4.     mysqli_stmt_bind_param($stmt, "s", $token);
    5.     if (mysqli_stmt_execute($stmt)) {
    6.         // Привязка результатов запроса к переменным
    7.         mysqli_stmt_bind_result($stmt, $step, $new_topics, $old_topics, $limit_seans, $address_dynamic);
    8.         // Получение и вывод значений столбцов
    9.        mysqli_stmt_fetch($stmt);
    10.     }
    А здесь производим проверку
    PHP:
    1. // Проверка динамической части адреса для отсечения внешнего вызова
    2.     if(isset($_GET['d'])) {
    3.         if(!ctype_digit($_GET['d'])) die('<p class="error">Запрещённая операция. 55 <a href="/">На главную</a></p>');     // В адресе должный быть только цифры
    4.         if($address_dynamic != $_GET['d']) die('<p class="error">Запрещённая операция. 56 <a href="/">На главную</a></p>');     // Не совпадают адреса
    5.     }
    6.     else die('<p class="error">Запрещённая операция. 58 <a href="/">На главную</a></p>');
    И почему-то получается, что примерно 1 раз на 200-300 ответов выводится сообщение об ошибке 58.
     
  4. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.835
    Симпатии:
    651
    Эхо в ответ на POST-запрос? :) Даже обычные ошибки надо выводить по GET (в POST-обработчике можно сделать редирект на что-то вроде /error/55). В ответ на POST выводите только критические ошибки времени выполнения со статусами 5хх. Ну, и 4хх тоже можно.
     
  5. Vladd55

    Vladd55 Новичок

    С нами с:
    11 дек 2021
    Сообщения:
    88
    Симпатии:
    1
    Чтобы выйти на проблему, я в приведенном в теме коде организовал запись в текстовый файл пары адресов из базы и полученные через GET.

    Вот результаты:

    address_dynamic 626890 | $-_GET['d'] 626890
    address_dynamic 748117 | $-_GET['d'] 748117
    address_dynamic 467814 | $-_GET['d'] 626890

    В последней строке запись, после которой диагностирована ошибка. Адрес в базе именно такой (467814), а по GET пришел другой (626890). Видно, что такое число (626890) было на два цикла раньше. Похоже, что браузер, почему то, извлек старый адрес из кеша и решил его передать вместо требуемого. То есть, проблема в том, что браузер из кеша что-то подсовывает.

    Можно ли от этого избавиться?

    (На всех страницах стоит
    header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
    header("Cache-Control: post-check=0, pre-check=0", false);
    header("Pragma: no-cache")
     
  6. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.835
    Симпатии:
    651
    Не надо. Даже при современном качестве Интернета не надо исключать «недогрузы» и т.п.
    --- Добавлено ---
    Короче у вас все неправильно. Подобные заголовки кеша не помогают на 100% защититься от использования кнопки «Назад». Нужно просто запоминать, пройден был вопрос или нет.
    --- Добавлено ---
    А когда перейдете на вывод по GET, что правильно, ваши случайные числа нужно генерировать при обработке POST-запросов.
    --- Добавлено ---
    Вообще нафига при наличии авторизации самому пользователю доп. ключи в адресе и т.п., не понятно. Это только для защиты от CSRF-атак нужно, но не при явной работе пользователя с вашим тестом.
     
  7. Vladimir Kheifets

    Vladimir Kheifets Новичок

    С нами с:
    23 сен 2023
    Сообщения:
    425
    Симпатии:
    79
    Адрес:
    Бавария, Германия
     
  8. Vladimir Kheifets

    Vladimir Kheifets Новичок

    С нами с:
    23 сен 2023
    Сообщения:
    425
    Симпатии:
    79
    Адрес:
    Бавария, Германия
    Не понять зачем сохранять в БД address_dynamic перед отправкой формы.
    Сделал так.
    1 Перехватаем submit form и отправляем fetch post request на сервер.
    2.На сервере генерируем на РНР $randomNumber , записываем в сессию и возвращаем $randomNumber в fetch response
    3.Присваем полученное значени в <input type = "hidden" name ="d" >
    4.Отправляем форму на green-handler.php

    Скрипт с формой
    PHP:
    1. <?
    2. if(filter_input(INPUT_POST, 'step')){
    3.     session_start();
    4.     $randomNumber = random_int(105000, 995000);
    5.     $_SESSION["address_dynamic"] = $randomNumber;
    6.     echo $randomNumber;
    7.     exit;
    8. }
    9. ?>
    10. <html>
    11. <head>
    12. <script>
    13. window.addEventListener("load", () => {
    14.     var form = document.forms[0];
    15.     //------------------------------------------------
    16.     form.addEventListener("submit", (e) => {
    17.         e.preventDefault();
    18.         fd = new FormData(form);
    19.         fetch("?", {
    20.         method: "POST",
    21.         body: fd,
    22.         })
    23.         .then((response) => response.text())
    24.         .then((text) => {          
    25.             form.d.value = text;
    26.             form.submit();
    27.         });
    28.     });
    29.     //------------------------------------------------
    30. });
    31. </script>
    32. </head>
    33. </head>
    34. <body>
    35. <form action="green-handler.php" method="POST">
    36. <div class="radiobutton-group">
    37.     <input type="radio" id="button1" name="ball" value="100">
    38.     <label for="button1">0</label>
    39.     <input type="radio" id="button2" name="ball" value="1">
    40.     <label for="button2">1</label>
    41.     <input type="radio" id="button3" name="ball" value="2">
    42.     <label for="button3">2</label>
    43.     <input type="radio" id="button4" name="ball" value="3">
    44.     <label for="button4">3</label>
    45.     <input type="radio" id="button5" name="ball" value="4">
    46.     <label for="button5">4</label>
    47.     <input type="radio" id="button6" name="ball" value="5">
    48.     <label for="button6">5</label>
    49.     <input type="radio" id="button7" name="ball" value="6">
    50.     <label for="button7">6</label>
    51.     <input type="radio" id="button8" name="ball" value="7">
    52.     <label for="button8">7</label>
    53.     <input type="radio" id="button9" name="ball" value="8">
    54.     <label for="button9">8</label>
    55.     <input type="radio" id="button10" name="ball" value="9">
    56.     <label for="button10">9</label>
    57.     <input type="radio" id="button11" name="ball" value="10">
    58.     <label for="button11">10</label>
    59. </div>
    60. <input type = "hidden" name="step" value=1>
    61. <input type = "hidden" name ="d" >
    62. <button class="start" type="submit">Далее</button>
    63. </form>
    64. </body>
    65. </html>
    script green-handler.php
    PHP:
    1. <?
    2. $err0 = '<p class="error">Запрещённая операция. 58 <a href="/">На главную</a></p>';
    3. if($_SESSION["address_dynamic"]){
    4.     $address_dynamic = $randomNumber = $_SESSION["address_dynamic"];
    5.     $token = $_SESSION["token"];
    6.     $d = filter_input(INPUT_POST, 'd');
    7.     if($d)
    8.     {
    9.         if(!ctype_digit($d)) die('<p class="error">Запрещённая операция. 55 <a href="/">На главную</a></p>');     // В адресе должный быть только цифры
    10.         if($address_dynamic != $d ) die('<p class="error">Запрещённая операция. 56 <a href="/">На главную</a></p>');     // Не совпадают адреса
    11.         else
    12.         {
    13.             $sql = "UPDATE user SET address_dynamic = ? WHERE token = ?";
    14.             // Подготовка и выполнение SQL запроса
    15.             $stmt = mysqli_prepare($db, $sql);
    16.             mysqli_stmt_bind_param($stmt, "is", $randomNumber, $token);
    17.             if (mysqli_stmt_execute($stmt))
    18.             {
    19.                 //echo "Data updated successfully";
    20.             }
    21.         }
    22.     }
    23.     else
    24.         die($err0);
    25.     }
    26. else
    27.     die($err0');
    Удачи!
     
  9. Vladimir Kheifets

    Vladimir Kheifets Новичок

    С нами с:
    23 сен 2023
    Сообщения:
    425
    Симпатии:
    79
    Адрес:
    Бавария, Германия
    Дополнение
    Независимо от реализации советую Вам закрывать кнопку submit пока не будет отчековано radio
    Код (Javascript):
    1. <script>
    2. window.addEventListener("load", () => {
    3.     var form = document.forms[0];
    4.     var balls = form.ball;
    5.     var nBalls = balls.length;
    6.     var start = document.querySelectorAll(".start")[0];
    7.     start.setAttribute("disabled","true");
    8.     //------------------------------------------------
    9.     clickBall = function(e){
    10.       start.removeAttribute("disabled");
    11.       for (i = 0; i < nBalls; i++)
    12.         balls[i].removeEventListener("click", clickBall);
    13.     }
    14.     //------------------------------------------------
    15.     for (i = 0; i < nBalls; i++)
    16.         balls[i].addEventListener("click", clickBall);
    17.     //------------------------------------------------
    18.     form.addEventListener("submit", (e) => {
    19.         e.preventDefault();
    20.         fd = new FormData(form);
    21.         fetch("?", {
    22.         method: "POST",
    23.         body: fd,
    24.         })
    25.         .then((response) => response.text())
    26.         .then((text) => {
    27.             form.d.value = text;
    28.             form.submit();
    29.         });
    30.     });
    31.     //------------------------------------------------
    32. });
    33. </script>


    В green-handler.рхр в 28 строке нужно убрать кавычку.
    Улачи!
     
    #9 Vladimir Kheifets, 8 ноя 2024
    Последнее редактирование: 8 ноя 2024