حفاظت از جريان كنترلى برنامه كاربردى وب

 

نويسنده: Romain Guay
مترجم: شهناز پيروزفر

 

چكيده: Struts، برگرفته از Apache Jakarta Project، الگوى Synchronizer Token را به منظور پيشگيرى از تكرار ارسال فرم، پياده سازى مىكند. شما مىتوانيد اين وضعيت را با بكارگيرى الگوى مذكور تشخيص دهيد و هنگام بروز اين حالت، عملكرد صحيحى را انجام دهيد. اما چه عملكردى؟ در اكثر موارد، بهترين راه كار ترميم (recover) اولين نتيجه ارسال است.

اغلب طراحان و برنامه نويسان برنامه كاربردى وب، با وضعيت‌هايى مواجه مىشوند كه بايد ارسال فرم را در برابر گسيختن توالى عادى جريان كنترلى محافظت نمايند. اين وضعيت در زمانيكه كاربر بيش از يكبار بر روى دگمه Submit پيش از آنكه پاسخ ارسال شود، كليك مىكند يا وقتى كه كلاينت با بازگشت به صفحه bookmark قبلى، به View دسترسى پيدا مىكند، روى مىدهد. حفظ توالى جريان كنترلى (Control Flow) در هنگامى كه ارسال فرم سبب پردازش ارتباطات بر روى سرور مىشود، حائز اهميت است.

اين مقاله راه كار مشكل مذكور را ارائه مىنمايد: استراتژيى كه به صورت كلاس انتزاعى با چهارچوب Strust پياده سازى مىشود.

 

 

راه كارهاى كلاينت در برابر راه كارهاى سرور

راه كارهاى مختلفى براى رفع وضعيت ارسال چندگانه فرم وجود دارد. بعضى از سايتها به كاربر هشدار مىدهند كه پس از ارسال فرم، منتظر دريافت پاسخ باشد و از ارسال مجدد فرم بپرهيزد. راه كارهاى پيچيده‌تر شامل اسكريپت نويسى كلاينت يا برنامه نويسى سرور هستند.

در استراتژى كلاينت براى اولين ارسال، flag ست مىشود و دگمه Submit با توجه به اين flag غيرفعال خواهد شد. اين استراتژى كم وبيش به مرور گر وابسته است.

در راه كار مبتنى بر سرور، الگوى Synchronizer Token (از الگوهاى اصلى J2EE) به كار مىرود كه كمترين مشاركت را با client-side دارد. در اين روش فرض بر اين است كه token در متغيير Session، پيش از بازگشت به كلاينت، ست شود. اين صفحه token را درون فيلد مخفى نگاه مىدارد. در هنگام ارسال، ابتدا پردازش درخواست، حضور token مجاز را در پارامتر درخواست، بررسى مىكند. بدين ترتيب كه آن را با يكى از tokenهاى ثبت شده در Session مقايسه مىكند. اگر token مجاز بود، پردازش روند عادى خود را طى مىكند. در غيراينصورت token به null ، Reset مىشود تا از ارسال مجدد فرم پيشگيرى شود. Token جديدى در Session ذخيره گردد. ذخيره token جديد در زمان مناسب و با توجه به جريان كنترلى برنامه كاربردى مورد نظر صورت مىگيرد. به عبارتى اجازه ارسال داده‌ها در زمان خاصى به نمونه‌اى از view داده مىشود. اين الگوى Synchronizer Token، در چهارچوب Struts (Apache Jakarta project) مورد استفاده قرار مىگيرد. اين چهارچوب پياده سازى رايج Model-View-Controller مىباشد.

 

 

عمليات همگام

راه كار با توجه به موارد فوق تكميل شده است. اما موردى را فراموش نموديم. چگونه عمليات مورد نظر را در هنگام تشخيص token غيرمجاز، مشخص/ پياده سازى نماييم. در واقع در حالتى كه دوباره دگمه Submit كليك شود، درخواست دوم سبب مىشود تا اولين پاسخ كه حاوى نتيجه مورد انتظار بوده از بين برود. thread كه اولين درخواست را اجرا مىكند، باز هم اجرا مىشود اما نمىتواند پاسخى را براى مرورگر داشته باشد. لذا، شايد كاربر تصور كند كه ارتباط به طور كامل صورت نگرفته، در حاليكه ارتباط با موفقيت كامل شده است.

