جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


در این بخش به چگونگی استفاده از HandlerInterceptor در Spring MVC میپردازیم، اما قبل از آن بیاییم نگاهی به HanlderMapping کنیم 

HandlerMapping کارش Map کردن (ایجاد تناظر) بین URL و متد رسیدگی به درخواست است که DispatcherServlet از آن برای پردازش درخواست ها استفاده میکند.

DispatcherServlet با استفاده از HandlerAdapter متد مورد نظر را پیدا میکند و درخواست به متد مورد نظر میرسد اینجا جایی است که HandlerInterceptor وارد کار میشود که میتوانیم یکسری عملیات را قبل از پردازش درخواست انجام دهیم که دایما در حال تکرار هستند و اینطوری کد تکراری کمتری خواهیم داشت مانند Log گرفتن، یا عوض کردن پارامتر های Model 



برای کار با HandlerInterceptor نیاز است که کتابخانه مورد نیازش را به pom پروژه اضافه کنیم :

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>


حال ما نیاز داریم که اینترفیس HandlerInterceptor را که دارای سه متد اصلی است پیاده سازی کنیم


prehandle : این متد قبل از اینکه متد رسیدگی به درخواست اجرا شود، اجرا میشود، در این مرحله هنوز View تولید نشده است 

postHandle : این متد بعد از اجرای متد رسیدگی به درخواست اجرا میشود ولی هنوز View تولید نشده است 

afterCompletion : این متد بعد از اجرای کامل متد رسیدگی به درخواست و تولید View، اجرا میشود 


ما میتوانیم بجای استفاده از اینترفیس HandlerInterceptor از کلاس اداپتر HandlerInterceptorAdapter هم استفاده کنیم و فرقش این است که موقعی که از کلاس Adapter استفاده میکنیم تنها متدی که مورد نیاز است را Override میکنیم

@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
 
   // your code
    return true;
}

مقدار برگشتی این متد از نوع boolean است و اگر false بود به مرحله بعد نخواهد رفت

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    // your code
}

کاربرد postHandler یکمی خاص تر است چون متد رسیدگی اجرا شده است ولی هنوز View شکل نگرفته در اینجا میتوان کارهایی نظیر اضافه کردن Avatar به کلاس Model کاربر لاگین کرده کرد

@Override
public void afterCompletion(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, Exception ex) {
    // your code
}


کاربرد afterCompletion در مواقعی است که مثلا میخواهیم آمار بازدید را ثبت کنیم و...



نکته ای که وجود دارد این است که HandlerInterceptor در یک Bean از نوع DefaultAnnotationHandlerMapping ثبت میشود که مسئول تایید کردن لایه بازرسی برای کلاس های Controller@ است 



مثالی از کاربرد Logger :

public class LoggerInterceptor extends HandlerInterceptorAdapter {
    ...
}

ابتدا یک کلاس مسئول لاگ گرفتن را ایجاد میکنیم که قرار است با Log4j کار کند و باید آنرا در Log4j به عنوان کلاس مسئول لاگ ثبت کنیم :

private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);
@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
     
    log.info("[preHandle][" + request + "]" + "[" + request.getMethod()
      + "]" + request.getRequestURI() + getParameters(request));
     
    return true;
}

در اینجا کا باید مطمئن شویم که پسورد کاربران را لاگ نمیگیریم یکی از راهکار های ساده عوض کردن کاراکتر های پسورد با کاراکتر ستاره است که در متد زیر انجام میدهیم :

private String getParameters(HttpServletRequest request) {
    StringBuffer posted = new StringBuffer();
    Enumeration<?> e = request.getParameterNames();
    if (e != null) {
        posted.append("?");
    }
    while (e.hasMoreElements()) {
        if (posted.length() > 1) {
            posted.append("&");
        }
        String curr = (String) e.nextElement();
        posted.append(curr + "=");
        if (curr.contains("password") 
          || curr.contains("pass")
          || curr.contains("pwd")) {
            posted.append("*****");
        } else {
            posted.append(request.getParameter(curr));
        }
    }
    String ip = request.getHeader("X-FORWARDED-FOR");
    String ipAddr = (ip == null) ? getRemoteAddr(request) : ip;
    if (ipAddr!=null && !ipAddr.equals("")) {
        posted.append("&_psip=" + ipAddr); 
    }
    return posted.toString();
}

در نهایت ممکن است ما بخواهیم IP کاربر را هم لاگ بگیریم که برای این منظور میتوانیم از کد زیر استفاده کنیم :

private String getRemoteAddr(HttpServletRequest request) {
    String ipFromHeader = request.getHeader("X-FORWARDED-FOR");
    if (ipFromHeader != null && ipFromHeader.length() > 0) {
        log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader);
        return ipFromHeader;
    }
    return request.getRemoteAddr();
}


میتوانیم بعد از اجرا متد رسیدگی به درخواست که یکسری دیتای دیگر در دسترس ما قرار میگیرد لاگ دیگری را بگیریم که در متد postHandle قابل انجام است :

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
     
    log.info("[postHandle][" + request + "]");
}


