1. 클라이언트와 서버
클라이언트는 서버에 정보를 요청하는 사용자이다. 그리고 서버는 사용자에게 요청받은 정보를 제공하는 관리자이다.
클라이언트의 요청이 서버에 제대로 전달 되려면 일종의 '주소'가 필요한데 그게 바로 IP주소이다. 네트워크 상에서 데이터의 송/수신은 IP주소를 기준으로 한다.
2. 웹 서버
웹 서버는 클라이언트의 요청을 웹 페이지 형태로 응답해주는 서버이다.
클라이언트는 웹 브라우저를 통해 HTTP Request 형태로 요청한다. 이후 웹 서버는 HTTP Response형태로 웹 사이트 데이터를 전송한다.
마지막으로 웹 브라우저는 받아온 데이터를 바탕으로 화면에 출력한다.
3. API와 REST(Representational State Transfer)ful API
API는 개발자와 소프트웨어간의 통신을 위해 따라야 하는 일종의 규칙이다. 손님은 메뉴판에 젹혀 있는 메뉴만 웨이터에게 주문을 할 수 있듯이 개발자는 정해진 API를 통해서만 소프트웨어와 데이터를 주고받을 수 있다.
RESTful API의 REST는 Representational State Transfer의 약자로 일종의 소프트웨어의 아키텍쳐의 형식 중 하나인데, 소프트웨어의 모든 자원을 이름으로 구분하여 해당 자원의 상태(정보)를 주고받는 것을 말한다.
RESTful API는 URI에 자원의 이름을 표시하고 정해진 이름(GET, POST, PUT, DELETE)를 통해 자원을 주고받는 API라고 할 수 있다.
4. 웹 서버와 웹 애플리케이션 서버
웹 서버는 클라이언트의 요청을 HTTP의 형태로 제공하는 서버이고 대표적인 예시로 Apache, Nginx 등이 있다. 그런데 웹 서버는 정적인 데이터만 제공할 수 있다. 예를 들어 서버에 미리 저장되어 있는 뉴스같은건 제공할 수 있지만 뉴스에 달린 코멘트는 실시간으로 변하기 때문에 웹 서버 만으로는 제공하기 어렵다.
웹 애플리케이션 서버(이하 WAS)는 웹 서버로 제공하기 어려운 동적인 데이터를 제공할 수 있다. WAS의 대표적인 예시로는 Tomcat, JBoss등이 있다.
Apache Tomcat은 WAS로 분류되지만 Apache의 웹 서버기능도 포함되어 있어 정적인 데이터도 제공함과 동시에 동적인 데이터를 제공할 수 있다.
5. Spring과 Spring Boot
앞으로 배우게 될 Spring을 간단히 소개하자면 Java(최근엔 Kotiln도 지원)언어를 기반으로 두고 웹 애플리케이션을 개발할 수 있는 프레임워크이다.
그리고 Spring Boot라는 프레임워크가 새로 등장했는데 Spring Boot는 Spring의 단점인 무겁고 복잡한 xml을 이용한 설정 대신 Java의 애너테이션을 활용한 간단한 설정이 가능하고 maven이나 gradle을 이용한 의존성 관리가 쉬워지는 등 기존의 단점을 개선한 프레임워크이다. 그리고 Spring Boot는 Tomcat을 의존성 관리로 간단하게 추가할 수 있어 별도로 Tomcat을 설치할 필요가 없다는 장점도 있다.
6. HTTP(HyperText Transfer Protocol)
http는 월드 와이드 웹에서 데이터를 주고 받는 양식을 뜻하는 '통신 규약'중 하나이다. http는 어떤 통신 규약인가 하면 하이퍼 텍스트(html 파일)를 주고 받는 통신 규약이다.
http가 데이터를 주고 받는 방법에 대한 설명이다.
1. 브라우저(클라이언트)는 서버에 자신이 원하는 정보(페이지)를 요청(request)한다. 이걸 request데이터라고 부른다.
2. 서버는 클라이언트가 요청한 정보에 맞는 데이터를 응답해준다. 없다면 해당 데이터는 존재하지 않는다는 의미의 데이터를 보낸다. 이걸 response데이터라고 부른다.
3. 브라우저는 응답받은 데이터를 바탕으로 화면에 출력한다.
http가 주고받는 데이터에 대해 좀 더 자세히 이야기를 해보도록 하겠다.
http가 주고받는 데이터의 구조는 크게 header, payload 두 가지로 나눌 수 있다.
header는 데이터에 관련된 정보들을 주로 담는다.
header에는 주로 어떤 형식의 데이터를 요청했는가(request method), 요청받은 데이터를 찾았는가(status code), 어떤 형식으로 데이터를 보낼 것인가(content type)에 관련된 정보들이 주로 저장된다.
request method에는 GET, POST, PUT, DELETE등이 있고 그외에도 여러가지가 있다. GET은 리소스를 받을 때 사용하는 타입의 메소드다.
POST는 리소스를 서버에 전송할 때 사용하는 메소드다.
PUT은 서버에 저장된 기존의 리소스를 수정할 때 사용하는 메소드고, DELETE는 리소스를 삭제할 때 사용하는 메소드다.
status code는 쉽게 말해서 요청의 처리 결과를 세 자리의 숫자로 나타낸 것이다. status code는 여기서 다루기에는 종류가 너무 많아서 생략하겠다.
payload에는 실질적으로 담겨있는 데이터의 내용이다. 요청할 때도 payload에 담아서 보낼 수 있고, 요청에 응답할 때에도 담아서 보낼 수 있다.
실제 저장된 header 예시이다. |
실제 저장된 payload 예시이다. |
7. Spring MVC
MVC는 Model-View-Controller의 약자로, 소프트웨어 디자인 패턴중 하나이다.
MVC패턴은 소프트웨어를 Model, View, Controller 3개의 기능으로 구분해서 각각의 역할을 분류한다.
Model: 데이터와 비즈니스 로직을 담당한다. 데이터베이스로 부터 데이터를 저장하고 불러오는 등의 작업을 수행한다.
View: 사용자 인터페이스이다. 사용자가 보는 화면과 폼(Form), 버튼등을 디자인하고 구현한다.
Controller: Model과 View사이의 상호작용을 조정하고 제어한다. 사용자의 입력을 Model 에 전달하고 Model 의 결과를 바탕으로 View에 업데이트한다.
Spring Web MVC는 Servlet API를 기반으로 구축된 독창적인 웹 프레임워크로, 처음부터 Spring Framework에 포함되어 왔으며, 정식 명칭인 "Spring Web MVC"는 소스 모듈(spring-webmvc)의 이름에서 따왔으나, "Spring MVC"로 더 일반적으로 알려져 있습니다. … Spring MVC는 중앙에 있는 DispatcherServlet이 요청을 처리하기 위한 공유 알고리즘을 제공하는 Front Controller 패턴을 중심으로 설계되어 있으며 이 모델은 유연하고 다양한 워크 플로우를 지원합니다.
출처:https://docs.spring.io/spring-framework/reference/web/webmvc.html
요약하자면 Spring에서는 Spring만의 MVC모델을 채용해서 HTTP요청을 효율적으로 처리하고 있다는 뜻이다.
다음은 서버의 요청을 서블릿(Servlet)이 요청을 처리하는 과정이다.
Servlet (서블릿)은 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램을 의미한다.
1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 한다.
2. 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse 객체를 생성한다.
HttpServletRequest, HttpServletResponse 객체는 약속된 HTTP의 규격을 맞추면서 쉽게 HTTP에 담긴 데이터를 사용하기 위한 객체이다.
3. 설정된 정보를 통해 어떤 Servlet에 대한 요청인지 찾는다.
4. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet 혹은 doPost 등의 메서드를 호출한다.
5. 호출한 메서드들의 결과를 그대로 반환하거나 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환한다.
6. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse 객체가 소멸된다.
Spring은 DispatcherServlet을 사용하여 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리하고 있다.
다음은 Front Controller 패턴의 동작과정이다.
1. Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석한다.
2. DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달해 준다.
3. 해당 Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Model')와 'View' 정보를 전달한다.
4. ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달한다.
8. Controller에 대해
여기 회원관리 API가 있다고 하자. 만약 Spring MVC에 Front Controller 패턴이 적용되어 있지 않다면 저 4개의 api를 구현하기 위해 3개의 클래스를 다 따로 구현해야할 것이다.
@WebServlet(urlPatterns = "/user/login")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
@WebServlet(urlPatterns = "/user/logout")
public class UserLogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
@WebServlet(urlPatterns = "/user/signup")
public class UserSingUpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
하지만 Spring MVC의 Front Controller 패턴은 이렇게 3개의 클래스를 따로 구현할 필요가 없도록 만들었다.
Spring에서는 하나의 컨트롤러 파일에 모든 API를 만드는 것도 가능하다.(물론 모든 api를 한 파일에 쑤셔넣지는 않고 비슷한 성격의 api를 나눠서 모아놓는다.)
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/login")
public String login() {
// ...
}
@GetMapping("/logout")
public String logout() {
// ...
}
@GetMapping("/signup")
public String signup() {
// ...
}
@PostMapping("/signup")
public String registerUser(SignupRequestDto requestDto) {
// ...
}
}
위의 코드처럼 컨트롤러를 만들고 싶으면 클래스 앞에 @Controller 애너테이션을 붙이면 Spring에서는 이 클래스를 컨트롤러의 역할을 수행하도록 처리한다.
컨트롤러에 api를 구현할 때 GET, POST, PUT, DELETE중 어느 타입의 메소드인지 구분할 필요가 있다. Spring에서는 각각 @GetMapping @PostMapping @PutMapping @DeleteMapping 애너테이션을 붙이면 메소드의 타입을 정할 수 있다.
@GetMapping("/api/get")
@ResponseBody
public String get() {
return "GET Method 요청";
}
@PostMapping("/api/post")
@ResponseBody
public String post() {
return "POST Method 요청";
}
@PutMapping("/api/put")
@ResponseBody
public String put() {
return "PUT Method 요청";
}
@DeleteMapping("/api/delete")
@ResponseBody
public String delete() {
return "DELETE Method 요청";
}
위의 코드처럼 url이 중복되는 부분이 있을 수 있다. 그럴 때는 @RequestMapping으로 중복되는 url을 단축할 수 있다.
@Controller
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "Hello World!";
}
@GetMapping("/get")
@ResponseBody
public String get() {
return "GET Method 요청";
}
@PostMapping("/post")
@ResponseBody
public String post() {
return "POST Method 요청";
}
@PutMapping("/put")
@ResponseBody
public String put() {
return "PUT Method 요청";
}
@DeleteMapping("/delete")
@ResponseBody
public String delete() {
return "DELETE Method 요청";
}
}
9. 정적 페이지와 동적 페이지
Spring에서 프로그램을 실행하고 정적인 페이지를 요청하고 싶으면 url에 파일이름을 치면 바로 반환해준다.
만약 컨트롤러에서 정적인 페이지를 호출하고 싶다면
@GetMapping("/html/redirect")
public String htmlStatic() {
return "redirect:/hello.html";
}
redirect:를 붙이면 정적인 페이지로 리다이렉트 해준다.
Spring의 컨트롤러는 기본적으로 templates폴더의 동적인 페이지, 즉 Thymeleaf템플릿이 적용된 동적인 페이지를 호출한다.
private static long visitCount = 0;
...
@GetMapping("/html/dynamic")
public String htmlDynamic(Model model) {
visitCount++;
model.addAttribute("visits", visitCount);
return "hello-visit";
}
1. Client 의 요청을 Controller에서 Model 로 처리한다.
- DB 조회가 필요하다면 DB 작업 후 처리한 데이터를 Model에 저장한다.
2. Template engine(Thymeleaf) 에게 View, Model 전달한다.
- View: 동적 HTML 파일
- Model: View 에 적용할 정보들
3. Template engine: View에 Model을 적용 → 동적 웹페이지 생성
4. Client(브라우저)에게 View(동적 웹 페이지)를 전달 한다.
10. Spring에서 JSON데이터 반환하기
Spring에서는 기본적으로 문자열을 반환하면 templates폴더의 html파일을 반환한다. 만약 html파일이 아니라 JSON데이터를 반환하고 싶으면 @ResponseBody 애너테이션을 추가해야 한다.
@GetMapping("/response/json/string")
@ResponseBody
public String helloStringJson() {
return "{\"name\":\"Robbie\",\"age\":95}"; // string 문자열이 JSON 데이터로 변환되서 반환된다.
}
@GetMapping("/response/json/class")
@ResponseBody
public Star helloClassJson() {
return new Star("Robbie", 95); // 객체를 JSON데이터로 변환할 수도 있다.
}
한 컨트롤러의 모든 api가 JSON데이터를 반환해야 한다면 일일이 @ResponseBody를 붙이지 않고도 해결할 수 있는 방법이 있다. 컨트롤러의 클래스 앞에 @RestController 애너테이션을 붙이면 된다.
@RestController // RestController: 데이터를 값 그 자체로 반환한다. 모든 api에 @ResponseBody포함
// Controller: thymeleaf template 뷰를 반환
// 객체를 반환하면 json 타입으로 출력한다.
@RequestMapping("/response/rest")
public class ResponseRestController {
// [Response header]
// Content-Type: text/html
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/string")
public String helloStringJson() {
return "{\"name\":\"Robbie\",\"age\":95}";
}
// [Response header]
// Content-Type: application/json
// [Response body]
// {"name":"Robbie","age":95}
@GetMapping("/json/class")
public Star helloClassJson() {
return new Star("Robbie", 95);
}
}
11. Jackson
Jackson은 JSON데이터 구조를 처리하는 라이브러리이다. Object객체를 JSON으로, 반대로 JSON을 Object객체로 변환할 수 있다.
Spring은 3.0버전 이후로 Jacskon과 관련된 API를 제공함으로써, 우리가 직접 소스 코드를 작성하여 JSON 데이터를 처리하지 않아도 자동으로 처리해준다.
직접 JSON데이터를 처리하고 싶으면 Jackson 라이브러리의 ObjectMapper를 사용하면 된다.
public class JacksonTest {
@Test
@DisplayName("Object To JSON : get Method 필요")
void test1() throws JsonProcessingException {
Star star = new Star("Robbie", 95);
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
String json = objectMapper.writeValueAsString(star);
System.out.println("json = " + json);
}
@Test
@DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
void test2() throws JsonProcessingException {
String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String
ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
Star star = objectMapper.readValue(json, Star.class);
System.out.println("star.getName() = " + star.getName());
System.out.println("star.getAge() = " + star.getAge());
}
}
12. Spring에서 서버에 데이터를 보내는 방식
1. Path Variable
서버에 데이터를 보내기 위해 url에 값을 추가해서 보내는 방식이다.
// [Request sample]
// GET http://localhost:8080/hello/request/star/Robbie/age/95
@GetMapping("/star/{name}/age/{age}")
@ResponseBody
public String helloRequestPath(@PathVariable String name, @PathVariable int age)
{
return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
}
2. RequestParam
보통 가장 많이 사용하는 방법으로 Request데이터에 매개변수를 추가한다.
// [Request sample]
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
@GetMapping("/form/param")
@ResponseBody
public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}
추가로 GET방식의 메소드에서는 url의 끝에 RequestParameter가 추가되서 보내진다.
(예시: http://localhost:8080/hello/request/form/param?name=Robbie&age=95)
// [Request sample]
// POST http://localhost:8080/hello/request/form/param
// Header
// Content type: application/x-www-form-urlencoded
// Body
// name=Robbie&age=95
@PostMapping("/form/param")
@ResponseBody
public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}
POST방식에서는 url이 아닌 Request데이터의 body에 추가되서 보내진다.
// [Request sample]
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
@GetMapping("/form/param")
@ResponseBody
public String helloGetRequestParam(@RequestParam(required = false) String name, int age) {
return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}
@RequestParam뒤에 required라는 변수를 넣으면 필수 여부를 정할 수 있다. 만약 required=false로 하면 해당 값이 없어도 오류가 발생하지 않는다.
13. HTTP데이터를 객체로 처리하는 법
@RequestParam이나 @PathVariable은 한두개의 값정도 전송하는건 상관없지만 값이 많아지면 이 둘론 부담스러울 것이다.
@ModelAttribute는 Java 객체를 query string(?name=Robbie&age=95)형태의 데이터로 받아올 수 있다.
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}
@RequestBody는 @ModelAttribute와 달리 JSON형태로 데이터를 반환한다.
// [Request sample]
// POST http://localhost:8080/hello/request/form/json
// Header
// Content type: application/json
// Body
// {"name":"Robbie","age":"95"}
@PostMapping("/form/json")
@ResponseBody
public String helloPostRequestJson(@RequestBody Star star) {
return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
}
'내일배움캠프' 카테고리의 다른 글
Spring 숙련주차 Part1 (0) | 2024.05.22 |
---|---|
Spring 입문주차 2주차 (0) | 2024.05.21 |
Spring 일정관리 앱 프로젝트 2일차 (0) | 2024.05.16 |
Spring 일정관리 앱 프로젝트 1일차 (0) | 2024.05.14 |
정수 내림차순으로 배치하기 (0) | 2024.05.13 |