BACK_END/Spring 공부

Spring - 상태유지기술

harry595 2021. 2. 25. 20:15

HTTP 프로토콜은 상태유지가 되지 않는 프로토콜

-> 상태유지를 위해 Cookie와 Session 기술이 등장함

 

쿠키 - 유지할 정보를 사용자 컴퓨터에 저장, 유효기간이 지나면 사라짐, 정보유출 가능성 有

세션 - 서버에 저장, 서버가 종료되거나 유효기간이 지나면 사라짐

 

쿠키 동작 방식

1. 클라이언트가 서버에 요청을 보냄

2. 유지 해야할 정보가 있으면 WAS는 쿠키를 생성 (name,value로 구성)

3. 응답 결과에 쿠키를 포함시켜 클라이언트에게 전송

4. 클라이언트는 갖고있는(받은) 쿠키를 요청할때 같이 전송

5. WAS는 쿠키를 받아 사용자가 유지해야할 정보 파악

 

세션 동작 방식

1. 클라이언트가 서버에 요청을 보냄

2. 세션키를 생성

3. 세션키를 이용한 저장소 생성

4. 세션키를 담은 Cookie 생성

5. 세션키를 담은 쿠키를 포함해 응담

6. 클라이언트는 요청할 때마다 쿠키를 함께 전송

7. 쿠키의 세션키를 이용해 이전에 생성한 저장소를 활용 (httpsession 이용)

 

Cookie를 이용한 상태 유지

쿠키는 그 수와 크기에 제한

- 하나의 쿠키는 4K Byte 크기로 제한

- 브라우저는 각각의 웹 사이트 당 20개의 쿠키 허용

- 모든 웹 사이트를 합쳐 300개 저장 가능

- 서버에서 쿠키 생성, Response의 addCookie 메소드를 이용해 클라이언트에게 전송

  Cookie cookie = new Cookie(이름,값);

  response.addCookie(cookie);

- 쿠키의 이름은 알파벳과 숫자로 구성, 공백,괄호,등호,콤마,콜론,세미콜론은 포함 불가능

- 클라이언트가 보낸 쿠키 정보 읽기

  Cookie[] cookies = request.getCookies(); (쿠키 값이 없으면 null 반환)

- Cookie가 가지고 있는 getName()과 getValue() 메소드를 이용해 원하는 쿠키정보를 찾아 사용

- 클라이언트에게 쿠키 삭제 요청 (삭제하는 명령이 없고 maxAge가 0인 같은 이름의 쿠키 전송)

  Cookie cookie = new Cookie("이름",null);

  cookie.setMaxAge(0);

  response.addCookie(cookie);

- 쿠키의 유효기간 설정

  setMaxAge() - 인자는 유효기간을 나타내는 초 단위의 정수형, 0은 삭제, 음수는 브라우저가 종료될 때 삭제

- Spring MVC에서의 Cookie 사용

  @CookieValue 애노테이션 사용 - 컨트롤러 메소드에 이렇게 사용하면 원하는 쿠키 정보를 파라미터에 담아 사용가능

  컨트롤러메소드(@CookieValue(value="쿠키이름",required=false,defaultValue="기본값") String 변수명)

출처 - https://www.boostcourse.org/web326/lecture/58992/

Cookie 사용 예시

package kr.or.connect.guestbook.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.service.GuestbookService;

@Controller
public class GuestbookController {
	@Autowired
	GuestbookService guestbookService;

