Last update: @2/14/2023
주의
본 포스팅은 인프런 강의를 통해 학습한 내용을 임의로 요약한 것으로 일부 내용의 오류 및 누락, 링크 숨김 등이 존재합니다.
웹 어플리케이션 이해
•
웹 시스템 구성 - WEB Server, WAS, DB로 분리
◦
웹서버
▪
정적 리소스 처리
▪
단순해서 잘 죽지 않음
▪
WAS, DB 장애 시 오류 화면 제공 가능
◦
WAS는 동적인 처리
◦
DB는 데이터 저장 및 관리
•
만약 화면 제공이 아닌 API 서비스만 제공하면 굳이 웹서버가 없이 WAS만 구축해도 됨
HTTP, HTTP API, CSR, SSR
•
HTTP
◦
정적인 HTML 페이지 전달
◦
SSR(Server Side Rendering)
▪
서버에서 동적으로 HTML 파일을 생성(rendering)해서 전달
▪
JSP, Thymeleaf, Freemarker, Velocity 등
•
HTTP API
◦
HTML이 아니라 데이터를 전달(주로 JSON)
◦
CSR(Client Side Rendering)
▪
클라이언트에서 HTTP API로 받은 데이터를 통해 동적으로 HTML 파일을 생성
▪
React, Vue.js, Angular 등
◦
서버 to 서버 통신에 사용
서블릿
•
서블릿의 기능 - HTTP 스펙을 편리하게 사용하도록 도와줌
◦
서버 TCP/IP 대기, 소켓 연결
◦
HTTP 요청 메시지 파싱
▪
시작 라인 파싱
▪
헤더 파싱
▪
바디 파싱
◦
저장 프로세스 실행 → request, response 객체 생성
◦
[사용자의 비즈니스 로직]
◦
HTTP 응답 메시지 작성
▪
시작 라인 생성
▪
헤더 생성
▪
메시지 바디 생성
◦
TCP/IP에 응답 전달, 소켓 종료
•
서블릿 컨테이너
◦
톰캣과 같이 서블릿을 지원하는 WAS를 뜻함
◦
서블릿 객체의 라이프사이클(생성, 초기화, 호출, 종료)을 관리 - 싱글톤으로 관리
◦
동시 요청을 위한 멀티쓰레드 처리 지원(개발자는 신경 안 써도 됨)
▪
요청 하나에 쓰레드 하나 할당
▪
쓰레드를 미리 생성해놓고 쓰레드 풀에서 가져다 씀
▪
이 쓰레드 풀의 최대치(max thread)가 WAS 튜닝의 포인트
•
너무 낮으면 리소스는 여유, 클라이언트는 응답 지연
•
너무 많으면 리소스 부족
•
아파치 ab, 제이미터, nGrinder 등 성능 테스트 툴로 성능 실험 필요
•
하나의 매핑에 하나의 서블릿호출 - service() 메서드 → 기본값은 doGet(), doPost() 메서드 호출, 재정의 가능
서블릿 기본 기능 - Request
•
기본 조회
◦
start line 정보 조회
▪
method, protocol, shceme, requestRL/URI, query string, secure)
◦
헤더 정보 조회
▪
host, accept-language, cookie, content-type 등
◦
기타 정보 조회
▪
remote host/address/remote port, local name/address/port
•
요청 데이터 사용
◦
파라미터 형식 - Get(query string), Post(content-type: application/x-www-form-urlencoded)
▪
getParameter(), getParameters(), getParameterNames()
◦
메시지 바디 형식 - Post, Put, Patch
▪
content-type: text/plain
•
request.getInputStream()
▪
content-type: application/json
•
request.getInputStream()
•
위에서 받은 inputStream을 ObjectMapper(jackson)로 파싱
ObjectMapper objectMapper = new ObjectMapper();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
Java
복사
서블릿 기본 기능 - Response
•
Http 응답코드 지정
response.setStatus(HttpServletResponse.SC_OK);
Java
복사
•
응답 헤더 설정
response.setHeader("Content-Type", "text/plain;charset=utf-8");
Java
복사
◦
편의 기능
▪
컨텐츠 타입
//response.setHeader("Content-Type", "text/plain;charset=utf-8");
// response.setContentLength(2);
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8")
Java
복사
▪
쿠키
// response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600); //600초
response.addCookie(cookie);
Java
복사
▪
리다이렉트
//response.setStatus(HttpServletResponse.SC_FOUND); //302
//response.setHeader("Location", "/basic/hello-form.html"
response.sendRedirect("/basic/hello-form.html");
Java
복사
•
응답 바디 생성
◦
단순 텍스트
PrintWriter writer = response.getWriter();
writer.println("<html>");
Java
복사
◦
JSON
ObjectMapper objectMapper = new ObjectMapper();
HelloData helloData = new HelloData("kim", 20);
String result = objectMapper.writeValueAsString(helloData);
response.getWriter().write(result);
Java
복사
JSP(Java Server Page)와 MVC
•
write() 노다가의 어려움을 극복하기 위한 템플릿 엔진 JSP의 등장
•
JSP에 HTML과 자바 코드가 섞이면서 개발 및 유지보수가 힘들어짐
•
그래서 View 로직을 담당하는 JSP와 비즈니스 로직을 담당하는 서블릿을 나눔
◦
이후에 데이터를 실어나르는 Model까지 추가한 Model-View-Controller 패턴 등장(model 1)
◦
이후 컨트롤러에서 웹 계층을 다루는 컨트롤러와 비즈니스 로직을 다루는 서비스/리포지토리 계층으로 나눈 패턴이 등장(model 2)
•
MVC 패턴의 한계
◦
View로 이동하는 forward 코드의 중복
◦
ViewPath 중복 - 절대경로와 확장자가 중복되고, 변경이 힘듦
◦
사용하지 않는 파라미터가 생김(request, response 등)
◦
테스트 작성이 힘듦
◦
공통 로직의 처리가 어려움
스프링 MVC의 진화 과정
•
컨트롤러간의 공통 로직을 처리를 하는 수문장(front controller)을 두는 것이 프론트 컨트롤러 패턴임
◦
스프링 웹 MVC의 DispatcherServlet이 이 FrontController 역할
1.
매핑 정보를 조회해서 해당 컨트롤러를 호출 → 컨트롤러에서 View 호출
2.
컨트롤러가 반환하는 View 객체를 통해 뷰 렌더링
3.
View Resolver 및 Model 도입으로 컨트롤러는 논리 경로만 반환하고 resolver에서 물리 경로로 변경
•
이 시점에서 컨트롤러는 서블릿을 전혀 사용하지 않아 단순해지고 테스트가 쉬워짐
4.
컨트롤러가 ModelView가 아닌 뷰 논리 경로를 단순 문자열로 반환
5.
파라미터와 반환값이 제각각인 여러 컨트롤러를 지원하기 위한 어댑터 목록 추가
•
이 시점에 어댑터 덕분에 컨트롤러가 아니라 어떤 클래스든 URL에 매핑해서 사용할 수 있게 됨. 따라서 이름도 더 넓은 핸들러(handler)로 변경됨
•
각 어댑터는 핸들러가 요청하는 파라미터를 건내주고 핸들러가 반환하는 값을 통해 요청을 처리함. 이후 모든 어댑터는 공통적으로 ModelView를 반환하도록 통일함
▪
위의 과정을 거쳐 탄생한 것이 스프링 MVC 구조
•
스프링 MVC의 큰 강점은 DispatcherServlet의 코드 변경 없이 기능의 변경 및 확장이 가능한 것
스프링 MVC의 주요 인터페이스 및 기본 구현체
•
핸들러 매핑
org.springframework.web.servlet.HandlerMapping 인터페이스
◦
자동 등록 및 우선순위
0 = RequestMappingHandlerMapping -> @RequestMapping 애노테이션으로 핸들러를 찾음
1 = BeanNameUrlHandlerMapping -> 스프링 빈의 이름으로 핸들러를 찾음
...
Java
복사
•
핸들러 어댑터
org.springframework.web.servlet.HandlerAdapter 인터페이스
0 = RequestMappingHandlerAdapter -> @RequestMapping 애노테이션이 붙은 핸들러 처리
1 = HttpRequestHandlerAdapter -> HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter -> Controller 인터페이스를 구현한 컨트롤러 처리(과거)
...
Java
복사
•
뷰 리졸버
org.springframework.web.servlet.ViewResolver 인터페이스
1 = BeanNameViewResolver -> 빈 이름으로 뷰를 찾아서 반환 (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver -> JSP를 처리할 수 있는 뷰를 반환
Java
복사
•
뷰
org.springframework.web.servlet.View 인터페이스
스프링 MVC 사용하기
•
컨트롤러 클래스 등록
◦
@Controller → @Component 포함
▪
반환값이 String이면 뷰 이름으로 인식
◦
@RestController
▪
반환값이 String이면 Http 메시지 바디에 바로 입력
•
URL 매핑
◦
매핑 - 컨트롤러 클래스 레벨에@RequestMapping("공통 URL 경로")
▪
스프링 부트 3.0(스프링 6.0) 이전에는 클래스 레벨에 @RequestMapping만 붙여도 컨트롤러로 등록이 됐지만 이상 버전에서는 꼭 @Controller를 붙여줘야 컨트롤러로 등록됨
◦
매핑 - 메서드 레벨에 다음 어노테이션 부착
▪
@RequestMapping("URL경로") → Get + Post 요청 모두 처리
•
@GetMapping("URL경로")
◦
=@RequestMapping(value = "/hello, method = RequestMethod.GET)
•
@PostMapping("URL경로")
◦
= @RequestMapping(value = "/hello", method = RequestMethod.POST)
•
그 외 @PutMapping, @DeleteMapping, @PatchMapping 등이 있음
◦
최종 매핑은 클래스 레벨의 매핑 + 메서드 레벨 매핑으로 조합한 URL에 매핑됨
◦
메서드 레벨에 URL 경로를 생략하면 클래스 레벨의 URL을 받는 메서드가 됨
◦
다중 매핑 가능함{"URL1", "URL2"}
◦
url 마지막 슬래시(/) 관련
▪
스프링 부트 3.0 이전 - 떼고 인식
•
/hello , /hello/ 요청 → /hello로 매핑
▪
스프링 부트 3.0 이후 - 붙인 것과 뗀 것 구분하여 인식
•
/hello 요청 → /hello로 매핑
•
/hello/ 요청 → /hello/로 매핑
•
각종 요청 매핑(path variable, parameter, consumes, produces 등)
◦
HTTP 요청 데이터 받기 - 헤더
•
HttpServletRequest request
HttpServletResponse response
HttpMethod httpMethod // enum 타입
Locale locale
@RequestHeader MultiValueMap<String, String> headerMap // 한 헤더에 할당된 여러 값들을 List로 반환
@RequestHeader("host") String host // 특정 HTTP 헤더를 조회
@CookieValue(value = "myCookie", required = false) String cookie // default value도 설정 가능
Java
복사
HTTP 요청 데이터 받기 - 파라미터
•
메서드 내부에서 request.getParamter(key) 사용
•
파라미터를 변수로 받기 - @RequestParam(key) 사용
◦
매개변수명 같으면 key 생략 가능
◦
String, int 등의 단순 타입이면 @RequestParam도 생략 가능(단, required=false 적용)
◦
옵션
▪
required - 값 필수 여부(없으면 오류 발생)
•
true가 기본값
•
빈 문자열("")은 값이 있는 것이니 주의
▪
default - 값이 없을 경우 초기값
◦
Map<String, Object>에 담에서 받을 수 있음
▪
만약 파라미터 하나당 2개 이상의 값이 예상되면 MultiValueMap 사용(key 하나에 value 여러개)
•
파라미터를 객체로 받기 - @ModelAttribute 사용
◦
파라미터 이름이 객체 필드와 일치할 경우 파라미터들을 객체에 조립한 후 model에도 자동으로 담아줌
◦
생략 가능. 생략된 파라미터는 String, int, Integer 등 단순 타입일 경우 @RequestParam이 적용되고 나머지 객체는 @ModelAttribute가 적용됨
▪
argument resolver로 지정해둔 타입은 제외
HTTP 요청 데이터 받기 - HTTP 메시지 바디 - 단순 텍스트
•
request.getInputStream() 사용
ServletInputStream inputStream = request.getInputStream();
Java
복사
•
파라미터에서 InputStream으로 받기
...(InputStream inputStream, Writer responseWriter)...
Java
복사
◦
받은 inputStream은 아래처럼 처리
String messageBody =
StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
Java
복사
•
파라미터에서 HttpEntity 객체로 받기
...(HttpEntity<String> httpEntity)...
String messageBody = httpEntity.getBody();
Java
복사
•
파라미터에서 @RequestBody로 받기
...(@RequestBody String messageBody)...
Java
복사
HTTP 요청 데이터 받기 - HTTP 메시지 바디 - JSON
•
위의 텍스트 얻는 방법으로 문자열을 얻은 후 ObjectMapper(Jackson 라이브러리) 사용
...(@RequestBody String messageBody)...
ObjectMapper objectMapper = new ObjectMapper();
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
Java
복사
•
파라미터에서 @RequestBody로 받기 - JSON을 객체로 조립해서 넘겨줌
...(@RequestBody HelloData helloData)...
Java
복사
◦
@RequestBody 생략 불가. 생략 시 @ModelAttribute 적용됨
•
파라미터에서 HttpEntity<객체>로 받기
...(HttpEntity<HelloData> data)...
Java
복사
HTTP 응답 - 정적 리소스, 뷰 템플릿
•
문자열 반환
◦
정적 리소스 먼저 찾고, 없으면 템플릿 찾음
◦
뷰 템플릿 경로
src/main/resources/templates
◦
정적 리소스 경로
src/main/resources/static
•
ModelAndView 반환
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
Java
복사
•
void 반환 - URL을 논리 뷰 이름으로 사용
◦
불명확하기 때문에 비추천
HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
•
텍스트 응답
◦
반환값 void
▪
response.getWriter.wriet(”ok”)
◦
반환값 ResponseEntity<String>
▪
return new ResponseEntity<>(”ok”, HttpStatus.OK)
◦
반환값 String + 메서드에 @ResponseBody
•
JSON 응답
◦
반환값 객체 + 메서드에 @ResponseBody + @ResponseStatus(HttpStatus.OK)
◦
반환값 ResponseEntity<객체>
▪
return new ResponseEntity<>(객체, HttpStatus.OK)
•
@Controller 대신 @RestController를 사용하면 해당 컨트롤러에 모두 ResponseBody가 적용되는 효과가 있음 - REST(HTTP) API 전용
◦
내부에 @ResponseBody가 적용되어 있음
•
메시지 컨버터
◦
다음의 경우에 적용
▪
HTTP 요청 시 - @RequestBody, @HttpEntity(RequestEntity)
▪
HTTP 응답 시 - @ResponseBody, @HttpEntity(ResponseEntity)
◦
적용 시, 다음 컨텐츠에 따라 적용
▪
기본 문자열 처리 시 - StringHttpMessageConverter 동작
▪
기본 객체 처리 시 - MappingJackson2HttpMessageConverter 동작
◦
스프링 부트 기본 메시지 컨버터
org.springframework.http.converter.HttpMessageConverter 인터페이스
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
...
Java
복사
▪
요청과 응답 시 데이터 형식(Content-Type)과 변환하는 타입(파라미터 대상 클래스)을 확인해서 둘 다 지원 가능하면 적용
◦
메시지 컨버터의 위치
▪
ArgumentResolver 및 ReturnValueHandler에서 사용
PRG (Post/Redirect/Get)
•
Post 요청을 통해 등록처리 후 뷰 템플릿이 아니라 다른 화면으로 리다이렉트하는 패턴
•
RedirectAttributes 이용
◦
URL 인코딩 및 pathVariable, 쿼리 파라미터까지 처리해줌
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/basic/items/{itemId}";
//리다이렉트 결과 URL: http://localhost:8080/basic/items/3?status=true
}
Java
복사
◦
attribute로 추가된 항목 중에서 pathVariable인 것을 URL에 넣고 나머지는 쿼리 파라미터로 붙여줌