JSP2011. 4. 20. 11:50

JSP 프로그래밍에서 페이지 이동 방법이 여러가지가 있습니다.
비지니스 로직 수행에서 redirect나 forward 아니면 해당 페이지에서 직접 redirect, forward, html, javascript 를 이용한 페이지 이동 등등..

그중에서 request.getRequestDispatcher("/view.jsp").forward(request, response) 를 사용하였을때 중복 저장 또는 중복 로직 수행에 대해서 알아 보려고 합니다.

forward를 사용하는 이유는 아래처럼 정리 할 수 있지만.. forward의 특징 중 하나가 이전페이지의 객체를 보존하고 있습니다.
'특정 페이지에서 전달된 데이터를 로직을 수행하고 수행된 결과(Object Data)를 원하는 페이지에 전달 후 보여주려고 한다.'

예로
              '사용자가 입력 페이지의 폼에서 저장버튼 클릭 -> 저장 로직 수행 후 DB 입력 -> 리스트 페이지 이동'
을 하였을 경우 이동한 리스트 페이지에서 F5키 나 새로고침을 하였을 경우 위의 로직을 다시 타는 경우가 발생합니다.
만약 DB의 PK 설정이 oracle의 sequence나 mssql의 시드를 이용한 자동 증가가 아닐 경우 PK 값 위반에 대한 Exception 이 일어 날 것이며 PK 가 없다면 같은 데이터가 중복 저장되는 경우가 생깁니다.

그래서 double submit을 막아 보려고 구글링 등등 이것저것 찾아 보았는데
Spring의 경우 SessionStatus, Struts의 Token, Post - Redirect- Get 패턴 등등 ;;

이미 프로젝트가 막바지라 급하게 손쓸 방법을 찾다가 쉽게 쓸수 있을 것 같아 적용해 보았는데 성공하여 포스트 합니다;;
http://blog.naver.com/PostView.nhn?blogId=canya83&logNo=40094442229&parentCategoryNo=15&viewDate=&currentPage=1&listtype=0

## 원리 ::
1. 최초 페이지(Token 없음) : jsp에서 Token을 생성 ( T1 생성)
2. 서버의 컨트롤러에서 JSP에서 생성된 Token이 컨트롤러의 Token과 같을 경우 신규 요청으로 설정
     (T1 == T2 으면 Token 생성)
3. 비지니스 로직 처리 후 특정 페이지로 이동하여 새로고침 ( T1 != T2)
4. 서버의 컨트롤러에서 이전 JSP에서 생성된 Token이 컨트롤러의 Token과 다르므로 Token 생성 안함
     (T1 != T2 )  해당 요청에 대해서는 비지니스 로직을 수행 안함.

## 코드
1. 공통 Controller에서 사용할 Token.java 를 하나 만들자!

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class Token {
 
 private static final String TOKEN_KEY = "TOKEN_KEY";
 
 public static void set(HttpServletRequest request) {
  HttpSession session = request.getSession(true);
  long systemTime = System.currentTimeMillis();
  byte[] time = new Long(systemTime).toString().getBytes();
  byte[] id = session.getId().getBytes();
  
  try {
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(id);
   md5.update(time);
   
   String token = toHex(md5.digest());
   request.setAttribute(TOKEN_KEY, token);
   session.setAttribute(TOKEN_KEY, token);
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
  }
 }
 
 public static boolean isValid(HttpServletRequest request) {
  HttpSession session = request.getSession(true);
  String requestToken = request.getParameter(TOKEN_KEY);
  String sessionToken = (String)session.getAttribute(TOKEN_KEY);
  if(requestToken == null || sessionToken == null) {
   return false;
  } else {
   return requestToken.equals(sessionToken);
  }
 }
 
 private static String toHex(byte[] digest) {
  StringBuffer buf = new StringBuffer();
  for(int i=0;i<digest.length;i++) {
   buf.append(Integer.toHexString((int)digest[i] & 0x00ff));
  }
  return buf.toString();
 }
}


2. HttpServlet 을 재구현한 공통 컨트롤러의 forward 부분에 아래와 같이 추가 한다.

if(Token.isValid(request)) {
    Token.set(request);
    request.setAttribute("TOKEN_SAVE_CHECK", "TRUE");
} else {
     request.setAttribute("TOKEN_SAVE_CHECK", "FALSE");
}