استراتژى مبتنى بر چهارچوب Struts، راه كار كاملى فراهم مىآورد كه از ارسال مجدد پيشگيرى خواهد شد. پياده سازى پيشنهادى، از كلاس انتزاعى SynchroAction  بهره مىگيرد كه در آن عمليات در حالت همگام قابل گسترش هستند. اين كلاس متد PerformSynchro() انتزاعى را فراهم مىآورد. كنترل با توجه به وضعيت همگام سازى به صورت ذيل خواهد بود:

 

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 token and session attributes

        reset(request);

 

        try {

            // Perform the action and store the results

            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) {

            // Store and rethrow the exception

            session.setAttribute(EXCEPTION_KEY, e);

            session.setAttribute(COMPLETE_KEY, "true");

            throw e;

 

        } catch (ServletException e) {

            // Store and rethrow the exception

            session.setAttribute(EXCEPTION_KEY, e);

            session.setAttribute(COMPLETE_KEY, "true");

            throw e;

        }

 

    } else {

 

        // If the action is complete

        if ("true".equals(session.getAttribute(COMPLETE_KEY))) {

 

            // Obtain the exception from the session

            Exception e = (Exception) session.getAttribute(EXCEPTION_KEY);

 

            // If it is not null, throw it

            if (e != null) {

                if (e instanceof IOException) {

                    throw (IOException) e;

                } else if (e instanceof ServletException) {

                    throw (ServletException) e;

                }

            }

 

            // Obtain the form from the session

            ActionForm f = (ActionForm) session.getAttribute(FORM_KEY);

 

            // Set it in the appropriate context

            if ("request".equals(mapping.getScope())) {

                request.setAttribute(mapping.getAttribute(), f);

            } else {

                session.setAttribute(mapping.getAttribute(), f);

            }

 

            // Obtain and save the errors from the session

            saveErrors(request, (ActionErrors)

                    session.getAttribute(ERRORS_KEY));

 

            // Obtain the forward from the session

            forward = (ActionForward) session.getAttribute(FORWARD_KEY);

 

        } else {

 

            // Perform the appropriate action in case of token error

            forward = performInvalidToken(mapping, form, request, response);

        }

    }

   return forward;

}

 

(چنانچه سوس فوق در مرورگر شما بطور درست نمايش داده نمي شود، بر روي اين لينك كليك كنيد)

 

همانگونه كه در فوق ملاحظه نموديد، اين عمليات در صورتيكه token مجاز باشد، فقط يكبار انجام مىشود. اگر در هنگام اجراى عمليات درخواستهاى ديگر دريافت گردد، به نتيجه متد PerformInvalidToken() هدايت مىشوند تا عمليات كامل گردد. به طور پيش فرض اين متد به سادگى ActionForward با نام “Synchro Error” باز مىگرداند. بدين ترتيب به صفحه اطلاع مىدهد كه عمليات در حال انجام است و دگمه‌اى را براى ادامه عمليات فراهم مىآورد. اين دگمه همان عمليات را بدون فرم يا پارامتر، دوباره ارسال مىكند. وقتى عمليات كامل شد، فرم، استثنا و خطا‌ها در صورت وجود، در Session ذخيره و flag را ست مىكند تا مشخص شود كه عمليات كامل شده است.

اولين درخواست فرم، استثنا و خطاها  را پس از تكميل عمليات از Session دريافت مىكند و روند ادامه مىيابد.

 

 

حفظ سازگارى كنترل جريان

ارسال چند فرم ممكن است سبب بروز ناسازگارى در ارتباطات شود و بايد از اينكار ممانعت بعمل آورد. بدين منظور از الگوى Synchronizer Token استفاده مىشود. استراتژى پيشنهادى در اين مقاله به زيبايى اين الگو را براى ترميم  پاسخ و حفظ جريان كنترل در برنامه‌هاى كاربردى وب به كار برده است.

 

 

منابع

·         Download this article's source including an example use of the proposed strategy:
http://www.javaworld.com/javaworld/javatips/javatip136/jw-javatip136.zip

·         Learn more about Core J2EE Patterns, best practices, and design strategies:
http://java.sun.com/blueprints/corej2eepatterns/index.html

·         The Struts homepage:
http://jakarta.apache.org/struts/index.html

·         For a client-only strategy to prevent duplicate form submission:
http://www.netmechanic.com/news/vol5/html_no16.htm

·         For more articles on design patterns, see the following JavaWorld resources:

o        The Design Patterns section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-patterns-index.shtml

o        David Geary's Java Design Patterns column:
http://www.javaworld.com/columns/jw-java-design-patterns-index.shtml