[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Защитите управляющую логику web-приложения

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Роман Гуай [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Методика построенная на Struts управляет повторной отправкой форм.

Tips 'N Tricks
PDF versionPDF версия
Обзор
Struts от Apache Jakarta Project реализует шаблон Синхронизирующий Маркер (Synchronizer Token) для предотвращения повторной отправки формы. Применяя данный шаблон, вы можете обнаруживать указанную ситуацию и следовать альтернативным потоком выполнения, когда она произошла. Но каковы альтернативные действия? В большинстве случаев, лучшим решением будет просто восстановление результатов первой отправки формы. Данная подсказка предлагает простую методику для достижения этой цели. (В оригинальной версии на английском языке 750 слов)

Проектировщики и разработчики Web приложений часто сталкиваются с ситуацией, когда отправка формы должна быть защищена от прерывания нормальной последовательности потока выполнения. Такая ситуация обычно происходит, когда пользователь нажимает более одного раза кнопку отправки формы до того, как будет послан ответ, или когда клиент попадает на страницу по сохранённой ранее ссылке. Особенно важно сохранять последовательность потока выполнения, когда отправка формы вызывает обработку транзакций на сервере.

Данная статья предлагает хорошо инкапсулированное решение для данной проблемы: методика реализуемая как абстрактный класс, усиливается каркасом web-приложений Struts.

Замечание
Вы можете скачать исходный код данной статьи из Ресурсов.

Клиентское решение против серверного

Возможны различные решения проблемы многократной отправки формы. Некоторые коммерческие сайты просто предупреждают пользователя об ожидании ответа после отправки формы и просят не отправлять форму дважды. Более совершенные решения используют клиентские сценарии или серверное программирование.

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

Для серверного решения может быть применён шаблон Синхронизирующий Маркер (из Core J2EE Patterns), который требует минимального участия с клиентской стороны. Основная идея заключается в установке маркера в сеансовую переменную перед выдачей клиенту транзакционной страницы. Эта страница содержит маркер в скрытом поле. При подаче запроса, обработчик сначала проверяет наличие корректного маркера в параметрах запроса, сравнивая его с зарегистрированным в сеансе. Если маркеры идентичны, обработка может быть продолжена, иначе, осуществляется переход к альтернативному потоку выполнения. После проверки маркер сбрасывается в null для предотвращения последующих подач запроса до тех пор, пока новый маркер не будет сохранён в сеансе, что должно быть сделанно в соответствующее время, основаное на желаемом потоке выполнения приложения. Другими словами, единовременная привилегия на подачу запроса даётся на один конкретный экземпляр представления. Описанный шаблон Синхронизирующий Маркер применяется в каркасе web-приложений Struts от Apache Jakarta Project - популярной реализации шаблона Модель-Представление-Контроллер (Model-View-Controller — MVC) с открытым кодом.

Синхронизированное действие

Основываясь на рассмотренном выше, решение может показаться полным. Но один фактор был пропущен: как мы будем определять/реализовывать альтернативный поток выполнения, когда будет обнаружен некорректный маркер. Фактически, в приведённом случае, когда кнопка подачи запроса нажата повторно, второй запрос вызовет потерю первого ответа, содержащего ожидаемый результат. Поток, который обрабатывает первый запрос, всё ещё запущен, но не имеет возможности вернуть ответ браузеру. Поэтому, у пользователя может создаться впечатление, что транзакция не завершена, хотя, на самом деле, она могла завершиться успешно.

Эта подсказка предлагает методику, основанную на каркасе web-приложений Struts, для обеспечения завершённого решения, которое предотвращает повторную подачу запроса и кроме того, гарантирует отображение ответа, представляющего результат первоначального запроса. Предлагаемая реализация содержит абстрактный класс SynchroAction, который могут расширять классы действий, что бы получить поведение в рассмотренной синхронизированной манере. Этот класс переопределяет метод Action.perform() и предоставляет абстрактный метод performSynchro() с аналогичными аргументами. Исходный метод perform() передаёт управление в соответствии со статусом синхронизации, как показанно в следующем листинге:

public final ActionForward perform(ActionMapping mapping,
         ActionForm form,
         HttpServletRequest request,
         HttpServletResponse response)
     throws IOException, ServletException {

     HttpSession session = request.getSession();
     ActionForward forward = null;

     if (isTokenValid(request)) {

         // Сбрасываем значения маркера и переменных сеанса
         reset(request);

         try {
             // Выполняем действие и сохраняем результат
             forward = performSynchro(mapping, form, request, response);
             session.setAttribute(FORM_KEY, form);
             session.setAttribute(FORWARD_KEY, forward);
             ActionErrors errors = (ActionErrors)
                         request.getAttribute(Action.ERROR_KEY);
             if (errors != null && !errors.empty()) {
                 saveToken(request);
             }
             session.setAttribute(ERRORS_KEY, errors);
             session.setAttribute(COMPLETE_KEY, "true");

         } catch (IOException e) {
             // Сохраняем и повторно выбрасываем исключение
             session.setAttribute(EXCEPTION_KEY, e);
             session.setAttribute(COMPLETE_KEY, "true");
             throw e;

         } catch (ServletException e) {
             // Сохраняем и повторно выбрасываем исключение
             session.setAttribute(EXCEPTION_KEY, e);
             session.setAttribute(COMPLETE_KEY, "true");
             throw e;
         }

    } else {

         // Если действие завершено
         if ("true".equals(session.getAttribute(COMPLETE_KEY))) {

             // Получаем исключение из сеанса
             Exception e = (Exception) session.getAttribute(EXCEPTION_KEY);

             // Если оно не пусто, то генерируем его
             if (e != null) {
                 if (e instanceof IOException) {
                     throw (IOException) e;
                 } else if (e instanceof ServletException) {
                     throw (ServletException) e;
                }
            }

            // Получаем форму из сеанса
             ActionForm f = (ActionForm) session.getAttribute(FORM_KEY);

            // Устанавливаем их в соответствующее окружение
             if ("request".equals(mapping.getScope())) {
                request.setAttribute(mapping.getAttribute(), f);
            } else {
                session.setAttribute(mapping.getAttribute(), f);
            }

            // Получаем и сохраняем ошибки из сеанса
             saveErrors(request, (ActionErrors)
                     session.getAttribute(ERRORS_KEY));

            // Получаем параметры пересылки из сеанса
             forward = (ActionForward) session.getAttribute(FORWARD_KEY);

        } else {

         // Выполняем соответсвующее действие
         // в случае некорректного маркера
         forward = performInvalidToken(
 		mapping, form, request, response);
         }
     }

     return forward;
}

Как было показано ранее, защищённое действие выполняется только однажды, когда маркер корректен. Если в процессе работы действия будут получены другие запросы, то они будут направлены на метод performInvalidToken(), пока не закончится выполнение действия. По умолчанию, этот метод просто возвращает ActionForward названный "synchroError". Это пересылка должна приводить на страницу, сообщающую, что действие находится в процессе выполнения и предоставляющую кнопку для продолжения обработки. Эта кнопка просто повторно подаёт запрос на тоже самое действие без каких-либо данных формы или параметров в запросе. Когда действие законченно, оно запоминает все свои пересылки, формы, исключения и ошибки, если они были, в сеансе и устанавливает флаг, сигнализирующий о своём завершении. Первый запрос, пришедший после завершения действия, получает пересылки, формы, исключения и ошибки из сеанса и продолжает работу так, как если бы это был первый запрос.

В исходные коды включён пример, демонстрирующий поведение простого синхронизированного действия. Предоставленная реализация основана на Struts 1.0.2, но может быть легко адаптирована для версии 1.1.

Сохранение последовательного потока управления

Множественная отправка формы может вызвать противоречивость в транзакциях и должна быть избегнута. Шаблон Синхронизирующий Маркер оказывает большую помощь в решении данной проблемы. Данная статья предлагает методику, хорошо дополняющую указанный шаблон в области восстановления ответа на первоначальный запрос и, по сути, помогающую сохранить последовательный поток управления в web-приложениях.

Об авторе

Роман Гуай (Romain Guay) проектировщик и программист в Systematix Consulting. Он имеет продолжительный опыт объектно-ориентированного проектирования, начиная со Smalltalk'а. Он участвовал в исследовательских проектах и различных разработках в промышленной и правительственной областях.

Ресурсы

Reprinted with permission from the March 2003 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javatips/jw-javatip136.html

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Олег Лапшин, 2003
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.