	@GetMapping(path="/list")
	public String list(@RequestParam(name="start", required=false, defaultValue="0") int start,
					   ModelMap model,
                       HttpServletRequest request,
					   HttpServletResponse response) {

        
		String value = null;
		boolean find = false;
		Cookie[] cookies = request.getCookies();
		if(cookies != null) {
			for(Cookie cookie : cookies) {
				if("count".equals(cookie.getName())) {
					find = true;
					value = cookie.getValue();
				}
			}
		}
		
      
		if(!find) {
			value = "1";
		}else { // 쿠키를 찾았다면.
			try {
				int i = Integer.parseInt(value);
				value = Integer.toString(++i);
			}catch(Exception ex) {
				value = "1";
			}
		}
		
   
		Cookie cookie = new Cookie("count", value);
		cookie.setMaxAge(60 * 60 * 24 * 365); // 1년 동안 유지.
		cookie.setPath("/"); // / 경로 이하에 모두 쿠키 적용. 
		response.addCookie(cookie);
		
		
		List<Guestbook> list = guestbookService.getGuestbooks(start);
		
		int count = guestbookService.getCount();
		int pageCount = count / GuestbookService.LIMIT;
		if(count % GuestbookService.LIMIT > 0)
			pageCount++;
		
		List<Integer> pageStartList = new ArrayList<>();
		for(int i = 0; i < pageCount; i++) {
			pageStartList.add(i * GuestbookService.LIMIT);
		}
		
		model.addAttribute("list", list);
		model.addAttribute("count", count);
		model.addAttribute("pageStartList", pageStartList);
		model.addAttribute("cookieCount", value); // jsp에게 전달하기 위해서 쿠키 값을 model에 담아 전송한다.
		
		return "list";
	}
	
	@PostMapping(path="/write")
	public String write(@ModelAttribute Guestbook guestbook,
						HttpServletRequest request) {
		String clientIp = request.getRemoteAddr();
		System.out.println("clientIp : " + clientIp);
		guestbookService.addGuestbook(guestbook, clientIp);
		return "redirect:list";
	}
}

 

@CookieValue 애노테이션을 사용하여 쿠키 관리

package kr.or.connect.guestbook.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.service.GuestbookService;

@Controller
public class GuestbookController {
	@Autowired
	GuestbookService guestbookService;

  	@GetMapping(path="/list")
	public String list(@RequestParam(name="start", required=false, defaultValue="0") int start,
					   ModelMap model, @CookieValue(value="count", defaultValue="1", required=true) String value,
					   HttpServletResponse response) {
		
        // 쿠키 값을 1증가 시킨다.
		try {
			int i = Integer.parseInt(value);
			value = Integer.toString(++i);
		}catch(Exception ex){
			value = "1";
		}
		
        // 쿠키를 전송한다.
		Cookie cookie = new Cookie("count", value);
		cookie.setMaxAge(60 * 60 * 24 * 365); // 1년 동안 유지.
		cookie.setPath("/"); // / 경로 이하에 모두 쿠키 적용. 
		response.addCookie(cookie);
		
		List<Guestbook> list = guestbookService.getGuestbooks(start);
		
		int count = guestbookService.getCount();
		int pageCount = count / GuestbookService.LIMIT;
		if(count % GuestbookService.LIMIT > 0)
			pageCount++;
		
		List<Integer> pageStartList = new ArrayList<>();
		for(int i = 0; i < pageCount; i++) {
			pageStartList.add(i * GuestbookService.LIMIT);
		}
		
		model.addAttribute("list", list);
		model.addAttribute("count", count);
		model.addAttribute("pageStartList", pageStartList);
		model.addAttribute("cookieCount", value); // 쿠키를 추가한다.
		
		return "list";
	}
	
	@PostMapping(path="/write")
	public String write(@ModelAttribute Guestbook guestbook,
						HttpServletRequest request) {
		String clientIp = request.getRemoteAddr();
		System.out.println("clientIp : " + clientIp);
		guestbookService.addGuestbook(guestbook, clientIp);
		return "redirect:list";
	}
}

 

 

Session을 이용한 상태 정보 유지

- 클라이언트 별로 서버에 저장되는 정보

- 웹 클라이언트가 서버측에 요청을 보내게 되면 서버는 클라이언트를 식별하는 session id 생성

- 서버는 session id를 이용해서 key와 value를 이용한 저장소인 HttpSession을 생성

- 서버는 session id를 저장하고 있는 쿠키를 생성하여 클라이언트에 전송

- 클라이언트는 서버측에 요청을 보낼때 session id를 가지고 있는 쿠키를 전송

- 서버는 쿠키에 있는 session id를 이용해서 그 전 요청에서 생성한 HttpSession을 찾고 사용

