Техническая реализация REST & user friendly уведомлений после редиректов

    Иногда есть необходимость показывать пользователю уведомления после редиректа уже на новой странице.
    В статье описаны достоинства и недостатки нескольких реализаций таких уведомлений,
    image


    Пример


    Есть внутренний поиск по сайту:
    image

    Хотим в случаях когда результат только один сразу редиректить на его страницу, минуя полу-бесполезный список:
    image

    Удивлённый пользователь — счастливый пользователь


    Просто так это делать плохо: что если наш поиск нашёл совсем не того Васю Пупкина которого искал пользователь, например, если другого у нас попросту нет?

    Решение: на странице с профилем Васи мы будем выводить уведомление, что у нас есть только один Вася Пупкин и на его страницу вы сейчас смотрите:
    image

    Не REST-friendly решение


    Перед тем как перенапавить пользователя на страницу Васи мы пишем в сессионное хранилище признак того, что хотим показать ему уведомление.
    Грубый пример на PHP:
    <?php
    function doRedirect($location) {
        $_SESSION['show_notice'] = true;
        header("Location: $location");
    }

    Когда показываем страницу Васи, проверяем флаг:
    <?php
    if (@$_SESSION['show_notice']) {
        ... показываем уведомление ...
    }


    Чем плохо такое решение?
    1. Если пользователь начнёт открывать много страниц одновременно, то в зависимости от реализации может наблюдать разные интересные эффекты (мелочи по сути).
    2. Если вы захотите целиком кэшировать страницы, проблемы с учётом зависимостей не заставят себя долго ждать.

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

    UPD: Кстати если вместо сессии использовать куки то вторая проблема обретает решение. Жаль что первая обостряется только сильнее :)

    Чтобы обойти эти две проблемы информацию о том показывать уведомление или нет надо передавать в URL-e.

    Приходим к очередному решению.

    GET параметр


    Тут всё просто — просто кидаем пользователя, скажем, на location/vasya/?show_notice=1:
    <?php
    function doRedirect($location) {
        header("Location: $location?show_notice=1");
    }

    и при рендериге шаблона проверяем флаг:
    <?php
    if (@$_GET['show_notice']) {
        ... показываем уведомление ...
    }

    • Никаких глюков с одновременно открывающимися страницами.
    • Можно кэшировать страницы «в лоб» по URL-у.

    Самый настоящий REST и только две неприятности:
    • если будем кэшировать — страница будет лежать в кэше два раза — с блоком, и без него (по сути мелочь).
    • пользователи начнут давать друг другу ссылки с "?show_notice=1" — это уже не очень приятно..

    document.location.hash


    Ещё одно место куда можно запихнуть информацию об уведомлении это хэш.
    Получится что-то вроде:
    <?php
    function doRedirect($location) {
        header("Location: $location#show_notice");
    }


    тогда пользователя перебросит, например, на localhost/vasya/#show_notice

    В отличие от GET параметров, сервер эту информацию попросту не получит так что показать уведомление получится только на клиенте, например, с помощью Javascript + PrototypeJS:

    <div id="notice" class="notice" style="display:none">Найдена только одна страница</div>
    <script type="text/javascript">
        if(document.location.hash == '#show_notice')) {
            $("notice").show();
            document.location.hash = '';
        }
    </script>


    Сразу после загрузки страницы localhost/vasya/#show_notice адрес сменится (без перезагрузки!) на localhost/vasya/# (назойливую решётку убрать никак не получается, но ничего страшного в ней опять же нет).
    Поделиться публикацией

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

      +2
      Хм, сперва полагал, что речь идёт о юзабилити-примерах, а тут программирование. Было бы неплохо пояснить, о чём речь в статье далее до ката.
      Спасибо.
        0
        спасибо, написал немного яснее о чём речь
        0
        Про хэш не задумывался даже. Между сессием и get-параметром выбрал сессию. Может позже на хэш переделаю. Спасибо
          0
          а я вот от сессии отказался из за кеширования, пользовался гетом, но соответственно баги при возврате по хистори

          хэш действительно хороший вариант
            0
            Это кеширование мешает только в опере. Я на него внимания не обращаю. И как уже сказали ниже я в сессию сохраняю и адрес страницы на которой показывать уведомление. Проблем не замечено
          +2
          Можно еще куку вешать.
          Ее уже можно и из js проверять и на стороне сервера, в зависимости от задачи.
            0
            с кукой есть проблемы примерно как с сессией, только более ярко выраженные,
            например если куку снимать при показе страницы после редиректа, то если странице не дать открыться, — уведомление вылезет уже позже в неподходящем случае
              +1
              Куку можно вешать с очень маленьким ttl и/или уникальную для страницы, например со значением урала самой страницы.
                +1
                Перекуем мечи на урала! =)
                  0
                  куку нужно вешать с такой областью видимости чтобы она только на целевой странице и была доступна. с маленьким TTL это решит вопрос с «запоздалым проявлением», а если в ней передавать «идентификатор сообщения» (которые хранятся отдельно) то это решит и проблемы одновременным открытием нескольких страниц с разыми сообщениями.
              +2
              где холивар насчет того, что у пользователя JS отключен? :)
                0
                можно отмазаться тем что ничего страшного без JS по сути и не произойдёт :) просто небольшая деградация интерфейса
                  +1
                  Пользователей без JS в учет не беру, для меня это тоже самое что и верстать под IE6
                    0
                    на мобильниках JS полноценного нет
                      0
                      Сайту на мобильном — отдельная версия
                        +1
                        это устаревший подход — сейчас из-за наличия нетбуков и коммуникаторов нет уже четкой границы между desktop и мобильником
                  +1
                  Для борьбы с открытием нескольких окон — кто мешает сохранять более полные данные в сессии? Не только сам факт редиректа (show_notice=1), но и адрес страницы, куда перекидывается юзер (location=$location)? Если откроет несколько страниц — нотификашка покажется только на той, у которой совпадает URI.

                  А если при открытии окна сразу убирать обе переменные из сессии — тогда вообще даже при повторном открытии окна (по F5) ничего не покажется.
                    0
                    Это тоже не панацея :) (для кук в большей степени, для сессии это надо умудрится, но теоретически тоже можно)

                    1. меня кидает на /a.php
                    2. я не даю ей загрузится (тупо кликаю по закладке и ухожу в другое место), т.е. JS-ы которые удалят куки не выполняются
                    3. гуляю по сайту, захожу на /a.php — и вижу уведомление. не очень к месту :)

                    Это можно лечить таймаутами — но опять же, на глючном GPRS-е страница может 30 секунд запросто грузиться. А для быстрого интернета за 30 секунд вполне можно вопсроизвести описанный ранее глюк :)

                    Если чесно, все эти заморочки не стоят своей разработки.
                    Да и проблемы при использовании сессии или кук появляются как раз от того что это неподходящее место для такой информации.
                      0
                      Погоди, при чем здесь JS? Наверное слово «окно» сбило с толку… Я говорю о выполнении этих вещей на сервере. Идет запрос на открытие страницы (шлются заголовки от браузера), PHP-парсер отрабатывает страницу, видит из переменных сессии, что стоит редирект на вызываемую страницу. Ну и далее — он сначала убирает 2 теперь уже лишние переменные из сессии и выдает страниы с примечанием наверху. Всё, при следующем обращении на эту же страницу ничего не показывается. При обращении на соседние страницы оно в принципе не отобразится — поскольку мы запомнили, куда именно надо показать инфу.
                        0
                        ну да, всё правильно, с сессией на практике всё сильно лучше чем с куками (хотя логически проблемы те же)

                        Мне просто по жизни механизм кэширование мешает пользоваться сессией, и я её как-то мысленно всё время отбрасываю.
                        Хотя когда такой проблемы нет, сессия и без лишних заморочек отлично справляется — очень удобно, — можно в любом месте кода добавлять туда уведомления и не думать когда произойдёт редирект.
                          0
                          Чем пользуешься для кеширования? Кеширование ведь надо параметризовывать — хотя бы для того, чтобы не отдавать одну и ту же инфу залогинены пользователям и гостям.
                            0
                            всё самописное, и с параметризацией всё порядке.

                            проблема, как я сейчас вспомнил, была немного хитрее — у меня поиск кэшировал редирект на страницу с результатом, — т.е. при попадании в кэш редирект происходил, а вот в сессию сообщение уже никто не писал, решение сохранять вместе с редиректом часть сессии показалось уж слишком странным, — тут-то и начались изыскания :)
                              0
                              Не совсем понял, что значит «кешировал редирект»? Ты делаешь поиск по базе, получаешь данные, и на основании того, что в этих данных только 1 запись — делаешь редирект. Так ведь? Где в этой цепочке кеш?
                                0
                                Кэширование идёт на самом верхнем уровне, до поиска
                                1. приходит запрос /search?q=вася
                                2. я к нему дописываю зависимости (например признак того что это страница для гостя)
                                3. смотрю нет ли чего у меня в кэше по такому запросу,
                                4. и не лазя в базу отдаю редирект
                                  0
                                  Собсно, взяв на шаге 3 данные, приписываешь в сессию пару переменнх и отдаешь редирект. Не знаю подробностей устройста, конечно… однако примерно — так.
                    –5
                    А никто не задумывался, что подобная проблема (notice'ы) возникает только если движок криво оперирует данными?

                    Я имею ввиду, например когда у программиста нет возможности на самой странице результатов поиска написать что-то типа:
                    if (только один вариант) {
                    показать(notice);
                    обработать_и_показать(только один вариант);
                    } else {
                    показать все результаты поиска;
                    }

                    и не будет проблем с никакими редиректами типа header:Location или document.location.
                      +1
                      я правильно понимаю что предлагается по урлу поиска ( например localhost/search? параметры) показывать найденную страницу?

                      на мой вкус это не хорошо хотя бы тем что нельзя дать нормальную ссылку на эту страницу,
                      всё таки если в результате поиска находится страница про Васю, то и отображатся она должна по соответствующему URL-у: например localhost/pages/Вася
                        0
                        Эк мы с тобой одновременно светлые мысли кидаем… :)))
                          –2
                          Если с точки публичного поиска — то да, Вы правы.
                          Но если поиск внутри, например, админ-панели, то помоему намного лучше избавляться от любых редиректов любыми силами.
                          Насчет того, что нельзя дать URL на страницу результатов — неправда, можно. Сомневаюсь что параметры в запрос поиска Вы будете передавать POSTом :)
                          0
                          Часто бывает, что за поиск данных и за их отображение отвечают разные модули системы. В этом слуае при поиске проще обработать количество найденного и переправить посредством стандартного заголовка HTTP.

                          Есть и более прозаическая причина — ЧПУ (ЧеловекоПонятныйУрл). У поиска и у конкретной страницы с данными разные урлы. Соответственно, если статья о Пупкине лежит по адресу site.com/people/pupkin/, то урл поиска — по адресу site.com/search/?subj=Пупкин. Выдавать одну и ту же страницу (про Васисуалия) по двум разным урлам — это не совсем правильно. Почему неправильно — есть разные причины, это тема отдельного большого топика.
                            0
                            Ответил выше.

                            Насчет разных модулей системы и того, что Вы не можете из одного модуля вызвать инициализацию другого — ну это явно же проблема движка (а точнее его отсутствия).

                            Хотя, даже если поиск доступен публике (поисковому боту). Я не уверен что он так просто в строку поиска введет «Вася пупкин» и получит вторую страницу идентичную ЧПУ Пупкину. И если Вы даже светите ссылку на site.com/search/?subj=Пупкин — что мешает Вам добавить во время поиска:

                            link rel=«canonical» href=«hvosting.ua/hosting.html»

                            или вообще закрыть к индексации /search/*
                              0
                              Про инициализацию — никто не говорит, что нельзя вызвать один из другого. Нормальная система сможет это сделать легко. Вопрос в нагромождении взаимных вызовов и, как следствие, усложнение логики, появление нетривиальных ошибок «на стыке» модулей.
                              Мой point в том, что поведение системы не должно быть сложным. Отработал модуль — выдал результат. В нашем случае результат — одна ссылка. И мы за пользователя просто делаем следующий шаг — «нажимаем» на ссылку и попадаем куда надо. Так вот это «нажатие» не должно происходить посреди алгоритмов и вызовов. Чем ближе «к поверхности», тем проще.

                              Насчет УРЛов — xnj tcnm URI (URL) — Unified Resource Identifier (Locator). Т.е. это унифицированное ID ресурса. Есть ресурс — страница о В. Пупкине — у него должен быть унифицированный (и, по возможности, уникальный) идентификатор. А так получится большое множество — по числу вариантов нахождения странице о Васе.
                              Хотя по этому пункту не буду настаивать — это полу-религиозный вопрос :)
                          0
                          Я использую тоглько 2й вариант (1й — муть, а 3й требует яваскрипта который нафиг не вперся ради такой мелочи). Только передавать удобее не цифры и ид, например: Location: /login.php?msg=need_login
                            0
                            Второй вариант проигрывает 3-му тем что пользователи начинают друг-другу давать ссылки на страницы с открытым уведомлением, хотя сторонним людям оно нафиг не упало ( пример: moikrug.ru/companies/571544408/?one=1 ). Но эта проблема не касается страниц на которые ссылок не дают ( ну например внутренние страницы какой-нибудь админки )

                            1-й вариант очень удобен как универсальное решение для всего сайта, могу объяснить почему если интересно
                              0
                              Насчет ссылок — да, тоже верно (( Тогда предлагаю дополнительно ставить короткоживущую куку к ссылке (просто кука — не совместима с открытием страницы в нескольких окнах параллельно).

                              А заводить ради этого сессию считаю неразумным все же.

                              > 1-й вариант очень удобен как универсальное решение для всего сайта, могу объяснить почему если интересно

                              Конечно интересно, попробуйте :)
                            +1
                            Мне кажется не совсем правильным перенаправлять пользователя на страницу Васи, даже если он всего один такой. Почему? Потому что это нарушает ожидания пользователя — он возможно просто хотел проверить файт наличия Васи (и число этих Вась), а не получать целую статью с картинками про него — зачем его заваливать бесполезной информацией. То есть мне кажется, это весьма спорное решение и подходит не везде и не всем…
                              0
                              Да, конечно, это довольно редкий случай когда имеет смысл так делать.
                              Собственно я и выбрал этот пример для статьи о нотификациях, потому что в нём без нотификатора ну никак не обойтись.
                              Спасибо.
                                0
                                В этом и суть уведомлений в данном примере — показать, что это уже не страница поиска, что система решила упростить жизнь пользователю, но он может вернуться.
                                Если вы не делаете перенаправлений, то и уведомления вам не нужны.
                                  +1
                                  Мне кажется не совсем правильным перенаправлять пользователя на страницу Васи, даже если он всего один такой

                                  А я бы сказал что редко когда это неправильно.

                                  К примеру, на «каталогах» меня напрягает то, что я *точно* знаю что мне надо, я ищу это, а получаю страницу результатов. Например, ищем «после прочтения сжечь». В данном случае релевантность первой записи более чем гигантская, всё остальное очевидно не подходит даже для сравнения.
                                  0
                                  Могу предложить ещё такое извращение — использовать 2-й способ, но при загрузке делать редирект на страницу без мусорного параметра, а на ней смотреть REFERER =)
                                  Устранены все проблемы, кроме F5.

                                  Я использую сессии.
                                    0
                                    у существенного процента пользователей REFERRER отшиблен файерволом(или типа того).
                                    вот такая вот паранойя :(

                                    ну и такой метод требует refresh страницы, а hash убирается без перезагрузки.
                                    +1
                                    «пользователи начнут давать друг другу ссылки с »?show_notice=1" — это уже не очень приятно"

                                    надо HTTP_REFERER && show_notice проверять
                                    тогда эта проблема исчезает
                                      0
                                      REFERER очень много у кого не работает из-за файерволов (и прочих InternetSecurity) и корпоративных проксей
                                        0
                                        и как же проверять родителя линка?
                                          +1
                                          для статистики хватит тех у кого он не отрублен.
                                          в логике сайта полагаться на REFERER нельзя.
                                      +2
                                      я бы выбрал вариант с cookie.

                                      сессия для каждого пользователя — слишком дорогое удовольствие для высоконагруженных проектов.

                                      $_GET не очень удобен в смысле шаринга ссылок и рефреша страницы

                                      javascript здесь не кажется идеологически верным вариантом (всё же он отвечает за поведение элементов на странице, а не их состав).
                                        –1
                                        храненение информации не в урле мешает кэшированию страниц. а это куда важнее сессионого хранилища для нагруженного проекта
                                        +1
                                        Я почему-то думал, что в статье будет приведен такой пример:
                                        function doRedirect($location) {
                                            header("X-Show-Notice: show");
                                            header("Location: $location");
                                        }
                                        
                                          0
                                          X-Show-Notice: show

                                          Будет выдано браузеру. Стало быть, обработку этого сообщения надо сделать силами, опять же, браузера. Можете привести работу JS-срипта для обработки этого заголовка?
                                            0
                                            что-то я сегодня не проснулся еще :)
                                            Заголовок X-Show-Notice будет радостно проигнорирован браузером, который получит 302-ой код от сервера и тут же пошлет следующий гет-запрос по указанному в Location адресу.
                                          0
                                          Можно хранить переменные в window.name.
                                          code.google.com/p/jquery-session/
                                            0
                                            Если кто выбрал Codeigniter своим фреймворком, то в нём есть удобный инструмент работы с такими сообщениями: Flashdata. Значение сохраняется только для следующей страницы и потом автоматически удаляется.
                                              0
                                              В подобной ситуации всегда пользовался модифицированным первым вариантом. Причём в $_SESSION['messages'] хранится массив вида: ('/news/253/'=>'Новость успешно добавлена') (не знаю как и зачем передавать просто флаг о сообщении, причём непоятно о каком). В нужном месте проверить текущий url на присутствие в messages, вывести и удалить url из массива в сессии. Всё просто. На сервере сразу будет видно, что результат поиска один и что будем редиректить сразу на /Вася_Пупкин/, следовательно запись такая: ('/Вася_Пупкин/'=>'Нашлась только.....').
                                              А в js втюхивать что-то — рука не подымится. А отключенный js? А i18n? Да и мороки больше. Можно, конечно, при желании генерировать нужный js-словарь сообщений при обращении, но это уже другая тема.
                                                0
                                                пример с флагом о просто сообщении приведён для простоты
                                                отключенный JS это тема отдельного холивара
                                                а js словарь всё равно придётся делать если есть js и i18n :)
                                                0
                                                мне кажется, если не самый легкий, то самый прямой (с наименьшим количеством костылей) вариант хранить нотификации в бд, да, данные будут пухнуть, но с этим можно что-то придумать Т.е. объекты системы могут нотифаить пользователю, а сам нотификатор забирает сообщения из базы и показывает, при этом выбранные к показу собщения можно либо сразу удалять из бд, либо ставить флажок «прочитано». С таким подходом мы обретаем и несколько дополнительных возможностей, например, нотификации, которые показываться до выполнения определенного условия (например «заполните номер телефона в профиле», «подтвердите введенный емейл»:) или в определенное время (можно повесить нотификацию которую пользователь должен будет увидеть через месяц).
                                                  +1
                                                  а для гостя как сообщения к пользователю привязывать? по id сессии? тогда это и есть просто сессия, которая хранится в БД :)
                                                    0
                                                    у автора очень простой пример, случаев когда полезно захардкодить сообщения прямо в шаблон/код можно придумать не много. Поэтому предложенное решение скорее костыль для такой вот конкретной ситуации. А когда вам надо передать сообщений 5-10 или чтото более контекстнозависимое изобразить, то я думаю такой вариант не подойдет
                                                      0
                                                      пример описывает частый случай нагруженной гостевой страницы на которой обязательно должно быть кэширование, которое плохо уживается с передачей сообщений через сессию (можно, но страшно костыльно).
                                                      Кстати не обязательно делать сообщения предвбитыми, на JS вполне можно сделать схему не менее гибкую чем на базе сессии в PHP.

                                                      5-10 сообщений могут вылезти только в каком-нибудь внутреннем залогиновом интерфейсе, тут безусловно удобнее делать реализацию через сессию например как в Codeigniter::Flashdata.

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

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