[JSP&Servlet] 서블릿의 상태 저장 및 유지 객체들 : Session

Re_Go·2024년 8월 1일
0

JSP&Servlet

목록 보기
11/19
post-thumbnail

1. 인터넷에서의 세션의 역할

서블릿에서 세션(Session)은 클라이언트와 서버 간의 상태 정보를 유지하기 위한 중요한 개념이라고 하는데요.

그 이유는 웹 애플리케이션에서 클라이언트와 서버 간의 상호작용은 보통 HTTP 프로토콜을 통해 이루어지는데, HTTP는 기본적으로 상태가 없는(stateless) 프로토콜입니다. 이는 각 요청이 독립적이며 이전 요청과의 연결이 없다는 것을 의미합니다. 이를 두고 무상태성, 비상태성이라고도 하는데요.

프로토콜의 무상태성을 잘 설명한 예시

(자료 출처 : https://velog.io/@beneficial/Session%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80)

세션은 이러한 상태 비유지의 문제를 해결하기 위해 사용되는데요. 서버는 클라이언트가 처음 요청을 보낼 때 새로운 세션을 생성하고, 이를 식별하기 위한 고유한 세션 ID를 부여합니다. 이 세션 ID는 클라이언트에게 쿠키를 통해 전달됩니다. 이후 클라이언트는 서버에 요청을 보낼 때마다 이 쿠키에 세션 ID를 포함하여 보냅니다.

그럼 서버는 해당 쿠키에서 세션 ID를 대조해 세션 별로 할당 된 메모리 적재 공간에 사용자의 정보를 저장하여 정제 및 활용한 뒤 사용자에게 응답을 하게 되는 방식으로 활용되는 것이죠.

쿠키와 세션의 연결성을 잘 보여준 예시

(자료 출처 : https://velog.io/@hyun6ik/%EC%84%B8%EC%85%98-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D)

2. 서블릿에서의 세션과 메서드들

물론 서블릿에서도 쿠키를 사용 가능하기에 당연히 세션도 사용 가능한데요. 이러한 세션은 HttpSession 객체에 의해 관리되며, HttpServletRequest 인스턴스를 이용해 세션의 정보를 획득할 수 있게 됩니다.

HttpSession session = request.getSession();

그럼 해당 세션 객체가 제공하는 대표적인 메서드들을 한 번 볼까요?

getSession()

현재 요청과 연관된 세션을 반환합니다. 세션이 없다면 세션을 새로 생성하는데요. 이때 매개변수로 true, false를 넣게 되면 그 의미는 좀 달라지는데,

true를 전달하게 되면 기존 세션이 있다면 기존 세션을 반환하고, 없으면 새로운 세션을 생성하는 반면

false를 전달하게 되면 기존 세션이 있다면 기존 세션을 반환하고, 없으면 null을 반환하게 됩니다.

// 세션을 확인해 null(없는 상태) 일 경우 새로운 세션을 생성
HttpSession session = request.getSession(false);
if (session == null) {
    session = setSession("newSession", "SessionValue")
    session.setAttribute(name, value);
}

isNew()

세션이 새로 생성된 것인지 기존에 존재하던 것인지 확인합니다.

String checked = session.isNew() ? "새로 생성" : "기존의 것"

getCreationTime(), getLastAccessedTime()

세션이 처음 생성된 시간과 마지막으로 엑세스된 시간을 반환합니다. 이때 반환되는 시간은 타임스탬프 형식이므로 적당한 포맷 작업이 필요합니다.

// 각각의 세션 시간들을 Date 객체로 포맷
long creationTime = new Date(session.getCreationTime());
long lastAccessedTime = new Date(session.getLastAccessedTime());

invalidate()

현재 세션을 무효화하여 모든 세션 데이터를 삭제합니다.

session.invalidate();

setAttribute(String name, Object value)

세션에 이름-값 쌍으로 속성을 저장합니다. 이때 세션의 속성 중 이름은 반드시 문자열이여야 하나, 값의 경우 다양한 데이터 타입을 허용합니다.

또한 이미 있는 속성의 이름을 지정하여 값을 변경한 상태로 넣을 경우, 해당 속성의 값만 바뀐채 재정의 됩니다.

// session에 해당 속성의 이름과 값을 지정하여 생성
session.setAttribute("attributeName", 500);
// 이미 있는 속성에 값을 변경하여 재정의
session.setAttribute("attributeName", 1500);

getAttribute(String name)

세션에 저장된 특정 속성의 값을 가져옵니다.

// attributeName 속성의 값을 가져옴
session.getAttribute("attributeName");

getAttributeNames()

세션에 저장된 모든 속성의 이름을 Enumeration 객체로 반환합니다. 이때 Enumeration은 열거형 타입이므로 hasMoreElements 메서드와 연계하여 사용할 수 있으며, 한 번 사용된 경우 해당 타입의 변수로 재사용이 불가능합니다.

// 해당 메서드를 이용해 이름과 속성의 이름과 값을 얻어옴
Enumeration<String> attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
    String name = attributeNames.nextElement();
    Object value = session.getAttribute(name);
    // 속성 이름과 값을 사용
}

removeAttribute(String name)

세션에 저장된 특정 속성을 제거합니다.

// attributeName 속성을 session에서 제거
session.removeAttribute("attributeName");

3. 실습 예제 - 세션을 이용한 모의 CRUD

위에서 소개해드린 세션 메서드들을 이용해 세션을 이용해서 CRUD 프로그램을 한 번 만들어 봤는데요. 다소 짧은 시간에 만든 예제라 엉성하긴 하지만, 그래도 세션을 이렇게도 활영할 수 있다는 것을 보여드리고자 간단하게 설명을 드려보도록 하겠습니다.

각각의 폼들은 세션 생성, 삭제, 속성 생성, 삭제, 속성 조회 다섯가지 폼으로 구성했으며, 각 영역 당 input의 hidden 속성을 이용해 고유의 선택값들을 넣어줬습니다. 이 값들을 서블릿의 switch문의 분기로 활용할 거고요.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Request Form</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        label { display: block; margin-bottom: 10px; }
        input, button { margin-top: 5px; }
    </style>
</head>
<body>
    <h1>세션 명령을 입력해 주세요</h1>
    <h3>1.세션 생성 2.세션 삭제</h3>

    <!-- 세션 명령 폼 -->
    <form action="SessionServer" method="get">
        <label for="choice">명령어 선택:</label>
        <input type="text" id="choice" name="choice" placeholder="명령어 입력">
        <input type="submit" value="전송">
    </form>

    <br />

    <!-- 속성 추가 폼 -->
    <h3>속성 추가</h3>
    <form action="SessionServer" method="get">
        <input type="hidden" name="choice" value="3">
        <label for="itemName">속성 이름:</label>
        <input type="text" id="itemName" name="name" placeholder="속성 이름">
        <label for="itemValue">속성 값:</label>
        <input type="text" id="itemValue" name="value" placeholder="속성 값">
        <input type="submit" value="속성 추가">
    </form>

    <br />

    <!-- 속성 제거 폼 -->
    <h3>속성 제거</h3>
    <form action="SessionServer" method="get">
        <input type="hidden" name="choice" value="4">
        <label for="removeName">제거할 속성 이름:</label>
        <input type="text" id="removeName" name="name" placeholder="속성 이름">
        <input type="submit" value="속성 제거">
    </form>

    <br />

    <!-- 속성 조회 폼 -->
    <h3>속성 조회</h3>
    <form action="SessionServer" method="get">
        <input type="hidden" name="choice" value="5">
        <label for="getName">조회할 속성 이름:</label>
        <input type="text" id="getName" name="name" placeholder="속성 이름">
        <input type="submit" value="속성 조회">
    </form>
</body>
</html>

다음은 인코딩 설정과 choice 파라미터를 가져오고, 세션 객체 하나와 데이터 포맷 양식 제공을 위한 simpleDateFormat 타입의 인스턴스를 하나 생성해 줍니다.

res.setContentType("text/html;charset=UTF-8");
req.setCharacterEncoding("UTF-8");
PrintWriter out = res.getWriter();
int choice = Integer.parseInt(req.getParameter("choice"));
HttpSession session = null;
SimpleDateFormat formatTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

switch문의 첫번째 구간입니다. getsession과 isNew 조합을 이용해 있을 경우 해당 세션을 생성 (값이 비었을 경우 true가 default 값이므로)하고, 기존 세션 반환된다면 아래 문구를 출력후 각각의 생성 시간과 마지막 섹션 엑세스 시간을 출력합니다.

case 1:
    // 세션을 생성하거나 기존 세션을 가져옵니다.
    session = req.getSession();
    if (session.isNew()) {
        out.print("세션이 새로 생성되었습니다.");
    } else {
        out.print("기존 세션이 존재합니다.");
    }
    out.print("<p>생성 시간 : " + formatTime.format(new Date(session.getCreationTime())) + "</p>");
    out.print("<p>마지막 접속 시간 : " + formatTime.format(new Date(session.getLastAccessedTime())) + "</p>");
    break;

두번째 구간입니다. 기존의 세션을 가져오되 false 값을 전달해 만약 null을 반환 받지 않을 경우, 즉 기존의 세션을 가져올 경우 해당 세션의 생성 시간과 마지막 접속 시간을 보여준 뒤 삭제하고, null, 즉 이미 없는 상태라면 섹션이 없다고 출력합니다.

case 2:
    // 기존 세션을 가져옵니다 (존재하지 않으면 null).
    session = req.getSession(false);
    if (session != null) {
        out.print("<p>생성 시간 : " + formatTime.format(new Date(session.getCreationTime())) + "</p>");
        out.print("<p>마지막 접속 시간 : " + formatTime.format(new Date(session.getLastAccessedTime())) + "</p>");
        session.invalidate(); // 세션을 무효화합니다.
        out.print("세션이 삭제되었습니다.");
    } else {
        out.print("삭제할 세션이 존재하지 않습니다.");
    }
    break;

세번째 구간입니다. 사용자가 폼에 세션의 속성 이름과 값을 입력하면 각각 그 값들이 넘어올텐데요. 두 값이 모두 입력된 경우에 setAttribute로 아이템을 추가하고, 두 값 중 하나가 빠진 경우라면 주의 문구를 출력합니다.

그리고 attributeNames 메서드를 이용해 속성의 이름과, 이름을 이용해 getAttribute 메서드로 값을 함께 보여줍니다.

case 3:
    // 속성 값을 추가합니다.
    session = req.getSession();
    String itemName = req.getParameter("name");
    String itemValue = req.getParameter("value");

    if (itemName != null && itemValue != null) {
        session.setAttribute(itemName, itemValue);
        out.print("속성이 추가되었습니다.");
    } else {
        out.print("속성 이름과 값을 제공해야 합니다.");
    }

    // 추가된 속성 출력
    out.print("<p>속성 목록 : ");
    Enumeration<String> attributeNames1 = session.getAttributeNames();
    while (attributeNames1.hasMoreElements()) {
        String name = attributeNames1.nextElement();
        out.print("<li>" + name + ": " + session.getAttribute(name) + "</li>");
    }
    out.print("</p>");
    break;

네번째 구간입니다. 세번째 구간에서의 설명과 동일합니다. 메서드 이름만 다르고 원리는 거의 비슷하기 때문이죠.

case 4:
    // 속성 값을 제거합니다.
    session = req.getSession();
    String attributeName = req.getParameter("name");

    if (attributeName != null) {
        session.removeAttribute(attributeName);
        out.print("속성이 제거되었습니다.");
    } else {
        out.print("제거할 속성 이름을 제공해야 합니다.");
    }

    out.print("<p>속성 목록 : ");
    Enumeration<String> attributeNames2 = session.getAttributeNames();
    while (attributeNames2.hasMoreElements()) {
        String name = attributeNames2.nextElement();
        out.print("<li>" + name + ": " + session.getAttribute(name) + "</li>");
    }
    out.print("</p>");
    break;

다섯 번째 구간입니다. 세션을 가져온 후 사용자로부터 찾을 속성의 값을 받아온 뒤 그 값을 이용해 속성을 받아옵니다. 이때 전달되는 타입은 오브젝트 이므로 string으로 변환한 뒤 해당 속성의 값을 보여줍니다.

만약 존재하지 않는 속성의 이름일 경우 else문과 같이 출력해 주고 열거형으로 속성 목록을 보여줍니다.

case 5:
    // 속성 값을 조회합니다.
    session = req.getSession();
    String queryName = req.getParameter("name");

    if (queryName != null) {
        Object value = session.getAttribute(queryName);
        if (value != null) {
            out.print("<p>속성 이름 '" + queryName + "'의 값: " + value.toString() + "</p>");
        } else {
            out.print("<p>속성 이름 '" + queryName + "'은 존재하지 않습니다.</p>");
        }
    } else {
        out.print("조회할 속성 이름을 제공해야 합니다.");
    }
    out.print("<p>속성 목록 : ");
    Enumeration<String> attributeNames3 = session.getAttributeNames();
    while (attributeNames3.hasMoreElements()) {
        String name = attributeNames3.nextElement();
        out.print("<li>" + name + ": " + session.getAttribute(name) + "</li>");
    }
    out.print("</p>");
    break;

시연 영상

만들고보니 삭제 예외 처리를 안해줬네요 ㅎ...

profile
인생은 본인의 삶을 곱씹어보는 R과 타인의 삶을 배워 나아가는 L의 연속이다.

0개의 댓글