ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 상태정보 유지 기술
    Servlet 2019. 2. 25. 20:22










    상태정보 유지

    웹에서 사용하는 HTTP 프로토콜 방식은 클라이언트와 서버간의 연결을 클라이언트 요청이 있을때마다

    매번 새롭게 연결하는 방식

    이 처럼 클라이언트와 서버 간에 연결상태가 유지되지 않는 통신방식을 무상태(StateLess)라고 한다.

    무상태 통신 방식의 특징은 한번 접속한 후 연결이 유지 되지 않고 끊어지기 때문에 서비스를 요청한 클라이언트에 대한 정보가 유지되지 않는다. 

    그래서 클라이언트가 이전 요청에서 처리결과를 계속 다른 요청에서도 사용하고 싶다면 서버 측이든 클라이언트 측이든 어딘가에 저장해서 정보를 유지해야한다. 


    정보를 유지하지 않는 무상태 통신방식은 다수의 사용자를 상태로하는 인터넷에서 서버의 오버헤드를 줄일수 있는 장점이 있다.

    일련된 작업에서 이전 서비스 결과를 이용해야할 때가 있는데.

    이전 요청된 정보를 계속해서 사용하고 싶다면 반드시 클라이언트나 서버에 저장해서 사용해야한다.

    이처럼 클라이언트나 서버에 계속 요청에서 사용 할 수 있도록 저장한 정보들을 상태정보라고 한다.


    저장위치 분류


    클라이언트의 어떤 요청에서 처리된 결과를 계속 사용하려면 정보를 저장해서 유지해야한다.
    저장하는 위치에 때라 상태 정보 유지기술을을 분류하면 다음과 같이 나뉜다.

    클라이언트 측에 저장기술

    웹에서 클라이언트 웹 브라우저를 의미한다. 그래서 클라이언트 측에 저장한다는 것은 웹브라우저가 저장하는 것을 의미.
    웹브라우저가 종료전 까지 정보를 유지 할 수 있고 종료 이후에도 유지할수도 있다.

    클라이언트측에 정보를 유지하는 기술을 쿠키가 있다.

    javax.servlet.http.Cookie

    서버 측에 저장 기술

    서버 측에 저장한다는 것은 서버에 힙메모리 영역에 만들어진 객체에 상태를 저장하는것을 의미
    상태정보가 저장된 객체가 힙메모리 영역에 존재하는 한 등록된 상태정보는 계속해서 사용할 수 있다.

    서블릿에 서버측에 상태정보를 저장할 수있는 객체

    javax.servlet.ServletContext

    javax.servlet.http.HttpSession

    javax.servlet.http.HttpServletRequest



    유지 기간 분류


    웹 애플리케이션 단위 유지

    웹 애플리케이션 서비스되 있는 동안 유지하는 것을 의미.

    웹 애플리케이션과 같은 객체는 ServletContext이다.

    ServeltContext 객체는 웹 애플리케이션 서비스가 시작될 때 생성되고 종료될 때 소멸한다.

    그래서 ServletContext 객체에 상태정보를 정하면 웹 애플리케이션이 서비스 되고있는 동안에 사용이 가능하다.


    javax.servlet.ServletContext


    클라이언트 단위 유지

    클라이언트 단위로 유지한다는 것은 클라이언트 별로 구분해서 상태정보를 유지하는 의미이다.

    A 클라이언트가 사용하는 상태정보는 다른 클라이언트는 사용을 못해야한다

    대표적인것이 로그인

    쿠키는 크라이언트 특에 상태정보를 유지하는 기술이어서 당연히 클라이언트 단위 상태정보 유지되어 사용되며, 서버 측에서 클라이언트별로 생성되는 HttpSession 객체를 통해 클라이언트 단위로 상태정보를 유지할 수 있다.


    javax.servlet.http.Cookie

    javax.servle.http.HttpSession


    요청단위 유지

    요청단위로 유지한다는 것은 클라이언트 서비스 요청 단위로 유지한다는 것이다.


    클라이언트로부터 요청이 들어오고 응답이 나가기까지가 하나의 요청이다.

    하나의 요청에서만 상태정보를 유지할때는 HttpServletRequest 객체를 통해서 할 수 있다.


    javax.servlet.http.HttpServletRequest


    ServletContext


    웹 애플리케이션 단위로 정보를 서버 쪽 에서 유지할 수있는 방법은 ServletContext 객체를 사용하는 것이다. 

    웹 애플리케이션 단위로 서비스 하는 웹서버에서 서블릿 컨테이너는 웹 애플리케이션 단위로 Context를 생성하여 관리한다.

    이 Context가 ServletContext 객체 ServletContext가 웹 애플리케이션 단위로 정보를 유지하는 기능도 있지만 , 

    서블릿과 서블릿 컨테이너 간의 통신하는 기능도 제공합니다.


    ServletContext 의 생성


    ServletContext는 서블릿 컨테이너와 통신하기 위해서 사용되는 메소드를 지원하는 인터페이스이다. 

    다음 그림과 같이 서블릿 컨테이너가 시작될때 웹서버에 등록된 웹 애플리케이션 단위로 하나의 ServletContext 가 자동 생된다.

    그리고 웹 애플리케이션 서비스가 중지될때 소멸한다.

    ServletContext 객체는 WAS의 생명주기가 같다 ServletContext 객체를 간단하게 웹컨텍스트 또는 컨텍스트라고 한다.



    WAS에 등록된 웹 애플리케이션 단위로 컨텍스트가 생성되는 이유는 컨테이너가 웹 애플리케이션 단위로 모든 자원을 관리할 수있게하기 위해서이다.

    즉 웹 애플리케이션 내에있는 모든 서블릿 그리고 JSP간에 정볼르 공유할 수있고 , 서블릿 컨테이너에 대한 정보를 추출할 수있게 하는 기술이 바로 ServletContext이다.


    웹 애플리케이션 서비스가 시작될 때 생성된 ServletContext 객체의 추출 방법은 메소드를 이용한다.

    ServletConfig의 getServletContext()


    서블릿을 실행 할때 최초 요청이면 ServletConfig 객체가 생성되며 init()메소드의 인자값으로 전달됨

    ServletConfig 객체에서 현재 웹 애플리케이션에 할당된 Servletontext 객체의 레퍼런스 값을 추출할 수있는 getServletContext 메소드를 제공


    1)init() 메소드를 재정의 하여 추출하는 방법


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context")
    public class ServletContextTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        ServletContext sc;
        
        
        
        @Override
        public void init(ServletConfig config) throws ServletException {
            sc = config.getServletContext();
        }
     
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("context : "+sc);
            out.close();
        }
     
    }
     
    cs


    서블릿 컨테이너는 init() 메소드를 호출하기 전에 ServletConfig 객체를 생성하여
    init() 메소드의 인자값으로 전달해준다.
    ServletConfig의 객체를 config 변수로 받는다.


    2)HttpServlet을 통해 추출하는 방법

    ServletContext 객체의 주소값을 추출할 수 있는 두번 째 방법은 HttpServlet 객체를 이용하는 방법이다.

    HttpServlet 객체의 상위 클래스인 GenericServlet 에서 ServletConfig를 상속받아 메소드를 재정의 하고 있기 때문에
    HttpServletContext() 메소드를 사용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context")
    public class ServletContextTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        
        
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            ServletContext sc= this.getServletContext();
            out.println("context : "+sc);
            out.close();
        }
     
    }
     
    cs


    ServletContext 변수 추출

    web.xml에 <context-param>으로 설정한 변수를 추출할때는 ServletContext 객체에서 제공하는 

    getInitParameter () 메소드를 활용한다.

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
      <display-name>edu</display-name>
      <context-param>
          <param-name>contextConfig</param-name>
          <param-value>/WEB-INF/context.xml</param-value>
      </context-param>
    cs


    web.xml에 context-param 태그를 추가


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context")
    public class ServletContextTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        
        
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            ServletContext sc= this.getServletContext();
            String location= sc.getInitParameter("contextConfig");
            out.println("location : "+location);
            out.close();
        }
     
    }
     
    cs
    web.xml context-param에 설정한 변수를 추출할때 getInitParameter( ) 메소드를 활용한다.

    개발시 Context-Param의 실제 사용 용도 


    웹서버가 사용하는 환결설정 페이지는 web.xml이지만
    웹프로그램은 
    클라이언트가 보는 페이지 서비스를 요청받는 페이지 
    서비스를 직접 처리하는 페이지 
    데이터베이스를 처리하는 페이지
    등으로 구분해서 작업하는데 이때 각 페이지 수준으로 환경설정이 만들어짐

    웹 애플리케이션 서비스 시작가 동시에 생성되는 ServletContext  객체에 환경설정 파일에대한 정보를 
    변수로 전달하고 실제 환결설정을 하는 페이지에서는 ServletContext 객체를 동해 전달 받은 환경변수 파일로 찾아가
    설정을 작업한다.


    서버정보추출


    웹서버는 웹 애플리케이션 단위로 정보를 나누어 관리하며 서비스 한다. 웹서버에는 웹 애플리케이션 당 
    하나 씩 ServletContext 객체가 생성되 어있다 .
    웹 애플리케이션 단위로 만들어진 ServletContext 객체로 웹애플리케이션에 관한 정보를 추출할 수있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context2")
    public class ServletContextTestServlet2 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        
        
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            ServletContext sc= this.getServletContext();
            
            out.println("서블릿 버전 : "+sc.getMajorVersion() +","+sc.getMinorVersion());
            out.println("서버 정보 : "+sc.getServerInfo());
            out.println("웹 애플리케이션 경로 : "+ sc.getContextPath());
            out.println("웹 애플리케이션 이름 : "+sc.getServletContextName());
            out.println("파일 실제 경로 : "+sc.getRealPath("/name.html"));
            sc.log("로그 기록!");
            out.close();
        }
     
    }
     
    cs


    웹 애플리케이션 단위 정보 공유


    ServletContext 객체는 웹 애플리케이션 단위로 사용되는 객체


    즉 동일한 웹 애플리케이션 안에 있는 모든 페이지에서 동일한 ServletContext 객체를 사용한다.


    ServletContext 객체를 이용해서 웹 애플리케이션 단위 정보를 유지함으로 써 공유할 수있는것이다.


    여러 페이지에서 사용할 하나의 ServletContext 객체에 등록하면 같은 웹 애플리케이션에 소속된 다른 페이지에서 ServletContext객체에 접근하여 공유된 데이터를 추출후 사용가능하다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.edu.test;
     
    public class ShareObject {
        private int count;
        private String str;
        
        public int getCount() {
            return count;
        }
        public void setCount(int count) {
            this.count = count;
        }
        public String getStr() {
            return str;
        }
        public void setStr(String str) {
            this.str = str;
        }
        
        
        
        
    }
     
    cs

    공유할 객체


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context3")
    public class ServletContextTestServlet3 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        
        
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            ServletContext sc= this.getServletContext();
            
            ShareObject obj1 = new ShareObject();
            obj1.setCount(100);
            obj1.setStr("객체 공유 테스트  -1");
            sc.setAttribute("data1", obj1);
            
            
            ShareObject obj2 = new ShareObject();
            obj2.setCount(200);
            obj2.setStr("객체 공유 테스트  -2");
            sc.setAttribute("data2", obj2);
            
            out.println("ServletContext객체에 데이터를 등록하였습니다.");
     
            
            
            out.close();
        }
     
    }
     
    cs

    객체를 ServletContext에 등록 

    위 소스가 실행되 후 의 메모리 구조는 다음과 같다.


    이어서 ServletContextTest4Servlet이라는 이름으로 등록된 객체의 데이터를 추출하는 서블릿 작성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/context4")
    public class ServletContextTestServlet4 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        
        
     
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            ServletContext sc= this.getServletContext();
            ShareObject obj1 =(ShareObject) sc.getAttribute("data1");
            ShareObject obj2 =(ShareObject) sc.getAttribute("data2");
            out.println("data1 : count "+obj1.getCount()+"str : "+obj1.getStr());
            out.println("data2 : count "+obj2.getCount()+"str : "+obj2.getStr());
            
            
            out.close();
        }
     
    }
     
    cs



    쿠키


    웹 서비스 중에 클라이언트 단위로 상태정보를 유지해야 하는 상황은 다음과 같다.

    - 이전에 방문한 적이 있는 웹서버에 다시 방문 했을 때 몇번째 방문이지를 출력하는 상황

    - 회원 가입화면에서 자동으로 주소, 전화번호등이 입력되는 상황

    - 로그인하고 난 후 로그 아웃할때 까지 인증 상태 유지

    - 쇼핑몰에서 주문 할때 까지 장바구니에 선택한 상품정보 유지

    - 쇼핑몰에서 클라이언트가 체크했던 상품 정보 유지

    - 포털사이트에서 클라이언트가 특별히 관심보이는 항목 정보 유지


    클라이언트 단위로 상태 정보를 유지하려문 쿠키와 세션을 사용한다.
    쿠키와 세션은 다음과 같은 기준에 따라 선택하여 사용한다.


    - 상태정보의 유지기간이 브라우저 종료될때 까지 인가의 여부
    - 유지하려는 정보의 저장 위치 (서버, 클라이언트)
    - 유지하려는 정보가 공개되어도 되는지의 여부

    쿠키와 세션의 공통점은 클라이언트 단위로 상태정보를 유지하는것이며 
    차이점은 다음과 같다.

     구분

    쿠키 

    세션 

     저장위치

    클라이언트 

    서버 

     저장 데이터 타입 

    텍스트 

    객체 

     저장 데이터 크기 

    제한 있음 

    서버에서 수용할 수 있는 만큼 



    쿠키속성


    클라이언트에 저장된 쿠키정보를 이용하여 연결이 끊겨도 개별적으로 상태유지가 가능하다.

    저장된 쿠키정보는 다시 서버를 방문할때 자동으로 요청정보의 헤더 안에 포함되어 전달된다.
    name과 value로 구성된 정보로서 
    사용 목적에 따라 적절한 
    쿠키유지시간
    유효디렉터리
    유효도메인
    등의 속성을 함께 지정할 수 있다.


    장점 : 간단히 구현가능

    단점 : 브라우저 저장 용량의 한계, 보안상문제 


    쿠키 설정에 대한 내용

    - 쿠키는 설정할때 name= value 형식으로 구성되어 있다.
    (쿠키 정보외에도 expire, path, domain, 그리고 secure등 여러 속성을 선택적으로 추가해서 지정해야된다.)

    - 쿠키의 name은 아스키코드 문자문 사용해야하며 한번 설정된 쿠키는 수정할 수없다.
    - "expire= 날짜"속성은 쿠키의 유지시간이다 , 우지시간이 없는 쿠키는 브라우저가 실행하는 동안만 유효하다.

    - "path=경로" 속성은 클라이언트에 저장된 쿠키가 전달되는 서버의 유효 디렉터리를 지정하는 속성이다.
      "path=/"는 서버의 모든 디렉터리로 해석된다.  생략하면 쿠키를 설정하는 파일이 존재하는 디렉터리가 유효 디렉터리가 된다.

    - "domain=서버정보" 속성은 클라이언트에 저장된 쿠키가 전달되는 유효 서버를 지정하는 속성이다. 생략되면 쿠키를 설정하는 서버가     유효 서버가 된다.

    - secure  속성이 설정되면 클라이언트에서 HTTPS나 SSL과 같은 보안 프로토콜로 요청할때만 서버에 전송된다.

    쿠키생성


    쿠키생성 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @WebServlet("/cookie1")
    public class CookieTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            Cookie c1 = new Cookie("id""guest");
            c1.setPath("/");
            response.addCookie(c1);
            
            
            Cookie c2 = new Cookie("code""001");
            c2.setMaxAge(60*60*3);
            c2.setPath("/");
            response.addCookie(c2);
            
            Cookie c3 = new Cookie("subject""java");
            c3.setMaxAge(60*60*24*10);
            c3.setPath("/");
            response.addCookie(c3);
            
            out.println("쿠키 전송 완료");
            out.close();
            
            
        }
     
    }
     
    cs


    쿠키 추출


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @WebServlet("/cookie2")
    public class CookieTestServlet2 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            Cookie[] list = request.getCookies();
            for (Cookie s  : list) {
                out.println(s.getName() +":"+s.getValue()+"<br>");
            }
            
            out.close();
            
            
        }
     
    }
     
    cs

    클라이언트 단위 정보 공유

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @WebServlet("/cookie3")
    public class CookieTestServlet3 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
            
            int    cnt =0;
            
            Cookie[] list = request.getCookies();
            for (Cookie s  : list) {
                if(s.getName().equals("count")) {
                    cnt= Integer.parseInt(s.getValue());
                }
            }
            cnt++;
            Cookie c = new Cookie("count", cnt+"");
            c.setMaxAge(60*60*24*10);
            response.addCookie(c);
            
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            out.println("<h1>방문 횟수 : "+ cnt +"</h1>");
            
            out.close();
            
            
        }
     
    }
     
    cs


    세션(Session)


    HTTP 기반으로 동작하는 클라이언트가 서버에 정보를 요청할 때 생성되는 상태정보를 세션이라고한다.


    세션은 HttpSession이라는 인터페이스 객체로 표현되며
    HttpSession 객체는 HttpServletRequest( )이나 getSession(true) 메소드를 이용하여 생성 할 수 있다.

    HttpSession 객체가 생성될때는 요청을 보내온 클라이언트 정보, 요청시간 정보 등을 조합한 세션 ID가 부여되며, 
    이 세션 ID는 클라이언트 측에 쿠키기술로 저장된다.

    즉 다음 그림처럼 HttpSession 객체는 서버에 생성되며 클라이언트에는 세션 ID가 쿠키 기술로 저장되어 각 클라이언트에 대하여 생성되는 
    HttpSession 객체를 클라이언트마다 개별적으로 유지 및 관리한다.




    클라이언트 마다 개별적으로 생성되어 유지되는 HttpSession 객체는 요청을 보내온 클라이언트와 서버간에 일정시간(최대 시간은 브라우저가 살아있는 시간) 동안 각클라이언트의 상태 정보를 서버에 저장하여 유지하고 하는 목적으로 사용되는 객체이다.

    장바구니 , 로그인 로그아웃, 

    클라이언트마다 상태정보를 일정 시간 동안 개별적으로 유지하여 사용하는 기술을 세션 트래킹 이라고 한다.


    세션트래킹의 기능 구현 순서


    1.클라이언트를 위한 세션을 준비한다. 이전에 이 클라이언트를 위해 생성된 새션이 이미 존재하면 존재하는 세션을 추출하고
    그렇지 않으면 새로 생성한다. 새션이 새로 생성될때는 고유한 ID가 하나 부여 되며 이 ID는 클라이언트에 쿠키기술로 저장된다.

    2. 유지하고자 하는 정보르 ㄹ저장할 목적의 객체를 생성하여 새션에 등록한다.

    3. 클라이언트가 요청을 전달할 때 마다 세션에 등록된 정보를 담고 있는 객체를 추출하여 원하는 기능에 사용한다.

    5. 세션이 더이상 필요없는 시점에 세션을 삭제 한다.


    HttpSession생성

    개발자가 수동으로 생성하는게아니라 메소드를 이용하거나 HttpSession 객체의 레퍼런스값을 추출하여 사용한다.
    HttpServletRequest 의 getSession을 이용한다.

    클라이언트가 가지고 있는 세션 ID와 동일한 세션객체를 찾아서 레퍼런스값을 반환, 만일 세션이 존재하지 않으면 HttpSession 객체를 생성하여 반환합니다.

    HttpSession 객체에 관한 메소드 테스트
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
     
    @WebServlet("/sessionTest")
    public class SessionTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
     
            String param = request.getParameter("p");
            String msg = null;
            HttpSession session = null;
     
            if (param.equals("create")) {
                session = request.getSession();
                if (session.isNew()) {
                    msg = "새로운 세션 객체가 생성됨";
     
                } else {
                    msg = "기존의 세션 객체가 리턴됨";
                }
            } else if (param.equals("delete")) {
                session = request.getSession(false);
                if (session != null) {
                    session.invalidate();
                    msg = "세션 객체 삭제 작업 완료";
     
                } else {
                    msg = "삭제할 세션객체 존재하지 않음";
                }
            } else if (param.equals("add")) {
                session = request.getSession(true);
                session.setAttribute("msg""메세지 입니다");
                msg = "세션객체에 데이터 등록 완료";
     
            } else if (param.equals("get")) {
                session = request.getSession(false);
                if (session != null) {
                    String str = (String) session.getAttribute("msg");
                    msg = str;
                } else {
                    msg = "데이터를 추출한 세션 객체 존재하지 않음";
     
                }
     
            }else if(param.equals("remove")) {
                session= request.getSession(false);
                if(session != null) {
                    session.removeAttribute("msg");
                    msg = "세션 객체의 데이터 삭제 완료";
                }else {
                    msg="데이터를 삭제할 세션 객체존재하지 않음";
                }
                
            }else if (param.equals("replace")){
                session = request.getSession();
                session.setAttribute("msg","새로운 메시지 입니다.");
                msg="세션 객체에 데이터 등록 완료";
                
            }
            out.print("처리 결과 : "+msg);
            out.close();
     
        }
     
    }
     
    cs


    session.isNew()


    없어서 새로운 새션을 생성하면 true 기존 세션이 유지되고있으면 false를 반환

    session = request.getSession(false);

    세션 객체를 추출하는데 없다면 null을 반환  새로 생성하지않음

    인자값이 true이면 새로 생성함.


    클라이언트 단위 정보 공유

    세션 트레킹을 이용한 간단한 로그인 로그아웃 

    1. 클라이언트로부터 ID와 비밀번호를 입력 받음

    2. 입력받은 ID와 비밀번호를 추출한다.

    3. ID와 비밀번호를 입력하지 않으면 되돌려보낸다.

    4. DB에 저장되 있는 정보와 일치확인

    5. 로그인처리를 하기전에 현재 로그인 상태를 확인 만일 로그인 상태라면 더이상 로그인 할 필요가 없으므로 오류메세지 출력하고 작업을 멈춘다.

    6.로그인  처리를 한다. 로그인 처리란 HttpSession 객체에 특정한 정보를 setAttribute 메소드를 이용하여 등록

    로그아웃 과정

    1. 현재 로그인 상태를 판단한다. 만일 로그인 상태가 아니라면 로그아웃 작업을 할 필요가 없다. 로그인 상태이면 다음 단계 진행을 계속한다.

    2. 로그아웃처리를 한다 HttpSession 객체를 삭제 또는 HttpSession 객체에 로그인 처리하면서 등록된 데이터를 삭제하는것을 의미
    삭제에는 invaildate() 메소드를 사용하고, 세션에 등록된 데이터를 삭제할 때는 removeAttribute() 메소드를 확인한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <form action="logProc" method="post">
            ID : <input  type="text" name="id"><br>
            비밀번호 : <input  type="password" name="pwd"><br>
            <input type="submit" value="로그인">
            
        </form>
        <a href="logProc">로그 아웃</a>
    </body>
    </html>
    cs




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
     
    @WebServlet("/loginProc")
    public class LoginOutServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
        
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            String id = request.getParameter("id");
            String pwd = request.getParameter("pwd");
            
            if(id.isEmpty() || pwd.isEmpty()) {
                out.println("아이디 또는 비밀번호를 입력하세요");
                return;
            }
            HttpSession session = request.getSession();
            if(session.isNew() || session.getAttribute("id")==null) {
                session.setAttribute("id",id);
                out.println("로그인을 완료하였습니다.");
            }else {
                out.print("현재 로그인 상태입니다.");
            }
            
            
        }
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            HttpSession session = request.getSession(false);
            if(session!=null && session.getAttribute("id")!=null) {
                session.invalidate();
                out.println("로그 아웃 작업 완료");
                
            }else {
                out.print("로그인 상태가 아닙니다.");
            }
            out.close();
            
        }
     
    }
     
    cs



    HttpServletRequest


    HttpServletRequest 객체는 클라이언트 실행 요청이 있을때마다 service()메소드가 실행되기 전에 자동으로 생성되고
    service 메소드가 종료되면 자동으로 소멸한다. 
    HttpServletRequest에 여러 페이지에서 공유할 정보를 저장 한다 하더라도 service()메소드가 종료되는 시점에 객체도
    소멸하는데 그곳에 저장하는 의미가 있을까?

    한번의 클라이언트 요청에 여러페이지들이 실행 되었을때 

    HttpServeltRequest객체가 이동되는 페이지에서도 그대로 전달하여 사용할 수있다.



    클라이언트로부터 요청에 대하여 서버에 존재하는 다른 자원으로 요청을 재지정 하는 것을 요청 재지정이라고한다.

    자원이랑 HTML , 이미지, 서블릿 , 그리고 JSP등 웹 애플리케이션을 구성하는 어떤 파일이든 대상이 될 수 있다.

    클라이언트에서느 서버에서 보낸 요청을 다른 자원으로 재지정하는 것을 알수 없다

    클라이언트 모르게 재지정하기 때문이다.


    재지정을 기능을 제공하는 객체은 다음 2가지가 있다.

    HttpServletResponse


    RequestDispatcher


    HttpServletResponse 요청 재지정


    HttpServletResponse 객체에서 제공하는 요청 재지정 메소드는 다음과 같다.


     접근자 & 반환형

    메소드 

    기능 

     public void

    sendRedirect(String Location) 

    location에 설정된 자원으로 요청을 재지정한다. 

    public String 

    encodeRedirectURL(String url) 

    url에 설정된 URL 문자열에 세션 ID를 추가하여 요청을 재지정한다. 


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <form action="potalSite">
            <input type="radio" name="site" value="naver">네이버<br>
            <input type="radio" name="site" value="daum">다음<br>
            <input type="radio" name="site" value="zum">줌<br>
            <input type="radio" name="site" value="google">구글<br>
            <input type="submit" value="이동">
        </form>
    </body>
    </html>
    cs



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.edu.test;
     
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @WebServlet("/potalSite")
    public class SendRedirectServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String param = request.getParameter("site");
            
            if(param.equals("naver")) {
                response.sendRedirect("http://www.naver.com");
            }else if(param.equals("daum")) {
                response.sendRedirect("http://www.daum.com"); 
            }else if(param.equals("zum")) {
                response.sendRedirect("http://zum.com");
            }else if(param.equals("google")) {
                response.sendRedirect("http://www.google.com");
            }
        }
     
    }
     
    cs



    RequestDispatcher  요청 재지정


    RequestDispatcher 객체에서 제공하는 메소드를 사용하여 요청을 재지정할대는 요청을 재지정 하는 자원이 반드시 현재 자원과 동일한 웹 애플리케이션이 있어야만 한다.

     접근자 & 반환형

    메소드 

    기능 

     public void

    forword(ServletRequest request,ServletResponse response) 

     요청을 다른 자원으로 넘긴다.

     public void

    include(ServletRequest request, ServletResponse response) 

     다른 자원의 처리 결과를 현재 페이지에 포함한다.  


    (1) RequestDispatcher 객체 생성

    인터페이스인 RequestDispathcer 객체를 생성 할때는 다음과 같이 팩토리 메소드를 사용한다.


    ServletContext에서 제공하는 메소드

    : RequestDispatcher getNamedDispatcher(String name)

    : RequestDispatcher getRequestDispatcher(String path)

    ServletRequest 객체에서 제공하는 메소드

    : RequestDispatcher getRequestDispatcher(String path)

    요청을 재지정할 대상에 대한 정보를 path 형식 named 등을

    어떤것으로 지정하는가만 다를 뿐 대상을 지정하면서 RequestDispatcher 객체를 추출하는 기능은 같다.


    차이는 get 할때  ServletRequest 객체는 상대경로 절대경로 둘다가능

    ServletContext는 절대경로가만 가능하다.


    (2)forword( ) 메소드 : forword(ServletRequest request , ServletResponse response)


    RequestDispatcher 객체의 forward( ) 메소드는 클라이언트 요청으로 생성되는 HttpServlet Request와 HttpServletResponse객체를 다른자원에 전달하고 수행제어를 완전히 넘겨서 다른자원의 수행결과를 클라이언트로 응답하도록하는 기능의 메소드이다.

    다음 그림과 같이 클라이언트로부터 요청을 다른자원에 넘겨서 다른 자원으로 요청을 재지정한다.


    포워드 예제



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.security.auth.message.callback.PrivateKeyCallback.Request;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/dispatcher1")
    public class ServletTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<h3>Dispatcher Test1의 수행결과</h3>");
            
            ServletContext sc = this.getServletContext();
            
            RequestDispatcher rd = sc.getRequestDispatcher("/dispatcher2");
            
            rd.forward(request, response);
        }
     
     
     
    }
     
    cs

    포워딩 되는 서블릿

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.security.auth.message.callback.PrivateKeyCallback.Request;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet("/dispatcher2")
    public class ServletTestServlet2 extends HttpServlet {
        private static final long serialVersionUID = 1L;
     
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<h3>Dispatcher Test2의 수행결과</h3>");
            
            out.close();
        }
     
     
     
    }
     
    cs

    웹브라우저에서 /dispatcher1을 요청하면

    응답받은 페이지는 forward()로 이동한 페이지 /dispatcher2임을 확인 할 수 있다.


    (3) include() 메소드 : include(ServletRequest request , ServletResponse response)

    include()는 클라이언트의 요청으로 생성되는 HttpServletRequest와 HttpServletResponse 객체를 다른자원에 전달하고

    수행한 다음 그결과를 클라이언트에서 요청한 서블릿 내에 포함하여 클라이언트로 응답하는 기능의 메소드 입니다.


    다음은 RequestDispatcher 객체의 include() 메소드를 활요하는 예제

    이전 소스에서 rd.forward를 rd.include로만 변경함


    Dispatcher Test1의 수행결과

    Dispatcher Test2의 수행결과



    rd.include(req,resp) 메소드를 만나면 지정된 path에 지정된 페이지로 이동
    forward() 메소드는 이동한 다음 클라이언트에 응답하짐나
    include 메소드는 실행이 끝나면 이전 페이지로 다시돌아와 계속해서 실행 된다음 클라이언트측에 응답을 보냄

    즉 include() 메소드는 다른 페이지를 현재 페이지에 글자 그대로 포함하는 효과를 얻는다.


    (4)Request 단위 정보 공유

    HttpServletRequest를 통해 동일한 요청 페이지 간에 정보를 공유하는 예제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <form action="bookReg" method="post">
            책 제목 : <input type="text" name="title"><br>
            책 저자 : <input type="text" name="author"><br>
            출판사 : <input type="text" name="publisher"><br>
            <input type="submit" value="등록">
        </form>
    </body>
    </html>
    cs


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.edu.test;
     
    public class Book {
        private String title;
        private String author;
        private String publisher;
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
        public String getAuthor() {
            return author;
        }
        public void setAuthor(String author) {
            this.author = author;
        }
        public String getPublisher() {
            return publisher;
        }
        public void setPublisher(String publisher) {
            this.publisher = publisher;
        }
        
        
    }
     
    cs


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.security.auth.message.callback.PrivateKeyCallback.Request;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
     
    @WebServlet("/bookReg")
    public class BookTestServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         response.setContentType("text/html;charset=UTF-8");
         PrintWriter out = response.getWriter();
         
         request.setCharacterEncoding("UTF-8");
         
         String title = request.getParameter("title");
         String author = request.getParameter("author");
         String publisher = request.getParameter("publisher");
         
         Book book = new Book();
         book.setTitle(title);
         book.setAuthor(author);
         book.setPublisher(publisher);
         
         request.setAttribute("book", book);
         
         RequestDispatcher rd = request.getRequestDispatcher("bookOuput");
        
        }
     
    }
     
    cs



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.edu.test;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    import javax.security.auth.message.callback.PrivateKeyCallback.Request;
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
     
    @WebServlet("/bookOuput")
    public class BookTestServlet2 extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
     
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         System.out.println("북아웃풋");
            response.setContentType("text/html;charset=UTF-8");
         PrintWriter out = response.getWriter();
         
         Book book = (Book) request.getAttribute("book");
         
         out.print("<h3>책 제목 : "+ book.getTitle()+"</h3>");
         out.print("<h3>책 저자 : "+ book.getAuthor()+"</h3>");
         out.print("<h3>출판사 : "+ book.getPublisher()+"</h3>");
         
         out.close();
        }
     
    }
     
    cs




    'Servlet' 카테고리의 다른 글

    JSP프로그래밍 정리  (0) 2019.03.03
    JSP프로그래밍  (0) 2019.03.02
    질의 문자열(Query String)  (0) 2019.02.21
    서블릿 디렉터리를 WEB-INF/classes 로지정하는 이유  (0) 2019.02.21
    요청정보와 응답정보  (0) 2019.02.20
Designed by Tistory.