3. 중복 저장을 막고자하는 JSP 페이지 아래와 같이 추가 한다.

       <%
        if(request.getAttribute("TOKEN_KEY")==null) Token.set(request);
       %>
       <input type="hidden" name="TOKEN_KEY" value="<%=request.getAttribute("TOKEN_KEY")%>"/>


4. business logic을 수행하는 부분에서 CRUD 등등 처리 부분의 처음 부분에 아래와 같이 추가한다.

if("TRUE".equals(request.getAttribute("TOKEN_SAVE_CHECK"))) {
   // 신규 요청이므로 원하는 로직 작성
} else {
  // 중복 요청이므로 그에 따른 로직 작성
}


5. 테스트!

'JSP' 카테고리의 다른 글

사용자 정의 태그  (0) 2010.04.09
필터와 리스너  (0) 2010.04.09
Posted by Tiwaz
JSP2010. 4. 9. 10:31

# 사용자 정의 태그(Custom tag = User Defined Tag)
-JSP에서 사용될 태그를 사용자가 직접 정의해서 사용
-JSTL은 사용자 정의 태그의 일부
-Tag인터페이스를 기반으로 하는JSP1.2 버전의 커스텀 태그의 SimpleTag인터페이스를 기반으로 하는JSP2.0버젼의 커스텀태그로 나눔.
-커스텀태그를 사용하면 태그의 재사용성이 증가하고, JSP개발이 간편해지며, 코드의 가독성을 향상 시켜주는 이익이 있다.
-계층도
       (i)JspTag
        |
  (i)Tag       (i)SimpleTag-(c)SimpleTagSupport
  (i)IterationTag-(c)TagSupport 
  (i)BodyTag-(c)BodyTagSupport

-커스텀 태그의 구성
 1) 태그클래스 : 태그가 할일(태그를 만나면 수행해야할 작업을 정의)
 2) TLD(Tag Library Descriptor) : XML로 만들어진 태그라이브러리(태그들의 모임)에 대한 서술자
 3) web.xml에 TLD에 대한 정의

- javax.servlet.jsp.tagtext.Tag인터페이스의 메소드
 1) void setPageCountext(PageContext pc)
 커스텀 태그가 포함된 JSP페이지의 pageContext를 설정
 2) int doStartTag() : 시작 태그를 만나면 해야할 작업을 정의
 3) int doEndTag() : 끝태그를 만나면 해야할 작업을 정의
 4) void release() : 태그클래스의 객체를 메모리에서 해제
 5) void setParent(Tag t) : 부모태그를 설정
 6) Tag getParent() : 부모태그를 획득
 
- javax.servlet.jsp.tagext.IterationTag 인터페이스의 메소드
 1)int doAfterBody() : 태그의 몸체내용을 처리한 후에 호출
 예) <pre:tagName> ==> doStartTag
   Body ...
  
   ----> doAfterBody
  </pre:tagName> ==> doEndTag
 
- javax.servlet.jsp.tagBodyContent클래스의 메소드
 1) String getString() : BodyContent의 내용을 문자열로 반환
 2) JspWriter getEnclosingWirter() : BodyContent의 내용을 출력스트림으로 반환
 3) void writeOut(Writer out) : BodyContent의 내용을 out 출력스트림으로 출력
 4) Reader getReader() : BodyContent의 내용을 읽어올 수 있는 Reader 반환
 5) void clearBody() : 몸체 내용을 삭제

 

 *Tag 파일
- JSP형식으로 태그핸들러 클래스
- 태그파일에서 사용할 수 있는 디렉티브
 1)tag : 태그파일의 설정 정보
 2)taglib : 태그파일내에서 사용할 태그라이브러리 설정 정보
 3)include : 태그파일에 다른 파일을 추가할 때 사용
 4)attribute : 태그파일이 입력받을 속성 설정
 5)variable: EL변수로 사용될 변수의 정보
-tag 디렉티브의 속성
 1)display-name : 표시 이름, tag를 표시할 수 있는 서버에서 표시하는 이름
 2)body-content : 이 태그가 바디를 포함할지
 3)dynamic-attributes : 동적 속성을 사용할 지
 4)description : 태그에 대한 설명
 5)import : 이 태그파일에서 사용할 클래스
 6)pageEncoding : 이 태그파일의 인코딩 방식
 7)isELIgnored : EL 무시 할지
-tag 파일에서 사용가능한 내장 객체
 1) jspContext : jsp파일에 대한 정보, getAttribute, setAttribute
 2) request, response, session, application, out