- HttpSession session = request.getSession();  (아래 true와 같음)

  HttpSession session = request.getSession(true);

  -> 서버에 생성된 세션이 있다면 세션을 반환하고 없다면 새롭게 세션을 생성하여 반환

- HttpSession session = request.getSession(false);

  -> 이미 생성된 세션이 있다면 반환하고 없으면 null을 반환

- 세션에 값 저장 session.setAttribute(String name, Object value)

- 세션에 값 조회 session.getAttrbute(String name)

- 세션에 값 삭제 session.removeAttrivute(String name)

- 세션은 클라이언트가 서버에 접속하는 순간 생성

- 세션의 유지시간은 기본 값으로 30분 설정

- 세션의 유지시간이란 서버에 접속한 후 서버에 요청을 하지 않는 최대시간

- 이 세션 유지시간은 web.xml 파일에서 설정 가능

  <session-config> <session-timeout>30</session-timeout> </session-config>

- @SessionAttributes 파라미터로 지정된 이름과 같은 이름이 @ModelAttribute에 지정되어 있을경우 메소드가 반환되는 값은 세션에 저장된다.

- @SessionAttributes의 파라미터와 같은 이름이 @ModelAttribute에 있을 경우 세션에 있는 객체를 가져온 후 클라이언트로 전송받은 값을 설정한다.

- 메소드에 @SessionAttribute가 있을 경우 파라미터로 지정된 이름으로 등록된 세션정보를 읽어와서 변수에 할당한다.

- SessionStatus는 컴트롤러 메소드의 파라미터로 사용할 수 있는 스프링 내장 타입이다. 이 object를 이용하면 현재 컨트롤러의 @SessionAttributes에 의해 저장된 오브젝트를 제거할 수 있다.

 

사용방법 - 숫자 맞추기 게임

controller.java

package kr.or.connect.guestbook.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class GuessNumberController {
	@GetMapping("/guess")
	public String guess(@RequestParam(name="number",required=false) Integer number,HttpSession session, ModelMap model) {
		String message = null;
		if(number == null) {
			session.setAttribute("count",0);
			session.setAttribute("randomNumber",(int)(Math.random()*100)+1);
			message = "내가 생각한 숫자를 맞춰 보세요";
		}else {
			int count = (Integer)session.getAttribute("count");
			int randomNumber = (Integer)session.getAttribute("randomNumber");
			
			if(number < randomNumber) {
				message = "입력한 값은 내가 생각한 숫자보다 작습니다.";
				session.setAttribute("count",++count);
			}else if(number > randomNumber){
				message = "입력한 값은 내가 생각한 숫자보다 큽니다.";
				session.setAttribute("count",++count);
			}else {
				message = "정답입니다 count:"+ ++count+"내가 생각한 숫자는 "+number+"입니다.";
				session.removeAttribute("count");
				session.removeAttribute("randomNumber");
			}
		}
		model.addAttribute("message",message);
		return "guess";
	}
}

view.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>       
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>숫자 맞추기 게임</title>
</head>
<body>
<h1> 숫자 맞추기 게임.</h1>
<hr>
<h3>${message }</h3>

<c:if test="${sessionScope.count != null}">
<form method="get" action="guess">
1부터 100사이의 숫자로 맞춰주세요.<br>
<input type="text" name="number"><br>
<input type="submit" value="확인">
</form>
</c:if>

<a href="guess">게임 다시 시작하기.</a>
</body>
</html>

 

@SessionAttribute 사용 방식

@GetMapping(path="/delete")
public String delete(@RequestParam(name="id", required=true) Long id, 
			         @SessionAttribute("isAdmin") String isAdmin,
			         HttpServletRequest request,
			         RedirectAttributes redirectAttr) {
	if(isAdmin == null || !"true".equals(isAdmin)) { // 세션값이 true가 아닐 경우
		redirectAttr.addFlashAttribute("errorMessage", "로그인을 하지 않았습니다.");
		return "redirect:loginform";
	}
	String clientIp = request.getRemoteAddr();
	guestbookService.deleteGuestbook(id, clientIp);
	return "redirect:list";		
}