package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {
    // 정적 컨텐츠
    @GetMapping("hello")
    public String hello(Model model){
        model.addAttribute("data","hello!!");
        String tmp = "hello";
        return "hello";
    }
    // MVC와 템플릿 엔진
    @GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name")String name,Model model){
        model.addAttribute("name",name);
        return "hello-template";
    }

    // api 방식 문자열 반환
    @GetMapping("hello-string")
    @ResponseBody // http 응답 바디부에 해당 내용을 직접 넣겠다는 어노테이션
    public String helloString(@RequestParam("name") String name){
        return "hello "+name; //"hello spring"
    }
    // api JSON 반환
    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name")String name){
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    static class Hello{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

정적 컨텐츠

  1. 웹(클라이언트 요청) url 입력
  2. 톰캣(서버) 
  3. 스프링 컨테이너에서 매핑되어 있는 컨트롤러가 있는지 탐색
  4. 관련된 컨트롤러가 없다면 resources 디렉토리에서 관련된 항목을 찾아 전달

MVC와 템플릿 엔진

<관심사의 분리 + 타임리프 엔진을 통한 html 변환>

  1. 웹(클라이언트 요청) url 입력
  2. 톰캣(서버) 
  3. 스프링 컨테이너에서 매핑되어 있는 컨트롤러 발견
  4. 해당 컨트롤러에서 model에 필요한 데이터를 넣고 return 해야 하는 view의 이름과 model을 viewResolver에게 전달
  5. Thymeleaf 템플릿 엔진이 처리하여 변환한 html을 웹 브라우저에게 반환 

API

@ResponseBody 어노테이션을 활용해 http 응답 바디부에 해당 내용을 직접 넣어 전달

String을 전달해 주어도 좋으나 객체(Class)를 반환하여 JSON 형식으로 전달하는 것이 기본

  1. 웹(클라이언트 요청) url 입력
  2. 톰캣(서버) 
  3. 스프링 컨테이너에서 @ResponseBody 의 매핑되어 있는 컨트롤러 발견 
  4. HttpMessageConverter가 객체(JSON) 혹은 문자열을 웹 브라우저에게 그래도 반환
    1. 문자처리 StringHttpMesaageConverter
    2. 객체처리: MappingJackson2HttpMessageConverter

+ 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 반환 데이터의 타입을 결정

 

 

다음과 같이 프로젝트 초기 셋팅을 완료하고 인테릴제이로 해당 프로젝트를 열었을 때

 

No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.0.4 was found. 

이라는 에러가 뜨면서 main파일의 내용을 실행할 수 없는 문제가 생기는데 

이의 원인은 ide 실행 시 작동하는 gradle 버전이 jdk와 호환되지 않아 해당 문제가 발생했던 것

필자는 다음과 같이 Gradle JVM의 버전을 11로 바꾸니 해당 문제를 해결 할 수 있었다.

'Development > Spring' 카테고리의 다른 글

Spring의 테스트 기능  (0) 2023.03.09
Spring 웹 데이터 전달 종류 3가지  (0) 2023.03.09
Spring 에서의 예외처리 방법들  (0) 2023.02.19
세션  (0) 2023.02.06
Spring 쿠키 생성 삭제  (0) 2023.02.02

1. @ExceptionHandler와 @ControllerAdvice

  • 예외 처리를 위한 메서드를 작성하고 @ExceptionHandler를 붙인다.
  • @ControllerAdvice로 전역 예외 처리 클래스 작성 가능(패키지 지정 가능) 예외 처리 메서드가 중복된 경우, 컨트롤러 내의 예외 처리 메서드가 우선

2. @ResponseStatus

  • 응답 메시지의 상태 코드를 변경할 때 사용
  • 사용자 정의 예외처리 메서드 위에 작성
<%@ page contentType="text/html;charset=utf-8" isErrorPage="true"%>
발생한 예외 : ${pageContext.exception}<br>
예외 메시지 : ${pageContext.exception.message}<br>
@ExceptionHandler(Exception.class)
	  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 200 -> 500
	  public String catcher(Exception ex, Model m) { 
		  System.out.println("catcher() in ExceptionController");
		  //m.addAttribute("ex",ex); 
		  return "error"; 
		  }

3. <error-page> - web.xml

  • 상태 표시별 뷰 맵핑
  • <web.xml>
<error-page>
		<error-code>400</error-code>
		<location>/error400.jsp</location>
	</error-page>
	<error-page>
		<error-code>500</error-code>
		<location>/error500.jsp</location>
	</error-page>
  • error400.jsp, error500.jsp
    • <%@ page contentType="text/html;charset=utf-8" isErrorPage="true"%>
      • 상태코드를 500으로 강제로 바꾼다.

4. SimpleMappingExceptionResolver

  • 예외 종류별 뷰 맵핑
  • servlet-context.xml에 등록
<beans:bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<beans:property name="defaultErrorView" value="error"/>
    		<beans:property name="exceptionMappings">
      			<beans:props>
        			<beans:prop key="com.fastcampus.ch2.MyException">error400</beans:prop>
      			</beans:props>
    		</beans:property>
		<beans:property name="statusCodes">
			<beans:props>
        			<beans:prop key="error400">400</beans:prop>
			</beans:props>
		</beans:property>
  	</beans:bean>	
	
	<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<beans:property name="basenames">
			<beans:list>
				<beans:value>error_message</beans:value> <!-- /src/main/resources/error_message.properties -->
			</beans:list>
		</beans:property>
		<beans:property name="defaultEncoding" value="UTF-8"/>
	</beans:bean>

+ ExceptionResolver (전체적인 예외 처리 과정)

  1. 클라이언트의 요청
  2. DispatcherServlet이 해당 요청에 맞는 Controller 매핑
  3. Controller에 예외 발생
  4. 다시 DispatcherServlet에게 예외 전달
  5. DispatcherServlet이 예외를 처리하기 위해 등록된 handlerExceptionResolvers 탐색
    1. ExceptionHandlerExceptionResolver
    2. ResponseStatusExceptionResolver
    3. DefaultHandlerExceptionResolver
  6. 찾은 경우 @ExceptionHandler 에 해당되는 error view를 DispatcherServlet에게 전달 (ExceptionHandlerExceptionResolver)
    1. 전달 된 view를 응답
  7. 찾지 못 한 경우 @ResponseStatus (ResponseStatusExceptionResolver) 탐색
    1. web.xml에 가서 해당 상태코드에 해당하는 뷰 탐색
    2. 탐색 된 view를 전달 하고 응답
  8. 위 과정에서 탐색 실패한 경우 Spring에 정의된 예외처리에 따라 상태 코드 500을 400이나 500으로 적절히 바꾸어서 줌 (DefaultHandlerExceptionResolver)

스프링에서의 예외 처리 (총 정리)

  • 컨트롤러 메서드 내에서 try-catch로 처리
  • 같은 컨트롤러 내부의 @ExceptionHandler 메서드가 처리
  • @ControllerAdvice(”패키지 지정”) 클래스의 @ExceptionHandler메서드가 처리
  • 예외 종류별로 뷰 지정 - SimpleMappingExceptionResolver
  • 응답 상태 코드별로 뷰 지정 - <error-page>

세션이란?

  • 서로 관련된 요청 응답들을 하나로 묶은 것 (쿠키를 이용)
    • 원래 요청은 독립적 (서로 관계가 없음)
  • 브라우저 마다 개별 저장소(session객체)를 서버에서 제공 (1대1 관계)
    • 세션 객체마다 한 개의 아이디를 가지고 있음
  • 로그인 부터 로그아웃 까지

세션의 생성 과정

  • 클라이언트가 요청
  • 서버가 세션 객체 생성
  • 서버가 세션 아이디가 담긴 쿠키를 응답으로 보냄
    • 만약 브라우저에서 쿠키를 허용하지 않았을 경우를 감안해 url에 get방식으로 해당 쿠키 정보를 담아서도 전송
      • STS에서 url 태그가 자동으로 세션아이디를 붙여줌 (서버에서 처리)
      <form action="<c:url value='/login/login'/>" method="post" onsubmit="return formCheck(this);">
      // 반드시 링크에는 url 태그를 붙여주어야 함 그래야만 쿠키를 허용하지 않는 브라우저에도 세션 아이디를 제공하는 것이 가능
      
  • 브라우저에 쿠키가 저장
  • 클라이언트가 요청을 보냄(쿠키가 자동으로 포함되어 전송)
  • 서버가 요청 중에 쿠키가 포함된 세션 아이디를 확인하여 같은 브라우저에서 온 요청인지 확인
HttpSession session = request.getSession();
session.setAttribute("id","asdf");
  • 일치하는 세션 아이디의 개별 저장소 사용
  • jsp에서 session영역에서 접근하려면 sessionScope 사용
<c:set var="loginOutLink" value="${sessionScope.id==null? '/login/login':'/login/logout'}"/>
<c:set var="loginOut" value="${sessionScope.id==null? 'Login':'Logout' }"/>

세션의 종료

  • 수동 종료
HttpSession session = request.getSession();
session.invaildate(); // 세션을 즉시 종료
session.setMaxInactiveInterval(30*60); // 예약 종료(30분 후)
  • 자동 종료 - web.xml
<session-config>
	<session-timeout>30<session-timeout>
</session-config>
  • StandardManager가 자동으로 세션의 생성과 소멸을 관리한다 하더라도 서버에 부담이 크기 때문에 세션 저장소를 사용한다면 최소한의 데이터만을 담아야 한다.

쿠키 vs 세션

  • 쿠키
    • 브라우저에 저장
    • 서버 부담 X
    • 보안에 불리
    • 서버 다중화에 유리
  • 세션
    • 서버에 저장
    • 서버 부담 O
    • 보안에 유리
    • 서버 다중화에 불리

쿠키

정의: 이름과 값의 쌍으로 구성된 작은 정보. 아스키 문자만 가능

서버에서 생성 후 전송, 브라우저에 저장, 유효기간 이후 자동 삭제

서버에 요청시 domain, path가 일치하는 경우에 하위경로 포함 자동 전송

  • 한글의 경우 URL인코딩 필요

목적: 클라이언트를 식별하기 위해서 http 프로토콜 보완

 

구성

  • name
  • value
  • Domain
  • path
  • 유효기간

선행 과정

  1. 클라이언트의 요청
  2. 서버가 쿠키 생성
  3. 생성된 쿠키를 해당 프로토콜 해더에 담아서 전달
  4. 브라우저에 해당 쿠키가 저장

후행 과정

  1. 클라이언트의 요청
  2. 만약 기존에 남아있던 쿠키와 일치하는 요청이라면
  3. 자동으로 브라우저에 저장된 쿠키가 함께 요청이 해더에 담겨 전송 됨

쿠키의 생성 (Spring)

Cookie cookie = new Cookie(”name”,”value”); // 쿠키의 생성
cookie.setMaxAge(60*60*24); // 유효기간 설정(초)
response.addCookie(cookie); // 응답에 쿠키 추가

 

쿠키의 삭제와 변경 (Spring)

Cookie cookie = new Cookie(”name”,””); // 변경할 쿠키의 같은 이름의 쿠키 생성
cookie.setMaxAge(0); // 유효기간을 0으로 설정(삭제)
response.addCookie(cookie); // 응답에 쿠키 추가

Cookie cookie = new Cookie(”name”,””); // 변경할 쿠키의 같은 이름의 쿠키 생성
cookie.setValue(URLEncoder.encode("남궁성")); // 값의 변경
cookie.setDomain("www.fastcampus.co.kr"); // 도메인의 변경
cookie.setPath("/ch"); // 경로의 변경
cookie.setMaxAge(60*60*24*7); // 유효기간의 변경
response.addCookie(cookie); //응답에 쿠키 추가

 

쿠키 읽어 오기

// 쿠키가 없으면 NULL 즉 NULL체크 필요
Cookie[] cookies = request.getCookies(); // 쿠키 읽기

for(Cookie cookie: cookies{
    String name = cookie.getName();
    String value = cookie.getValue();

    System.out.printf("[cookie]name=%s,value=%s%n",name,value);
}

 

쿠키를 활용하여 로그인 정보 남게 하기

<HTML>

  • EL을 활용하여 value = ${cookie.id.value} 사용
  • 쿠키가 있으면 체크 없으면 no체크 ${empty cookie.id.value ? "":"checked”}

직접 브라우저에서 쿠키 생성

빈칸을 더블클릭 후 원하는 이름과 값을 적어 놓으면 생성 완료

 

Spring 코드로 생성

public String login(String id,String pwd,boolean rememberId, HttpServletResponse response){
    // 아이디를 기억하기를 원하면
        if(rememberId) {
            //    1. 쿠키를 생성
            Cookie cookie = new Cookie("id",id); 
            //    2. 응답에 저장
            response.addCookie(cookie);
        }
        // 그렇지 않으면
        else {
            Cookie cookie = new Cookie("id","");
            cookie.setMaxAge(0); // 쿠키를 삭제
            response.addCookie(cookie);
        }
        //    3. 홈으로 이동
        return "home";
    }

'Development > Spring' 카테고리의 다른 글

Spring 에서의 예외처리 방법들  (0) 2023.02.19
세션  (0) 2023.02.06
URL 패턴  (0) 2023.01.20
Spring의 4개의 저장소  (0) 2023.01.19
HTTP 프로토콜의 바이너리 파일 전송  (0) 2023.01.08

사용처: @WebServlet으로 서블릿을 URL에 매핑할 때 사용

예시 형식: @WebServlet(urlPatterns={"/hello","hello/*"}, loadOnStartup=1)

  • Servlet은 늦은 초기화가 기본이다 하지만 loadOnStartup 옵션을 주면 미리 초기화 하는 것이 가능 (=1) 값은 우선순위를 나타내며 값의 중복이 가능하다.

 

exact mapping ~ default mapping 까지 우선순위로 매핑

exact mapping: 정확히 매칭되는 것

  • 우선순위 1등
  • ex: /login/hello.do
  • 동적 리소스(서블릿)

path mapping: 해당 경로가 들어간 url이 들어오면 처리

  • 우선순위 2등
  • ex: /login/*
  • 동적 리소스(서블릿)

extension mapping: 해당 확장자가 붙은 url이 들어오면 처리

  • 우선순위 3등
  • ex: *.do
  • 동적 리소스(서블릿)

default mapping: 디폴트 매핑 모든 주소와 매핑이 됨

  • 우선순위 4등
  • ex: /
  • 정적 리소스(image, css, text, 404)

작동 순서

  1. Servlet Context 안에 Servlet 등록시
  2. children(서블릿)안에 저장
  3. servletMappings의 매핑됨

 

+ Spring에서는 위의 과정을 사용하지 않고 DispatcherServlet이 해당 과정을 유사하게 내부에 가지고 있다. (즉 DispatcherServlet 내부적으로 처리)

  • Spring은 Servlet을 발전 시킨 것
  • spring으로 개발 할 때는 jsp와 Servlet을 view로 쓰긴 하지만 등록하지는 않는다. 따라서 default(DispatcherServlet) 이 모든 것을 받는다.
  • 공통 Spring web.xml (설정)파일 부분 (default mapping)
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  • 개별 Spring web.xml (설정)파일 부분 (DispatcherServlet이 받는 것을 알 수 있다.)
	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

'Development > Spring' 카테고리의 다른 글

세션  (0) 2023.02.06
Spring 쿠키 생성 삭제  (0) 2023.02.02
Spring의 4개의 저장소  (0) 2023.01.19
HTTP 프로토콜의 바이너리 파일 전송  (0) 2023.01.08
웹과 HTTP 프로토콜  (0) 2023.01.06

<기본 전제>

HTTP 특징 - 상태정보를 저장하지 않는다 (stateless) 

만약에 클라이언트가 로그인 후 다시 요청을 보낸다면
HTTP 특성상 로그인 정보를 저장하지 않기 때문에 같은 클라이언트가 보낸건지 알 수 없다.

따라서 상태정보를 저장해줄 저장소가 필요하다

 

공통적인 특징

Map 형태로 데이터가 저장되어 있음 - Key값: 속성(attribute)
접근 범위, 생존 기간 존재
setAttribute() 로 데이터를 쓰기(저장)
getAttribute() 로 데이터를 읽기(저장)
removeAttribute() 로 속성을 삭제 가능
getAttributeNames() 모든 속성의 이름을 반환
서로 간의 읽기 쓰기가 가능

 

pageContext 저장소

  • 유효범위: 1개 JSP 페이지
  • lv(지역변수) 저장
    • request
    • response
  • 접근 범위: 해당 jsp페이지 내부에서만 접근 가능
  • EL 에서 사용하기 위해서 lv(지역변수)를 저장하는 것
    • 문법: ${} 
  • 요청 할때 마다 초기화

 

application 저장소

  • 유효범위: context 전체
  • WebApp 전체에서 공유하고 접근이 가능한 저장소
    • 개별적인 id를 저장하기에는 좋은 저장소가 아님
  • 딱 1개만 존재
  • 어디에서나 접근이 가능
  • 전체 데이터 저장소

 

session 저장소

  • 유효범위: n개 JSP페이지
  • 클라이언트와 1대1로 대응한다.
    • 클라이언트가 로그인 하면 생성
    • 클라이언트가 로그아웃시 삭제
    • 문제점: 사용자 숫자 만큼 객체가 생기기 때문에 수가 많아짐
      • 서버 부담이 가장 큼
      • 따라서 최소한의 데이터만 저장
  • 개별 저장소
    • id, 장바구니
  • id pass 정보를 저장 하는 곳
  • 쿠키를 이용하여 클라이언트와 연결
  • 웹 페이지 들 간의 데이터를 전달할 때 가장 쉽다 (권장 x)-메모리 부담이 큼
    • 사용을 최소하는 것을 권장
    • 사용 하더라도 잠깐 저장하고 삭제하는 방식

 

request 저장소

  • 유효범위: 1개 이상 JSP페이지
  • 요청의 시작부터 응답까지. 다른 JSP로 전달(forward) 가능. 요청마다 1개
  • 클라이언트가 요청 할 때 마다 생성
  • 각각의 request 저장소는 서로 독립적
  • 웹 페이지 들 간의 데이터를 전달할 때 부담이 제일 적게 사용 가능 (최우선)

'Development > Spring' 카테고리의 다른 글

Spring 쿠키 생성 삭제  (0) 2023.02.02
URL 패턴  (0) 2023.01.20
HTTP 프로토콜의 바이너리 파일 전송  (0) 2023.01.08
웹과 HTTP 프로토콜  (0) 2023.01.06
클라이언트와 서버  (0) 2023.01.05

HTTP 프로토콜은 txt 기반 전송 프로토콜이다. 

하지만 HTTP 프로토콜을 사용하여 이미지, 동영상 파일을 보내기도 하는데 이것이 어떻게 가능한 것 일까?

 

바이너리 파일이란?

  • 텍스트 파일과는 다르게 문자와 숫자로 저장되어 있는 파일
  • 동영상, 이미지 파일, 음성 파일이 바이너리 파일에 속한다. 
  • 따라서 해당 종류의 파일을 단순한 txt 편집기로 열게 된다면 숫자 부분이 깨져서 표시가 된다.

<방법>

1. MIME 타입을 쓰고 바이너리 파일을 그대로 넣는 방법

MIME 이란 (Multipurpose Internet Mail Extensions)의 약어이며 
전송할 데이터의 타입과 서브타입을 명시하는 것을 말한다.
ex: text/htiml, image/bmp, audio/wav, video/webm, application//pdf 

2. Base64 인코딩을 사용하여 바이너리 파일을 텍스트 파일로 변환하여 보내는 방법

64비트 체계로 변환하여 사용하는 것 (6비트씩 끊어서 문자 하나로 변환)
어떤(OS)의 인코딩이든 6비트의 해당 문자 체계는 존재한다.
따라서 OS가 달라도 2^6 64진법을 사용하면 안전하게 변환이 가능하다.
단 인코딩의 종류에 따라서 문자 하나의 크기가 다를 수 있는데.
UTP-8에서는 영문자, 숫자가 1byte(8비트) 임으로 6비트 -> 8비트로
(데이터의 양이 늘어나는 단점이 있다.)

2-1 활용 예시:

HTML파일에서 이미지 파일의 링크가 깨지더라도 Base64 인코딩 방법을 사용하면 안전하게 이미지 파일 전송 가능

해당 HTML 파일 같은 경우 같은 경로에 text.png가 존재 하여야 함

하지만 다음과 같이 태그 내용을 수정하고 뒤에 해당 이미지 파일을 Base 64로 인코딩한 내용을 붙여주면

해당 경로에 사진 파일이 존재하지 않아도 제대로 해당 이미지가 나타남을 알 수 있다.

'Development > Spring' 카테고리의 다른 글

URL 패턴  (0) 2023.01.20
Spring의 4개의 저장소  (0) 2023.01.19
웹과 HTTP 프로토콜  (0) 2023.01.06
클라이언트와 서버  (0) 2023.01.05
Spring 원격 프로그램과 데이터 주고(request) 받기 (response)  (0) 2023.01.04

+ Recent posts