[스프링 부트의 정석] Part2. 스프링 부트 시작하기

박소은·2024년 7월 19일
0
post-thumbnail

static 메서드는 객체 생성 없이 호출 가능하다. 그렇다면 원격 프로그램은 어떻게 실행할 수 있을까?

원격 프로그램의 실행

웹 브라우저 + WAS(톰캣) -> 원격 프로그램의 실행

  1. 프로그램 등록
    @Controller 을 통해 프로그램 등록
  2. URL과 프로그램을 연결
    이 URL을 연결했을 때 실행될 메서드를 연결해준다.
@Controller // 1. 프로그램 등록
public class Hello {
	@RequestMapping("/hello") // 2. URL과 main()을 연결
	public void main() {
		System.out.println("Hello");
    }
}

원래 static이 없다면 객체를 생성해야 한다. 다만, @Controller 어노테이션이 붙은 프로그램은 스프링이 자동으로 객체를 생성해주기 때문에 인스턴스 메서드(static이 안 붙은 메서드)가 URL이 연결되어 다른 컴퓨터에서 호출될 수 있다.

** 인텔리제이 단축키
패키지를 선택한 상태에서 Command + N
해당 패키지에 파일 추가

** github

  • git add .: 현재 디렉토리와 그 하위 디렉토리에서 변경된 파일들만 스테이징한다. 새로운 파일과 변경된 파일을 스테이징하지만, 이미 삭제된 파일은 스테이징하지 않는다.
  • git add -A: 리포지토리 전체에서 모든 변경 사항을 스테이징한다. 새로운 파일, 변경될 파일뿐만 아니라 삭제된 파일도 스테이징한다.

따라서, 프로젝트 전체에서 변경 사항을 커밋하려는 경우 'git add -A'를 사용하는 것이 더 안전하며, 특정 디렉토리에서만 작업하는 경우 'git add .'를 사용할 수 있다.

클라이언트와 서버

클라이언트와 서버는 역할로 구분한다.

서버의 종류

  • Email을 서비스하면 Email Server
  • Web을 서비스하면 Web Server

브라우저를 통해 사용하는 서비스들은 Web Server를 통해 이루어진다.

한 대의 컴퓨터에 서버 프로그램이 여러 개 설치되어 있다면 같은 IP 내에 서비스가 존재하기 때문에 포트가 필요하다. 서버 프로그램은 특정 포트와 연결되어 있다. Email Server는 25번 포트, File Server는 22번 포트, Web Server는 80번 포트를 기본적으로 사용한다.

WAS란?

Web Application을 서비스하는 서버이다.

  • Application = Server

클라이언트가 애플리케이션을 사용할 수 있게 해준다. 서버에 있는 프로그램을 클라이언트에게 서비스해주는 프로그램이다.

HTTP 요청과 응답

  1. DNS에 문의

사용자는 브라우저에 URL을 입력하게 된다. 이때 DNS 서버가 도메인 이름(fastcampus.co.kr)이 IP 주소(111.222.33.44)를 알려준다. 해당 IP 주소가 있는 웹 서버에 요청이 간다.

  1. TCP 연결(3-way handshake)
  • SYN / ACK + SYN / ACK
  • 연결을 하면 두개의 Stream이 생긴다. I/O Stream
  1. HTTP 요청

  2. HTTP 응답

  • 헤더 + 바디(html)
  1. 응답을 수신하고 연결을 종료 (재사용)

원격 프로그램에 데이터 전달하기

HttpServletRequest

  1. HttpServletRequest
  • 클래스
  • http 요청 정보를 제공
  • URL로 블라우저에서 클라이언트가 요청을 한다. Tomcat이 요청 정보를 받아서 HttpServletRequest 객체를 생성한다. 이 객체에 요청 정보를 받는 것이다.
  • 메서드의 매개 변수에 HttpServletRequest request를 적어주면 메서드 안에서 해당 객체의 메서드를 사용할 수가 있다.
  1. HttpServletRequest의 메서드
  • getScheme()
  • getServerName()
  • getServerPort()
  • getContextPath(): Web Application 시작 루트 경로이다. 루트로 설정 시 없을 수도 있다.
  • getRequestURI(): 서버 Name과 Port를 제외한 부분
  • getQueryString(): ?부터 끝까지. 원격 프로그램에 data를 전달할 때 사용한다. &는 구분자이다. 값의 name과 value를 구분자(&)를 통해 원격 프로그램에 전달한다.
  • getRequestURL()

