Редирект после POST запроса

    Каждый веб-разработчик знает, что после POST сабмита формы желательно сделать редирект, чтобы предотвратить повторную отправку данных, когда пользователь захочет обновить страницу. В основном это критически необходимая операция, так как данные формы могут сохраняться в базе данных или участвовать в платёжной транзакции. И тогда данные не только продублируются, но и спишутся лишние деньги.

    Но речь не о деньгах, а о правильном редиректе…

    Практически все веб-приложения при редиректе POST запроса возвращают статус 302 Found. Например, в php редирект делают так: header('Location: /new/location');. Без дополнительных параметров или если отдельно не указан другой статус, функция вернёт именно 302 Found.

    Теперь обратимся в официальным документам. В RFC 2616 сказано следующее:
    If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

    Если статус 302 получен в ответ на запрос отличный от GET или HEAD, юзер-агент НЕ ДОЛЖЕН автоматически редиректить запрос до тех пор, пока он не будет подтверждён пользователем, так как это может нарушить условия запроса.

    Там же в заметках написано, что несмотря на это, многие юзер-агенты пренебрегают этим правилом и интерпретируют 302 статус как 303. А пошло это ещё со времён HTTP/1.0, в котором 303 статуса ещё не было.

    Т.е. для редиректа POST запроса нужно использовать статус 303 See Other, который специально для этого и предназначен. В php редирект будет выглядеть, например, так: header('Location: /new/location', true, 303);

    В RFC в заметке к статусу 303 написано:
    Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303

    Многие пре-HTTP/1.1 юзер-агенты не понимают 303 статус. Если совместимость с такими клиентами важна, то вместо него можно использовать 302 статус, так как большинство таких агентов реагируют на 302 статус также как на 303.

    И получается два варианта:
    1. По прежнему использовать 302;
    a. есть вероятность нарваться на юзер-агента, который чтит спецификацию и выдаст предупрежление.
    б. так как такое поведение не стандартно, можно нарваться на вообще непредсказуемый результат.

    2. Использовать 303, тогда старые клиенты не поймут, что от них хотят.

    Во втором случае, можно анализировать версию протокола, запрошенную клиентом, и выдавать 302 для старых клиентов. В теле ответа писать ссылку на новый УРЛ. Тогда пользователь старого агента, сможет хотя бы кликнуть на ссылку.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 79

      –21
      Интересную конечно тему подняли. Теперь стоит думать, что и где применять
        +18
        >>есть вероятность нарваться на юзер-агента, который чтит спецификацию и выдаст предупрежление.

        Пока про такой юзер-агент не слышал. Как появится — будем решать проблему :)
          +1
          Я тоже. Но есть вероятность, что они появятся, например, среди мобильных приложений. В любом случае, лучше быть в курсе=)
            +10
            Да никто не будет делать такое. Везде нормально редиректится, а в новом продукте не будет? Кто после этого будет его использовать? :)
            +2
            А так же (к автору поста):
            > Использовать 303, тогда старые клиенты не поймут, что от них хотят.
            это какие, например?
              +7
              Например:
              Opera младше 4.0,
              Internet Explorer младше 4.0

              Может для кого-то критична поддержка и этих браузеров=)
                +3
                В таком случае, при всей моей романтической любви к PC-антиквариату, «следовать стандарту и использовать 303 или заботиться о совместимости со старыми клиентами и использовать 302?» кажется мне вопросом из компетенции капитана Очевидность.
              +1
              Например, Zend_Http_Client из ZF с включенной опцией strict_redirects, которая, кстати говоря, по умолчанию выключена.
              –5
              Мне кажется лишним выносить подобную логику в PHP (или любой другой серверный язык), выдачей «правильного» кода должен заниматься веб-сервер, когда увидит заголовок Location, если это действительно может на что-то повлиять (поскольку Apache отправляет 302, вероятно, это-таки ни на что не влияет :)). Так-то!
                +9
                Ну прям уж) Не должен сервер это делать, вы что. Что значит «правильный код»? Это должно приложение решать — какой код возврата «правильный», это прикладное понятие, а не протокольное.
              • НЛО прилетело и опубликовало эту надпись здесь
                • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      Не думаю что return будет корректным. В фреймворках обычно бросается некий Redirect_Exception, который обрабатывается контроллером (корректно завершается работа). А если контроллера, как такового, нет то die() или exit именно то что нужно.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Ох, озадачили. Фреймворк это набор компонентов, с уже решенными типичными (и не очень) задачами а также облегчающие и ускоряющие разработку. Например ZF, Symfony

                          Или это шутка? )
                            +1
                            это тролль
                            • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                табличку надо было по-больше
                                • НЛО прилетело и опубликовало эту надпись здесь
                    • НЛО прилетело и опубликовало эту надпись здесь
                        +3
                        это для чувства глубокого удовлетворения ;)
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              В Опере можно отключить редиректы и я часто это делаю для отладки.
                              • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Яваскрипт мерзкий убрали бы…
                                +1
                                Тогда уж было бы правильным ввести условие для header($_SERVER['SERVER_PROTOCOL']." 303 See Other").
                                Чтобы при $_SERVER['SERVER_PROTOCOL']=='HTTP/1.0' возвращался 302-й код.
                                –5
                                Неужели так сложно сделать защиту от повторного постинга без редиректа?
                                Всё же не для этого ИМХО редиректы придуманы.
                                  0
                                  да легко. например в форму внедряется скрытый ключ…
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                      0
                                      Да, про 303 конечно соглашусь — ваша правда.

                                      Хотя мне как-то ближе способ с дополнительным уникальным скрытым полем — он ко всему прочему от ботов защищает.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                          0
                                          ещё помогает задизэблить кнопку после нажатия
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              –1
                                              а сейчас это такая большая редкость?
                                              +3
                                              Если потом вернуться кнопкой «Back», то кнопка окажется заблокированной, и придется перезагружать страницу, чтобы ее разблокировать.
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Да, вы совершенно правы — это ещё один аргумент чтобы отказаться от редиректа в пользу скрытого поля :)
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                  0
                                                  Совершенно верно. Но в топике шла речь именно о повторной отправке данных.
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                  0
                                                  Ну, а откуда возьмется скрытое, сразу поле после того, как юзер сабмитил форму, если страница еще не успела загрузиться снова?
                                                    +1
                                                    Скрытое поле генерится *до* сабмита формы и передаётся вместе с ней.
                                                    Если сервер видит что такое значение ему уже передавали — даёт отлуп.
                                                      0
                                                      Я имел ввиду вот этот момент:
                                                      >иногда пользователи успевают несколько раз отправить форму (кликая по кнопке в ожидании)
                                                      То есть если по вашему, то все равно нужно генерить яваскриптом либо новое значение для хайдена, либо дисэблить кнопку.
                                                      Однако, редирект — это более кошерно.
                                                        +1
                                                        Значение генерится не на клиенте, а на сервере. Второй раз ничего генерить как раз не нужно — мы эту форму второй раз принимать не хотим.
                                                          0
                                                          Черт, даже стыдно, что для меня пришлось разжевывать, но буду убеждать себя, что это все из-за сложного дня, а не из-за того, что тупею.
                                                            0
                                                            В том-то и дело что не хотим, и обязательно скажем об этом пользователю выведя ошибку вместо красивой странички с успешной регистрацией :)
                                                        0
                                                        оно там было с самого начала, с произвольным значением — если запросы приходят с одинаковыми значениями — нужно игнорировать все, кроме первого.
                                                          0
                                                          Второй вариант — брать не произвольные, а известные значения для этого поля, чтобы сервер знал, какую форму он сгенерил, а какую — чужой дядя.
                                                        +6
                                                        Отказываться от редиректа все-таки не надо.
                                                        С редиректом для пользователя сохраняется возможность свободно гулять по истории без предупреждений броузера «сейчас вы повторно отправите какие-то данные».

                                                        Т.е. использовать POST запросы только как setter'ы (что-то меняющие на стороне сервера, но ничего не отображающие), а GET — как getter'ы (только отображающие, но никогда не меняющие ничего на стороне сервера) — идеологически правильно.

                                                        Более того, если мы переходим по какому-то специальному урлу (линк с картинкой кнопки) и при этом что-то меняется в даных на стороне сервера, правильно и после этого сделать редирект.
                                                          0
                                                          Согласен. crud далеко не худший способ организации веб-приложения.
                                                      +1
                                                      для бота не проблема запросить форму с уникальным скрытым полем перед отправкой. это скорее защита от CSRF.
                                                        +1
                                                        Это если он не совсем тупой.
                                                        А большинство ботов (по крайней мере те, которые мне пытаются досаждать) — совсем тупые.
                                                    0
                                                    Отправлять форму ajax-запросом, если включен js.
                                                    0
                                                    Если автор поста проведет тестирование различных браузеров в такой ситуации, мы ему будем очень благодарны. Может быть все браузеры корректно себя ведут?
                                                      0
                                                      Не понял вопроса. Что значит «все браузеры корректно себя ведут»? Все браузеры, поддерживающие HTTP/1.1 понимают 303 статус. И все популярные браузеры интерпретируют 302 статус как 303.
                                                        0
                                                        Совершенно верно, все браузеры не являющиеся музейными экспонатами, ведут себя корректно. Я думаю, это вопрос скорее академического характера.
                                                          +2
                                                          скорее все браузеры ведут себя некорректно)
                                                            –1
                                                            если есть спецификация, которая всеми выполняется как-то по другому, то наверное в спеке ошибка, а не в реализациях.
                                                        –1
                                                        Есть какая практическая польза?
                                                          0
                                                          Через 10 лет ваше приложение продолжит работать правильно, в то время как у других бразуер будет спрашивать пользователя при переходе.
                                                            0
                                                            заметил, что в последнем сафари все чаще приходится тыкать на кнопку «если вас не перебросило автоматически тыкните сюда»… это оно? уже началось?
                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                            0
                                                            это факт. потому что запостить повторно форму можно и умышленно.
                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                              +1
                                                              Врядли кто-то сейчас использует настолько старый клиент, который не поддерживает HTTP/1.0
                                                              Большинство сайтов хостятся на виртуальных хостингах, где без HOST заголовка не попадешь на нужный сайт.
                                                                0
                                                                тьфу…
                                                                * который не поддерживает HTTP/1.1
                                                                  +1
                                                                  >Врядли кто-то сейчас использует настолько старый клиент, который не поддерживает HTTP/1.0
                                                                  Если твои запросы в мир идут через squid, скажем, то они будут именно http/1.0. А таких ой как немало.
                                                                  +2
                                                                  например, так: header('Location: /new/location', true, 303);

                                                                  Например, не так. URL в Location должен быть абсолютным:

                                                                  The field value consists of a single absolute URI.

                                                                  Location = «Location» ":" absoluteURI

                                                                  RFC 2616, п.14.30.
                                                                    +1
                                                                    И правда:) Несмотря на то, что все браузеры поддерживают относительные URL'ы, по стандарту он должен быть абсолютным.
                                                                      0
                                                                      302 тоже все браузеры поддерживают.

                                                                      уж если педантствовать — то во всем! ;)

                                                                      кстати, если предположить, что некий сферический браузер в вакууме реализован строго по букве стандарта, то 302 не так страшно как относительный адрес.
                                                                        –1
                                                                        есть еще одна проблема, на сей раз уже практическая.

                                                                        ныне совершенно типична конфигурация сервера, когда фронтендом стоит nginx, проксирующий динамику на апач. Так вот проксирование делается по http/1.0; ответ http/1.1 будет просто некорректен (и может иметь крайне негативные последствия в виде отдачи chunked transfer encoding nginx-у, сделавшему запрос по 1.0 и этого никак не ожидающему); a http/1.0 303 формально некорректен, что будет практически — надо проверять.
                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                            0
                                                                            И прекрасно понимает http/1.1, пока это не касается работы с бэкендом.

                                                                            Кстати host в 1.0 никаким образом не запрещается, в 1.1 же он просто обязателен
                                                                    +5
                                                                    Хороший пример рака мозга, причем, заразного.

                                                                    Итак, в вебе миллионы серверов с миллионами приложений, которые написаны с использованием 302. Старые клиенты, которые не знали про 303 постепенно отмирают, но приложения на серверах остаются и по-прежнему используют 302 вместо 303. И будут, потому что это вошло в образование веб-программистов и впитано с исходниками примеров из книжек.

                                                                    Разработчики стандарта поступили не очень мудро. Нужно было быть ближе к народу и наделить 302 именно тем смыслом, который в него вкладывают те, кто его использует. А «старый 302» переименовать в 303-й =)

                                                                    И все дела =)
                                                                      –1
                                                                      Не надо париться по этому поводу :) Направление уже задано, и переделывать кучу софта смысла нет
                                                                        0
                                                                        задумывался об этом, читая rfc. но пришел к выводу, что фактически это бесполезная хрень. все клиенты понимают 302 и автоматически редиректят, поэтому инсинуации в rfc про 303 ни кого не волнуют. :)

                                                                        «проблему» стоило бы порешать лишь в одном случае — если бы браузеры действительно запрашивали потдветждение юзера при 302 редиректе. но этого нет и никогда не будет, потому, что так устроен современный интернет. :)
                                                                          0
                                                                          Неожиданно столкнулся с проблемой: IE9 делает 302 редирект после POST тоже с помощью POST, т.е., если я правильно понимаю, он отрабатывает 302 статус как 307, в то время, как Firefox и Chrome отрабатывают 302 статус, как 303.

                                                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                          Самое читаемое