Как HTML-форма отправляет данные и как безопасно обработать их на сервере

HTML / Безопасность

Как HTML-форма отправляет данные и как безопасно обработать их на сервере

Путь данных от элементов формы до PHP: action, method, имена полей, GET и POST, валидация, CSRF и безопасный вывод ошибок.

Опубликовано 04.09.2026 7 минут чтения
Содержание статьи

HTML собирает данные и описывает способ отправки, но сам их не сохраняет. После нажатия кнопки браузер формирует HTTP-запрос, а серверная программа проверяет значения и выполняет нужное действие: отправляет письмо, создаёт запись или возвращает ошибки.

Разберём весь путь на форме обратной связи.

Из чего состоит форма

HTML
<form action="/feedback" method="post">  <div>    <label for="name">Имя</label>    <input id="name" name="name" type="text" autocomplete="name" required>  </div>​  <div>    <label for="email">Email</label>    <input id="email" name="email" type="email" autocomplete="email" required>  </div>​  <div>    <label for="message">Сообщение</label>    <textarea id="message" name="message" rows="6" maxlength="3000" required></textarea>  </div>​  <button type="submit">Отправить</button></form>​

Главные атрибуты <form>:

  • action — адрес обработчика;
  • method — HTTP-метод, обычно get или post;
  • enctype — формат тела запроса; для загрузки файлов нужен multipart/form-data.

У каждого отправляемого поля должен быть name. id связывает элемент с <label>, но не определяет имя параметра.

Какие элементы попадают в запрос

Браузер отправляет только успешные элементы управления. В частности:

  • поле должно иметь name;
  • disabled-поле не отправляется;
  • у группы radio отправляется выбранное значение;
  • у checkbox отправляется значение только при установленном флажке;
  • нажатая submit-кнопка может передать собственные name и value.
HTML
<label>  <input type="checkbox" name="agreement" value="yes" required>  Я согласен с правилами</label>​

Сервер не должен ожидать, что agreement существует в каждом запросе: клиент может снять атрибут required или отправить запрос вручную.

GET или POST

GET подходит для операций чтения: поиска, фильтрации и пагинации. Параметры видны в URL, ссылку можно сохранить и передать.

HTML
<form action="/search" method="get" role="search">  <label for="query">Поиск по сайту</label>  <input id="query" name="q" type="search">  <button type="submit">Найти</button></form>​

Результат может открыться по адресу /search?q=html.

POST используют, когда запрос изменяет данные или передаёт большой объём. Значения не появляются в адресной строке, но это не шифрование: конфиденциальность в пути обеспечивает HTTPS. Пароли и токены нельзя писать в URL.

Клиентская валидация

Атрибуты HTML помогают найти часть ошибок до отправки:

HTML
<input  id="email"  name="email"  type="email"  maxlength="254"  autocomplete="email"  required>​

Можно использовать min, max, minlength, maxlength и pattern, но не следует превращать каждое правило в сложное регулярное выражение. Например, type="email" понятнее самодельного паттерна для адреса.

Клиентская проверка служит удобству. Сервер обязан заново проверить тип, длину, формат и допустимые значения.

Безопасный обработчик на PHP

Ниже показан минимальный учебный обработчик. Он не отправляет письмо и не пишет в базу, а демонстрирует проверку запроса, CSRF-токена и данных.

PHP
<?php​declare(strict_types=1);​session_start();​if (!isset($_SESSION['csrf_token'])) {    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));}​$errors = [];$name = '';$email = '';$message = '';​if ($_SERVER['REQUEST_METHOD'] === 'POST') {    $token = is_string($_POST['csrf_token'] ?? null)        ? $_POST['csrf_token']        : '';​    if (!hash_equals($_SESSION['csrf_token'], $token)) {        http_response_code(403);        exit('Не удалось проверить запрос. Обновите страницу.');    }​    $name = is_string($_POST['name'] ?? null)        ? trim($_POST['name'])        : '';    $email = is_string($_POST['email'] ?? null)        ? trim($_POST['email'])        : '';    $message = is_string($_POST['message'] ?? null)        ? trim($_POST['message'])        : '';​    if ($name === '' || mb_strlen($name) > 100) {        $errors['name'] = 'Укажите имя длиной до 100 символов.';    }​    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {        $errors['email'] = 'Укажите корректный email.';    }​    if ($message === '' || mb_strlen($message) > 3000) {        $errors['message'] = 'Введите сообщение длиной до 3000 символов.';    }​    if ($errors === []) {        // Здесь вызывают отдельный сервис сохранения или отправки.        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));        header('Location: /feedback/success', true, 303);        exit;    }}​

is_string() защищает от неожиданного массива вместо строки. trim() нормализует пробелы по краям, но не делает значение безопасным автоматически. Каждое поле проходит отдельную проверку.

Добавление CSRF-токена в форму

Изменяющий POST-запрос должен быть связан с пользовательской сессией:

PHP
<form action="/feedback" method="post">  <input    type="hidden"    name="csrf_token"    value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>"  >  <!-- Поля формы --></form>​

Скрытое поле не является доверенным: сервер сравнивает его с токеном сессии через hash_equals().

Как вернуть значения и ошибки

При повторном показе формы недоверенные данные экранируют в момент вывода:

PHP
<label for="name">Имя</label><input  id="name"  name="name"  type="text"  value="<?= htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"  <?= isset($errors['name']) ? 'aria-invalid="true" aria-describedby="name-error"' : '' ?>>​<?php if (isset($errors['name'])): ?>  <p id="name-error"><?= htmlspecialchars($errors['name'], ENT_QUOTES, 'UTF-8') ?></p><?php endif; ?>​

Для <textarea> значение помещается между тегами и тоже экранируется. Не вставляйте пользовательскую строку через innerHTML и не храните «уже экранированные» данные: контекст вывода может измениться.

Сохранение в базе

Если данные попадают в SQL, используйте подготовленный запрос. Экранирование HTML не защищает от SQL-инъекции.

PHP
$statement = $pdo->prepare(    'INSERT INTO feedback (name, email, message) VALUES (:name, :email, :message)');​$statement->execute([    'name' => $name,    'email' => $email,    'message' => $message,]);​

Валидация, SQL-параметры и HTML-экранирование решают разные задачи, поэтому ни один слой не заменяет другой.

Что проверить перед публикацией

  • форма работает с клавиатуры, у всех полей есть <label>;
  • сервер принимает только ожидаемый метод;
  • значения проверяются по типу, длине, формату и allowlist;
  • POST защищён CSRF-токеном;
  • SQL использует параметры;
  • пользовательский текст экранируется при выводе;
  • после успешного POST выполняется redirect, чтобы обновление страницы не повторяло отправку;
  • публичная ошибка не раскрывает внутренние пути, запросы и настройки базы.

HTML-форма — только начало цепочки. Надёжный сценарий появляется, когда разметка, серверная валидация, защита запроса и понятная обратная связь работают вместе.

Сообщить об ошибке в статье

Рекомендации редакции

Другие публикации из этого раздела.

Новые материалы

Получайте практические разборы без информационного шума

Одна общая рассылка о новых статьях и инструментах. Адрес будет активироваться только после подтверждения.

Подписка появится после подключения сервиса рассылок и Yandex SmartCaptcha.