2022. 5. 22. 16:07ㆍLanguage`/Spring
웹 & HTTP
웹 상에서 일어나는 모든 동작은 HTTP에 의해서 제어가 된다
Client (웹 브라우저) : HTTP Request
Server : HTTP Response
Client가 HTTP Reqeust Message를 통해서 "어떤 자료(html, text, image, ...)좀 주세요"라고 요청하면 Server는 logic을 수행하고 그에 따른 HTTP Response Message를 생성해서 Client에게 보내준다
결국 모든 것이 HTTP와 관련이 있고 HTTP Message를 통해서 서로 정보를 교환한다고 볼 수 있다
- HTML, TEXT, ...
- IMAGE, 음성, 영상, 파일, ...
- JSON, XML (API)
- ...
거의 모든 형태의 데이터들을 HTTP Message를 통해서 교환한다
웹 서버 (Web Server)
웹 서버는 당연히 HTTP 기반으로 동작하는 서버이다
웹 서버는 "정적 리소스"를 제공해주고 기타 부가적인 기능도 제공해준다
- 정적 HTML
- CSS
- JS
- Image
- 영상
- ...
웹 서버에는 NGINX, APACHE, ... 등이 존재한다
Client가 웹 서버에게 http request를 보내면 웹 서버는 "이미 보관하고 있는 정적 리소스"를 그대로 요청에 따라서 응답해준다
보유하고 있는 정적 리소스에 대해서 어떠한 변경도 가하지 않고, 그대로 전달한다
웹 애플리케이션 서버 (WAS : Web Application Server)
WAS도 당연히 HTTP 기반으로 동작하는 서버이다
WAS는 웹 서버의 기능을 포함하고 "프로그램 코드를 통해서 Application Logic의 수행"이 가능하다
- 동적 HTML
- HTTP API : JSON
- 서블릿 & JSP & 스프링 MVC
WAS에는 Tomcat, Jetty, Undertow, ... 등이 존재한다
WAS는 logic의 수행이 가능하기 때문에 client의 request에 대해서 "logic을 수행"하고 그에 따라서 변경된 "동적인 HTML"을 제공할 수 있다
웹 시스템 구성
(1) WAS + DB
이렇게 구성을 하게되면 WAS에서 {정적 리소스 + 애플리케이션 로직} 모두 제공이 가능하다
하지만 WAS는 굉장히 특별한 "로직을 수행할 수 있는"서버이므로 저 2가지를 모두 담당하면 너무 많은 역할을 담당하게 되어서 서버에 과부하가 걸릴 수 있다
그리고 Logic을 수행한다는 것은 굉장히 비싼 작업을 한다는 의미이고, 비싼 작업을 수행하려면 많은 자원이 필요하다. 그런데 정적 리소스까지 제공해준다면 비싼 Logic의 수행이 어려워질 수 있다
만약 서버에 에러가 발생하게 된다면 모든 기능이 마비가 되고 그에 따라서 Client들에게 오류화면조차 제공을 할 수 없을 것이다
(2) WS + WAS + DB
이렇게 구성을 하면 WA & WAS는 역할을 분담할 수 있다
WS : 정적 리소스 제공
WAS : 애플리케이션 로직 수행
WAS는 로직 수행에 더 집중할 수 있게 된다
정적 리소스는 WS에서 먼저 처리를 하고, 만약 로직이 필요한 request일 경우 그에 대한 처리를 WAS에 위임하게 된다
결론적으로 굉장히 효율적인 리소스 관리를 할 수 있다
만약 정적 리소스가 더 필요하다면 WS를 더 늘리면 되고, logic수행이 많이 필요하면 WAS를 늘리면 된다
일반적으로 정적 리소스를 제공하는 WS는 서버가 잘 죽지 않는다
하지만 비싼 logic을 수행하는 WAS의 경우 서버가 자주 죽는다
따라서 WAS & DB에 장애가 발생할 경우 그에 대한 오류 화면을 WS에서 제공해줄 수 있다
HTML Request & Response
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<form action="/save.html" method="post">
<fieldset>
<legend>Information</legend>
이름 <input type="text" name="name" /><br><br>
나이 <input type="text" name="age" /><br><br>
전화번호 <input type="text" name="phone"/><br><br>
<button type="submit">제출</button>
</fieldset>
</form>
</body>
</html>
이런 Form이 있다고 가정하고 현재 form을 보면 action = "/save.html"이므로 /save.html인 url로 위의 정보들이 post방식으로 전송된다
POST방식이란 server로 데이터를 보내서 저장한다는 의미이다
맨위는 Client가 생성한 "HTTP Request Message"이고 그 다음은 Server가 request에 대해서 생성한 "HTTP Response Message"이다
마지막은 client가 server로 보낸 "데이터"이다
POST방식으로 데이터를 보내게 되면 데이터는 "HTTP Request Message의 Body"에 실려서 보내지게 된다
이러한 과정 속에서 "서버"에서 처리해야 하는 업무는 다음과 같다
1. 서버 TCP/IP 연결 대기 & Socket 연결
-> 3-way handshake를 통한 연결 확립
2. HTTP Request Message를 Parsing해서 분석
-> 어떤 method로 데이터가 보내졌고, url 어디고, ....
3. Context-Type 확인
4. HTTP Request Message의 Body 부분 Parsing
-> Post방식으로 request가 왔으면 데이터는 body부분에 존재하기 때문에
5. "POST" Process 실행
## 6. 비즈니스 로직 실행 ##
-> WAS가 수행해줘야 하는 부분 & DB에 데이터 저장 요청
7. HTTP Response Message 생성
-> Start Line & Header & Body
-> Body부분에 로직을 통해서 동적으로 렌더링된 HTML이 보내진다
8. TCP/IP에 Response Message 전달 & Socket 종료
비즈니스 로직은 단순히 회원 정보를 DB에 저장하는 것이 전부인데, 나머지 부가적인 단계가 너무 많다.
>> 중요 logic이 아닌 이외의 단계를 처리하기 위해 "서블릿"이 탄생하게 되었다
서블릿
서블릿은 스프링과 비슷하게 "서블릿 컨테이너"가 존재한다
서블릿 컨테이너는 "서블릿 객체"를 생성/호출/관리해준다
@WebServlet(name="helloServlet", urlPatterns="/hello")
public class HelloServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response){
// Application Logic
}
}
/hello의 url이 호출되면 서블릿 코드들이 실행된다
HttpServletRequest : HTTP Request 정보를 편리하게 사용 가능
HttpServletResponse : HTTP Response 정보를 편리하게 사용 가능
-> 개발자가 직접 여러 요소들을 parsing하지 않아도 된다
서블릿 HTTP 흐름
1. 웹 브라우저에서 localhost:8080/hello로 request
2. WAS는 HTTP Request Message를 토대로 {request & response 객체}를 새로 생성
3. 그 다음에 서블릿 컨테이너에서 해당하는 서블릿 객체인 "helloServlet"을 꺼내서 코드를 실행
- parameter로 새로 생성한 request, response 전달
- 개발자는 request 객체에서 http request 정보를 편하게 꺼내서 사용한다
- 개발자는 response 객체에 request에 의한 http response 정보를 입력
4. helloServlet 코드의 실행이 종료되면 WAS는 response 객체를 토대로 "HTTP Request Message"를 생성
5. HTTP Response Message를 웹 브라우저로 전달
서블릿 컨테이너의 특징은 다음과 같다
1. 서블릿 컨테이너는 "서블릿 객체"를 {생성/초기화/호출/종료}하는 생명주기를 관리해준다
2. 서블릿 객체를 "싱글톤"으로 관리해준다
3. SSR을 하는 JSP의 경우에도 서블릿으로 변환되어서 사용된다
4. 동시 요청을 위한 "멀티 쓰레드 처리"를 지원한다
멀티 쓰레드
Server는 Client의 request 하나당 response하나를 생성한다
(1) One Client + Single Thread
1. Client가 request를 보낸다
2. Connection이 이루어지고, 해당 Client를 위한 "Thread 하나"를 할당해준다
3. Response를 보내면 할당된 Thread는 다시 휴식
여기서 할당된 Thread가 "서블릿 객체"를 호출해준다
(2) Many Client + Single Thread
현재 ClientA, ClientB가 있다고 하자
먼저 ClientA가 request를 보내고 thread 하나를 할당받은 후 logic을 수행하고 있었다
하지만 logic 수행중에 어떠한 오류가 발생해서 처리가 지연되고 있다고 하자
>> 여기서 ClientB가 Reqeust를 보냈다
그러면 Thread는 1개인데 이 Thread가 ClientA에게 잡혀있고 여기서 ClientB가 thread를 요청하면 결국 둘다 아무일도 할 수 없는 상태가 발생한다 : Deadlock
>> 이에 대한 해결책은 "Request가 올때마다 Thread를 생성하자"
(3) Many Client + Multi Thread
request가 올때마다 thread를 생성해서 할당해주는 것을 볼 수 있다
※ 요청마다 thread를 생성하는 것에 대한 장단점
장점
1. 동시 요청에 대한 처리 가능 (blocking X)
2. 시스템상의 자원의 한계까지 처리 가능
>> 결론적으로 하나의 스레드가 blocking되어도 나머지 스레드는 정상 동작 가능
단점
1. 쓰레드 생성 비용은 굉장히 비싸다
2. request마다 스레드를 생성하면 user에 대한 response가 늦어진다
3. 쓰레드는 "Context Switch Cost"가 발생한다
4. 쓰레드 생성에 제한이 없기 때문에 결국 자원의 threshold를 넘어서서 서버가 죽을 수 있다
>> 단점을 해결하기 위한 "Thread Pool"
쓰레드 풀
미리 WAS에 쓰레드를 여러개 "미리" 생성해놓는다
- 미리 생성된 쓰레드는 "쓰레드 풀"에서 휴식을 취하고 있다
request가 오면 쓰레드 풀에 존재하는 쓰레드들 중에서 하나를 reqeust에 대해서 할당해준다
request에 대한 logic처리가 끝나서 response를 보내면 할당된 thread는 다시 쓰레드 풀로 들어간다
쓰레드 풀에 쓰레드가 없을 경우 해당 request에 대해서 "대기하라고 할 수도있고 아예 거절할 수도 있다"
"필요한 쓰레드를 쓰레드 풀에 보관하고 관리"
-> 쓰레드 풀은 생성 가능한 쓰레드의 최대치를 관리한다. Tomcat의 경우 최대 200개까지 관리할 수 있다
쓰레드가 필요하면 이미 생성된 쓰레드를 쓰레드 풀에서 꺼내서 할당해주고, 사용이 끝나면 다시 쓰레드 풀에 반납한다
## 장점 ##
1. 쓰레드가 미리 생성되어 있기 때문에 매번 쓰레드를 생성하는 것보다 {생성 & 종료(context switch) 비용}이 절약되고, response가 빠르다
2. 생성 가능한 쓰레드의 최대치가 존재하기 때문에 request가 over되어도 기존 request는 안전하게 처리할 수 있다
쓰레드 풀에 설정할 수 있는 최대치를 너무 낮게 설정한다면?
>> 자원은 남아돌지만 최대치가 너무 낮기 때문에 request에 대한 response delay가 빨리 발생할 수 있다
쓰레드 풀에 설정할 수 있는 최대치를 너무 높게 설정한다면?
>> 최대치가 너무 높으면 자원의 threshold를 넘어서서 서버가 죽을 수 있다
쓰레드 풀의 MAX는 {logic의 복잡도 & 자원 상황}에 따라 전부 다르게 설정해야 한다
따라서 일단 설정하고 "성능 테스트"를 통해서 MAX를 조절하면 된다
- 아파치 ab
- 제이미터
- nGrinder
결국 WAS의 핵심은 "멀티 쓰레드"를 지원한다는 것이다. 따라서 개발자는 "멀티 쓰레드"를 신경쓰지 않고 "싱글 쓰레드 코딩"하듯이 개발할 수 있다
하지만 객체는 싱글톤이기 때문에 공유변수같은 field를 조심해서 다뤄야 한다
정적 리소스
정적 리소스는 {순수 HTML 파일, CSS, JS, 이미지, 영상, ...}등을 의미한다
Client는 정적 리소스를 요구하면 Server는 "이미 생성된 리소스 파일"을 그대로 응답해준다
동적 HTML
동적 HTML이란 HTML내에서 동적으로 변경을 가해서 생성해낸 HTML이다
예를 들어서 client가 회원의 정보를 보고싶어한다
그러면 server에서는 회원의 정보에 관한 데이터를 db에서 가져와서 동적으로 렌더링을 하고 나서 생성된 동적 HTML을 client에게 응답해준다
주로 JSP & Thymleaf같은 "뷰 템플릿"을 사용해서 렌더링을 한다
HTTP API
HTML이 아니라 "데이터"를 전달하는 것을 HTTP API라고 한다
주로 "JSON"형식을 활용한다
HTTP API는 다양한 시스템에서 호출한다
1) 앱 to 서버
앱 개발자와 협업을 한다고 했을 때 앱 개발자가 "자체적으로 컴포넌트 다 만들었으니까 상품에 대한 데이터만 전달해주세요"라고 요구를 한다면 백엔드 개발자는 DB로부터 데이터 받은 다음에 그 데이터를 JSON형식으로 전달해줄 것이다
2) Cliet to 서버
프론트엔드 개발자와 협업을 할 때, 웹 브라우저에서 CSR을 하기위해서 js의 "ajax"라는 api를 통해서 서버 api를 호출한다. 그러면 백엔드 개발자는 필요한 데이터를 받아서 JSON형식으로 전달해준다
3) 서버 to 서버
주문 서버 & 결제 서버가 서로 통신할 경우
SSR vs CSR
※ SSR (Server Side Rendering)
서버에서 최종 HTML을 생성해서 Client에 전달
- 서버에서 렌더링을 다해서 최종 HTML을 생성
- 주로 정적인 화면에서 사용한다
- JSP / Thymeleaf :: 백엔드 개발자
※ CSR (Client Side Rendering)
HTML 결과를 js를 통해서 웹 브라우저에서 동적으로 생성해서 적용
- 서버에서 데이터를 받고, 웹 브라우저에서 렌더링
- 주로 동적인 화면에서 사용
- 구글 지도, gmail, 구글 캘린더, ...
- React / Vue.js :: 프론트엔드 개발자