--attribute 디렉티브의 속성
 1)description : 설명
 2)name : 속성명
 3)required : 속성이 필수속성인지
 4)rtexprvalue : 표현식(EL)사용가능 여부
 5)type : 속성값의 타입
 6)fragment : 속성값 전달시 사용

-variable 디렉티브의 속성
 1)description : 설정
 2)name-given : EL변수의 이름을 속성의 값으로 사용
 3)name-from-attribute : 속성의 값을 EL변수의 이름으로 사용
 4)alias : name-from-attribute가 있을때만 사용. Tag파일에서  커스텀 태그의 몸체에서 사용될 변수와 값을 가지는 EL변수의 이름.
 5)variable-class : 변수의 타입
 6)scope : 변수의 유효범위
  -AT_BEGIN : 시작 태그부터 사용가능
  -NESTED : 태그내에서 사용가능
  -AT_END : 끝태그 후 부터 사용가능
 * 2) 3)은 둘중 하나만 사용해야함. 중복 사용 불가
 

 

 

 

   

Posted by Tiwaz
JSP2010. 4. 9. 10:31

# Filter
- Request를 필터링하는 역할(보안, 인증, 인코딩, XSL/T, 로깅 ... )
- javax.servlet.Filter인터페이스를 구현한 클래스를 필터클래스로 사용
- Filter인터페이스의 메소드
 1) void init(FilterConfig filterConfig)
  Filter의 설정정보를 가진 FilterConfig를 인자로 받아서 Fillter초기화시 한번만 호출
 2)void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  Filter의 작업을 정의,FilterChain을 통해 적용될 Filter들의 정보를 가져옴
 3)void destroy()
  Filter가 메모리에서 해제되기 전에 ㅣFilter가 사용한 자원을 해제 
 
# Listener
- 어플리케이션에서 일어나는 여러가지 이벤트를 자동 감지해서
  이벤트에 따른 처리를 가능하도록 함
- 리스너의 종류
 1) javax.servlet.ServletContextListener
  - void contextDestroyed(ServletContextEvent sce)
           컨텍스트가 메모리에서 제거될때(웹어플 종료 직전 호출)
   - void contextInitialized(ServletContextEvent sce)
           컨텍스트가 메모리에 로딩될때 (웹어플 시작시)
 2) javax.servlet.ServletContextAttributeListner
  - void attributeAdded(ServletContextAttributeEvent scab)
         컨텍스트 유효범위에 속성변수가 추가될때(application.setAttribute("a", new Date())) 
   - void attributeRemoved(ServletContextAttributeEvent scab)
    컨텍스트 유효범위에 속성변수가 제거될때(application.removetAttribute("a", new Date()))
   - void attributeReplaced(ServletContextAttributeEvent scab)
    컨텍스트 유효범위에 속성변수가 대체될때
    application.setAttribute("a", new Object())
    application.setAttribute("a", new Date())
 3) javax.servlet.http.HttpSessionListner
  - void sessionCreated(HttpSessionEvent se)
   세션이 생성될때         
   - void sessionDestroyed(HttpSessionEvent se)
            세션이 종료될때
 4) javax.servlet.http.HttpSessionAttributeListener
  - void attributeAdded(HttpSessionBindingEvent se)
          세션 유효범위에 속성변수가 추가될때
          session.setAttribute("userid", "홍길동);
   - void attributeRemoved(HttpSessionBindingEvent se)
         세션 유효범위에 속성변수가 제거될때
          session.removeAttribute("userid");
   - void attributeReplaced(HttpSessionBindingEvent se)
            세션 유효범위에 속성변수가 대체될때
          session.setAttribute("홍길동");
          session.setAttribute("강감찬");
 5) javax.servlet.http.HttpSessionActivationLIstener 
  세션이 활성화 될때
  -void sessionDidActivate(HttpSessionEvent se)
   세션이 활성화된 후          
   -void sessionWillPassivate(HttpSessionEvent se)
             세션이 비활성화되기 전
 6) javax.servlet.http.HttpSessionBindingLIstener
  세션이 클라이언트에 바인딩 될때 
  - void valueBound(HttpSessionBindingEvent event)
          세션이 클라이언트에 바인딩될 때
   - void valueUnbound(HttpSessionBindingEvent event)
          세션이 클라이언트에 바인딩 해제 될 때
        
  
 
 
 
 
 
 
 
 
 
 
 
Posted by Tiwaz