위의 메서드를 통해 원하는 정보를 얻을 수 있다. URL을 잘라서 원하는 것을 얻을 필요가 없다.

String year = request.getParameter("year");
String month = request.getParameter("month");
String day = request.getParameter("day");

// Enumeration은 Iterator의 old version이다.
Enueration enum = request.getParameterNames();
Map paramMap = request.getParameterMap();

// 같은 name이 많을 때 배열로 반환한다. 
// 만약 getParameter()를 사용 시 첫 번째 value만 반환된다.
/* ex) 체크박스의 경우 
hobby=computer&hobby=drawing&hobby=music 
이런 식으로 들어오게 된다. */

String[] yearArr = request.getParameterValues("year");

GET과 POST의 차이점

GET

입력한 내용이 query string으로 URL뒤에 붙는다.

  • 서버로부터 리소스를 얻어오기(=읽기)
  • 요청에 body가 없다.
  • URL에 전송 데이터가 있다.
  • Query String을 통해 데이터를 전달(소용량)
  • URL에 데이터 노출되므로 보안에 취약
  • 데이터 공유에 유리
    ex. 검색 엔진에서 검색단어 전송에 이용

POST

URL 뒤에 query string이 붙지 않고 별도로 전송이 된다.

  • 글 쓰기
  • 전송할 데이터가 body에 있어서 URL에 붙이지 않는다.
  • 서버에 데이터를 올리기 위해 설계됨.
  • 전송 데이터 크기의 제한이 없음(대용량)
  • 보안에 유리, 데이터 공유에는 불리
    ex. 게시판에 글쓰기, 로그인, 회원가입

HTTP 요청 방법

  1. URL 직접 입력 - GET
  2. <a> - GET
  3. <form> - GET이 default, POST

HTTP 요청과 응답

프로토콜이란?

서로 간의 통신을 위한 규칙
주고 받을 데이터에 대한 형식을 정의한 것

HTTP

Hyper Text Transfer Protocol
단순하고 읽기 쉽다.
텍스트 기반의 프로토콜

클라이언트가 브라우저에 URL을 입력하면 HTTP 요청 메시지를 만들어 서버에 요청 메시지를 보낸다. 서버는 이를 보고 HTTP 응답 메시지를 만들어 클라이언트에게 보여준다.

상태를 유지하지 않는다(stateless)
클라이언트 정보를 저장하지 않는다. 서버에 부담이 없어진다.

확장 가능하다.
커스텀 헤더 추가 가능

상태 코드

  • 1XX: Informational(정보 제공)
    임시 응답으로 현재 클라이언트의 요청까지는 처리되었으니 계속 진행하라는 의미입니다. HTTP 1.1 버전부터 추가되었습니다.
  • 2XX: Success(성공)
    클라이언트의 요청이 서버에서 성공적으로 처리되었다는 의미입니다.
  • 3XX: Redirection(리다이렉션)
    완전한 처리를 위해서 추가 동작이 필요한 경우입니다. 주로 서버의 주소 또는 요청한 URI의 웹 문서가 이동되었으니 그 주소로 다시 시도하라는 의미입니다.
  • 4XX: Client Error(클라이언트 에러)
    없는 페이지를 요청하는 등 클라이언트의 요청 메시지 내용이 잘못된 경우를 의미합니다.
  • 5XX: Server Error(서버 에러)
    서버 사정으로 메시지 처리에 문제가 발생한 경우입니다. 서버의 부하, DB 처리 과정 오류, 서버에서 익셉션이 발생하는 경우를 의미합니다.

텍스트 파일과 바이너리 파일

  • 바이너리 파일: 문자와 숫자가 저장되어 있는 파일
  • 텍스트 파일: 문자만 있는 저장되어 있는 파일

원격 프로그램으로 응답하기

정적 리소스와 동적 리소스

  • 정적 리소스
    메서드가 호출 될 때마다 다른 결과가 반환된다.