بعد از اجرای کامل و تولید شدن View ممکن است خطایی هم رخ داده باشد که نیاز به لاگ گرفتن داشته باشد 

@Override
public void afterCompletion(
  HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) 
  throws Exception {
    if (ex != null){
        ex.printStackTrace();
    }
    log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
}




ثبت کلاس Interceptor در تنظیمات :

در کلاس WebConfig کافی است متد addInterceptors را override کنیم 

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoggerInterceptor());
}

تنظیمات به روش XML :

<mvc:interceptors>
    <bean id="loggerInterceptor" class="org.baeldung.web.interceptor.LoggerInterceptor"/>
</mvc:interceptors>





مثال عملی :

بکار بردن HandlerInterceptor به عنوان Session Timeout Logic :


در ابتدا باید چک کنیم که کتابخانه های مورد نیاز را به pom اضافه کرده باشیم که در ابتدای مطلب ذکر شد و علاوه بر آن ما نیاز به Spring-Core و Spring-Context نیز خواهیم داشت 


مرحله بعد هدف را مشخص میکنیم :

هدف از Session Timeout Logic بکار بردن سیاست غیر فعال و نامعتبر کردن session کاربری که مدت زیادی فعالیتی نداشته است

گاهی کاربران یادشان میرود که Logout کنند و یا منطق برنامه نیازی به Logout کردن نداشته ولی کاربر مدت زیادی است که فعالیتی نمیکند و session معتبری که برای احراز هویت ساخته شده بدلایل امنیتی نباید همچنان در سیستم فعال بماند در این صورت session ای که ساخته باید بصورت اتوماتیک با حداکثر زمان غیر فعال بودن کاربر چک شود و در صورت زیاد بودن حذف شود


یک متغیر سراسری بصورت final و static برای تعیین کردن حداکثر زمان تعریف میکنیم :

private static final long MAX_INACTIVE_SESSION_TIME = 5 * 10000;

بعد در Handler ای که ساختیم نیاز به HttpSession خواهیم داشت :

@Autowired
private HttpSession session;


در متد preHandle :


در این متد ما باید سه کار را انجام دهیم :


- قرار دادن زمان جاری برای درخواست هایی که دریافت میشوند 

- چک کردن Login بودن کاربر

- Logout کردن کاربری که از تایم تعیین شده مقداری بیشتری غیر فعال بوده


ابتدا یک متد برای تعیین کردن اینکه کاربر Logout کرده است یا نه ایجاد میکنیم :

public static boolean isUserLogged() {
    try {
        return !SecurityContextHolder.getContext().getAuthentication()
          .getName().equals("anonymousUser");
    } catch (Exception e) {
        return false;
    }
}

بعد preHandle را در کلاس Handler ای که ایجاد کردیم Override میکنیم :

@Override
public boolean preHandle(
  HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    log.info("Pre handle method - check handling start time");
    long startTime = System.currentTimeMillis();
    request.setAttribute("executionTime", startTime);

    if (isUserLogged()) {
       session = request.getSession();
       log.info("Time since last request in this session: {} ms",
          System.currentTimeMillis() - request.getSession().getLastAccessedTime());
        if (System.currentTimeMillis() - session.getLastAccessedTime()
          > MAX_INACTIVE_SESSION_TIME) {
            log.warn("Logging out, due to inactive session");
            SecurityContextHolder.clearContext();
            request.logout();
            response.sendRedirect("/spring-rest-full/logout");
        }
   }
  return true;
}





در متد PostHandle :


در این متد زمان اجرای درخواست ارسالی کاربر را مورد بررسی قرار میدهیم و میتوانیم حساب کنیم که کل پروسه اجرای درخواست چقدر بوده است در preHandle ما executionTime را به عنوان یک Attribute به ابجکت HttpServletRequest داده بودیم حال آنرا دریافت میکنیم و زمان جاری را از آن کم میکنیم تا مدت زمان اجرا بدست آید :

@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView model) throws Exception {
    log.info("Post handle method - check execution time of handling");
    long startTime = (Long) request.getAttribute("executionTime");
    log.info("Execution time for handling the request was: {} ms",
      System.currentTimeMillis() - startTime);
}

حال اگر استراتژی خاصی بخواهیم داشته باشیم روی زمان حساب شده میتوان اعمال کرد مثل لاگ گرفتن یا موارد امنیتی و خرابکارانه




چطور باید این Interceptor را ست کنیم ؟ 

حال در کلاس WebConfig که از کلاس WebMvcConfigurer ارث بری کرده بود متد addInterceptor را override کرده و Interceptor ساخته شده را به آن اضافه میکنیم :

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new SessionTimerInterceptor());
}

اگر با روش XML راحت تر هستید میتوانید تنظیمات بالا را بصورت xml اعمال کنید :

<mvc:interceptors>
    <bean id="sessionTimerInterceptor" class="org.baeldung.web.interceptor.SessionTimerInterceptor"/>
</mvc:interceptors>


همچنین باید به عنوان یک Listener به Application Context / Servlet Context آنرا اضافه کنیم :

public class ListenerConfig implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext sc) throws ServletException {
        sc.addListener(new RequestContextListener());
    }
}









نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی