Tuesday, May 13th, 2008

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) — уже не используется.

Продолжение следует.
(3 comments | Leave a comment)