| Ванька ( @ 2008-05-13 20:15:00 |
| Entry tags: | programming, web-development |
Redirect after POST
Немного о тонкостях протокола HTTP.
Q: Нужно ли делать редирект после обработки данных, полученных при отправке формы методом POST?
Q: Какой статус выдавать в редиректе — 302, 303 или 307?
A:
По логике редирект надо выдавать всегда, когда мы хотим избежать двойной отправки данных при нажатии в браузере на reload. Иногда двойная отправка бывает полезной (при реализации визардов), но в большинстве случаев — потенциально опасна (например, при случайном нажатии F5 в банковской системе после транзакции произойдет повторное списание денег со счета).
Большинство существующих веб-приложений выдает статус 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[…]
К сожалению, большинство приложений исторически забили на стандарт, и обрабатывают этот статус так же, как 303 (почему к сожалению — объясню ниже).
Для автоматического перенаправления пользователя на новый урл как раз и предназначен статус 303 (See Other):
This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource.
Статус 307 (Temporary Redirect) отличается от 303 по смыслу: если 303 указывает на новый урл, то 307 — на альтернативный, тот урл, по которому рекомендуется обращаться в дальнейшем вместо первоначального:
The requested resource resides temporarily under a different URI.
Этот статус используется редко, и опять же, стандарт запрещает автоматический переход в ответ на запрос методом POST, поэтому забудем о нем.
Теперь, о самом главном. Все бы было хорошо, если бы не особенности реализации некоторых браузеров. Вот тут мы сталкиваемся с такими проблемами:
- некоторые старые юзер-агенты так и не научились понимать HTTP/1.1, в котором отсутствует статус 303 (предыдущая версия стандарта HTTP/1.0 описана в RFC 1945, принятом на год раньше RFC 2068, где этот статус уже был, но пока не было 307),
- некоторые юзер-агенты все-таки свято соблюдают стандарты, и поэтому выдают предупреждение в ответ на редирект 302 после отправки формы методом POST,
- некоторые юзер-агенты еще и интерпретируют стандарты по-своему, например, после выдачи редиректа они повторяют отправку данных через POST (!) на новый урл, вызывая непредсказуемое поведение.
Хорошая новость в том, что таких браузеров абсолютное меньшинство, и в основном замечены они были на просторах вапа, среди производителей мобильных девайсов.
Но все-таки, мы ведь хотим предоставить качественный сервис всем пользователям без исключения?
Одной из особенностей статуса 303 является то, что по стандарту урл, на который осуществляется переход, должен запрашиваться по протоколу GET:
The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource.
Можно воспользоваться этой особенностью, а можно придумать более универсальное решение (тем более глагол SHOULD в rfc вносит не строго обязательный характер):
- проанализировать версию HTTP протокола, которую запросил пользователь,
- отказаться от статуса 302 для клиентов с версией 1.1 в пользу статуса 303,
- всегда добавлять в урл для редиректа параметры, позволяющие предотвратить непредсказуемую повторную отправку при редиректе (например, в чате, при редиректе «на самого себя»).
Кроме того, стандартом настоятельно рекомендуется сопровождать тело редиректов документом со ссылкой на перенаправляемую страницу, а для метода POST желательно добавить пояснение: какие действия нужно выполнить, чтобы, перейдя по ссылке, повторить запрос. Это нужно для совместимости между старыми, ошибочными, или неполными реализациями протокола.
Пару слов про другие статусы.
301 (Moved Permanently) — подразумевается, что для одного и того же запроса выдается сервером постоянно, всегда, вне зависимости от каких-либо других факторов, в отличие от 307 — который может быть выдан в зависимости от некоторых внешних условий. Этот статус в большинстве юзер-агентов полностью не поддерживается, браузеры не подменяют после редиректа ссылки так, как это следовало бы делать по стандарту, и скорее всего, обрабатывают его так же, как и 302.
304 (Not Modified) связан с кешированием, об этих особенностях постараюсь рассказать в следующий раз.
300 (Multiple Choices) предназначен для выбора одного урла из нескольких альтернативных, в жизни реального применения я не встречал.
305 (Use Proxy) предназначен для принудительного направления запроса через прокси, на практике не встречал.
306 (Unused) — уже не используется.
Продолжение следует.