static 폴더의 index.html (정적 리소스)

src/main/resources/static 폴더에 위치한 파일들은 정적 리소스로 취급된다.
이 파일들은 서버 측 처리 없이 그대로 클라이언트에 제공된다.
따라서 localhost:8080/index.html로 직접 접근할 수 있다.

templates 폴더의 index2.html (동적 리소스):

src/main/resources/templates 폴더에 위치한 파일들은 동적 리소스로 취급된다.
이 파일들은 주로 템플릿 엔진(예: Thymeleaf)을 통해 처리되어야 한다.
직접 URL로 접근할 수 없으며, 컨트롤러를 통해 접근해야 한다.

templates 폴더의 index2.html에 접근하려면 컨트롤러를 만들어야 한다.

@Controller
public class HomeController {
    @GetMapping("/index2")
    public String showIndex2() {
        return "index2";
    }
}

이후 localhost:8080/index2로 접근하면 index2.html이 보일 것입니다.

만약, localhost:8080/index2.html로 접근하면 error 페이지가 나온다.

Spring Boot는 templates 폴더의 파일을 직접 URL로 접근할 수 있는 정적 리소스로 취급하지 않는다.
따라서 해당 URL에 맞는 컨트롤러 매핑이 없으면 404 에러가 발생하고, 기본 에러 페이지가 표시된다.

요약

static 폴더

  • 정적 파일 직접 제공 (CSS, JavaScript, 이미지 등)

templates 폴더

  • 동적 콘텐츠를 위한 템플릿 파일 (컨트롤러를 통해 접근)

이러한 구조는 보안과 유연성을 위해 설계되었다. 동적 콘텐츠는 서버 측 로직을 통해 제어되어야 하므로, 직접 접근을 제한하고 컨트롤러를 통해 접근하도록 한다.

HttpServletResponse로 응답하기

Tomcat이 response 객체를 생성해서 준다. 요청 시 사용할 수 있는 객체, 응답에 사용할 수 있는 객체가 있다.

응답에 필요한 출력 스트림을 담아서 준다.

response.getWriter()

  • 출력 스트림 얻어온다.
  • 응답을 얻을 수 있다.
response.setStatus(200);
response.setContentType("text/html");

PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head></head>");
out.println("<body>Monday</body>);
out.println("</html>");
out.close();

톰캣은 응답의 헤더를 기본적으로 만들어준다. 헤더는 변경과 확장이 가능하다.

HttpServletResponse의 메서드

메서드설명
void addCookie(Cookie cookie)응답에 쿠키를 추가
void addHeader(String name, String value)응답 헤더를 추가
String getHeader(String name)
void setHeader(String name, String value)
지정된 이름의 헤더의 값을 반환
지정된 이름의 헤더의 값을 변경
void sendRedirect(String location)지정된 위치(URL)로 요청하라는 응답(3xx)를 전송
ServletOutputStream getOutputStream()
PrintWriter getWriter()
클라이언트(브라우저)와 연결된 출력 스트림을 반환

코드의 분리

  1. 관심사
  2. 변하는 것과 변하지 않는 것
  3. 중복 코드 - 별도의 메서드로 만들어 제거해야 함.

OOP - 변경에 대비

객체 지향 설계 5대 원칙

  1. 단일 책임 원칙(SRP)
    하나의 메서드는 하나의 책임(Concern=관심사=작업)만 가진다.
  2. 개방 폐쇄 원칙(OCP)
    상속 Open, 변경 Closed
  3. 리스코프 치환 원칙(LSP)
  4. 인터페이스 분리 원칙(ISP)
  5. 의존관계 역전 원칙(DIP)
  • 코드가 너무 구체적이면 변경에 불리
  • "추상화" 통해 변경에 유리

Spring의 Reflection API

@Controller
public void date(@HttpservletRequest request) {

	request.getParameter("year");
	request.getParameter("month");
	request.getParameter("day");
}

위의 코드를 아래처럼 바꿀 수 있다.

@Controller
public void date(int year, int month, int day) {
}

각 매개변수에는 @RequestParam이 생략되어 있다.

DispatcherServlet

  1. 입력 & 변환
  • request.getParameter()
  • String -> int
  1. 모델 생성
  • Controller 메서드에 Model을 선언하면 DispatcherServlet이 모델 객체를 생성해서 넘겨줌. (객체의 주소를 줌)
  • 뷰에 Model이 넘어오면 이를 사용할 수 있다.
  • 작업 결과를 보여줄 View의 이름을 반환
  • 매개변수가 참조형일 경우 원본 객체를 수정할 수가 있다.

컨트롤러 메서드의 반환타입

  1. String
    뷰 이름을 반환
@GetMapping("/getYoil")
pubic String main(int year, int month, int day) {

	return "yoil" // resources/templates/yoil.html
}
  1. void
    맵핑된 url의 끝단어가 뷰 이름
@GetMapping("/yoil") //resources/templates/yoil.html
public void main(int year, int month, int day) {
}
  • JSP 방식

@RestContoller와 @Controller의 차이점은?

@RestController 어노테이션은 메소드의 반환값을 HTTP 응답 본문으로 직접 전송한다. Thymeleaf 템플릿을 사용하려면 @Controller 어노테이션을 사용해야 한다.

@RequestParam과 @ModelAttribute

@ModelAttribute

  • 적용 대상을 Model의 속성으로 자동 추가해준다.
  • 반환 타입 또는 컨트롤러 메서드의 매개변수에 적용 가능
  • Controller 메서드 파라미터에서 참조형 앞에 사용할 경우 어노테이션 생략 가능
  1. 참조형에만 사용 가능하다.
    ex. int, char 등 사용 불가하다.

  2. 메서드 앞에 사용

@ModelAttribute("yoil")
    private char getYoil(MyDate myDate) {
		/*** 생략 ***/
        char yoil = '일';
        return yoil;
    }
  • 이 메서드의 반환값(yoil)을 "yoil"이라는 이름으로 모델에 자동으로 추가한다.
  • 모든 요청 처리 전에 이 메서드가 실행된다. 따라서,
    char yoil = getYoil(myDate);을 추가할 필요가 없다. 또한, main 메서드가 실행될 때 모델에는 이미 "yoil" 속성이 추가되어 있다.
  1. 파라미터 앞에 사용
@Controller
public class YoilTeller {
    @RequestMapping("/yoil")
    public String getYoil(@ModelAttribute MyDate myDate, Model model) {

        return "yoil";
}
  • Spring은 요청 파라미터를 MyDate 객체에 자동으로 바인딩한다.
    예) "/yoil?year=2023&month=7&day=20" 요청이 오면, myDate 객체의 각 필드에 값이 설정된다.
  • 생략되는 코드
model.addAttribute("myDate", date);
  • Model model
    뷰에 전달할 데이터를 담는 객체
    *** Model은 스프링이 자동으로 주입하는 객체이므로 @ModelAttribute 어노테이션을 붙일 필요가 없다.
@Controller
public class YoilTeller {
    @RequestMapping("/yoil")
    public String main(@ModelAttribute MyDate date, Model model) throws IOException {

        return "yoil";
    }

    @ModelAttribute("yoil")
    private char getYoil(MyDate date) {
        Calendar cal = Calendar.getInstance(); // 현재 날짜와 시간을 갖는 cal
        cal.clear(); // cal의 모든 필드를 초기화
        cal.set(date.getYear(), date.getMonth() - 1, date.getDay());

        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        char yoil = "일월화수목금토".charAt(dayOfWeek - 1);
        return yoil;
    }
}

** 이때, 컨트롤러 메서드의 매개변수가 기본형일 경우 @RequestParam이 생략된 것이며, 참조형일 경우 @ModelAttribute가 생략된 것이다.

WebDataBinder의 역할

@RequestMapping("getYoil")
public String main(@ModelAttribute MyDate date, BindingResult result) {
}

URL -> getYoil?year=2023&month=1&day=2

Map<String, String>

namevalue
"year""2023"
"month""1"
"day""2"
  1. 타입 변환
  • string -> int
  1. 데이터 검증(validator)

MyDate

yearmonthday
202312

@RequestParam

  • 요청의 파라미터를 연결할 때 매개변수에 붙이는 애너테이션
  1. HttpServletRequest

서블릿(Servlet)은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 HTTP 메시지를 대신 파싱한다.
이렇게 파싱된 메시지를 HttpServletRequest 객체에 담아서 제공하는 것이다.
즉, HttpServletRequest는 서블릿이 HTTP 요청 메시지를 파싱한 결과를 담은 객체이다.
HttpServletRequest를 사용하면 HTTP 요청 메시지를 편리하게 조회할 수 있게 된다.

@RequestMapping("/requestParam")
public void main(HttpServletRequest request) {
	String year = request.getParameter("year");
}
  1. Reflection API
@RequestMapping("/requestParam")
public void main(@RequestParam String year) {
}
  • request.getParameter() 필요 없어짐.
  • 여기서 더해, @RequestParam도 생략하는 것이 좋다.
    • @RequestParam(name="year", required=true)
    • year이 필수값이 된다.
  • @RequestParam이 붙었다면 반드시 client에서 year 정보가 넘어와야 한다.
  • http://localhost/requestParam
    • 400 Bad Request
  • http://localhost/requestParam?year
    • 빈 문자열로 처리되어 값이 넘어온 것으로 본다.
  1. WebDataBinder 덕분에 .. @RequestParam도 생략 가능
@RequestMapping("/requestParam")
public void main(String year) {
}

브라우저를 통해서 요청받은 값이 실제 서버의 객체에 바인딩 될 때, 중간 역할을 한다.
1. 타입변환
2. 데이터 검증

➡️ 변환 결과나 에러는 BindingResult에 저장

  1. WebDataBinder의 타입 변환
@RequestMapping("/requestParam")
public void main(int year) {
}
@RequestMapping("/requestParam")
public void main(@RequestParam(required=false, defaultValue="1") int year) {
}
  • default value를 설정함으로써 사용 가능

로그인 화면 만들기

@RequestMapping, @GetMapping, @PostMapping

  • @RequestMapping의 default method는 GET이다.
@Controller
@RequestMapping
public class LoginController {

    @RequestMapping("/login/login", method = RequestMethod.GET)
    public String login(){
        return "login";

}
@Controller
@RequestMapping
public class LoginController {
		
    @GetMapping("/login/login")
    public String login(){
        return "login";

}

두 코드는 동일하다. URL이 같아도 method가 다르면 서버가 요청을 구별할 수 있다.

만약, 하나의 메서드로 GET, POST를 둘 다 처리하는 경우에는 @RequestMapping으로 사용해야 한다.

@Controller
@RequestMapping
public class LoginController {

    @RequestMapping("/login/login", method = {RequestMethod.GET, RequestMethod.POST})
    public String login(){
        return "login";

}

클래스에 붙이는 @RequestMapping
맵핑될 URL의 공통 부분을 @RequestMapping으로 클래스에 적용

@Controller
@RequestMapping("/login")
public class LoginController {

    @PostMapping("/login")
    public String login(){
        return "login";

}

URL Encoding
URL에 포함된 non-ASCII 문자를 문자 코드(16진수) 문자열로 변환

String msg = URLEncoder.encode("id 또는 pwd가 일치하지 않습니다", "utf-8");

Redirect

@GetMapping

return "redirect:/login/login";

url 다시쓰기

  • URL을 변경하는 것.
  • 주로 URL에 쿼리 스트링을 추가
String msg = "id 또는 pwd가 일치하지 않습니다";
return "redirect:/login/login?msg="+msg;
  • queryString으로 다른 정보를 추가
  • 이때 한글이 포함되어 있다면 URL 인코딩을 직접 처리
  • 브라우저를 통해 입력한 경우 ASCII가 아닌 문자가 포함되어 있다면 수동으로 처리해야 한다.

ThymeLeaf 엔진

  1. model
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="|id=${id}|">asdf</h1>
<h1 th:text="|pwd=${pwd}|">1234</h1>
</body>
</html>

json 이외의 형식이 들어가려면 | |로 감싸주어야 한다.

  1. query parameter
<h1 th:text="${param.msg}"></h1>
profile
Backend Developer

0개의 댓글