<추천글>[WAS] 웹 서버, WAS, Servlet

2021. 10. 17. 01:52[ 백엔드 개발 ]/[ Spring ]

대체로 HTTP 웹 서버를 줄여서 웹 서버라고 한다. 말그대로 HTTP나 HTTPS를 지원하는 서버이다. 웹 서버는 이미지나 HTML파일, 자바스크립트 파일, CSS파일과 같은 정적 리소스를 지원하기 위해 만들어졌다.  따라서 웹 서버는 정적 컨텐츠를 제공하기 위한 용도로 사용된다. 여기서 정적 리소스는 모든 사용자에게 똑같이 보여지는 파일로써 어떤 클라이언트로부터 요청이 오든지 상관없이 항상 같은 파일을 제공한다. 

 

웹 서버와 WAS의 큰 차이점은 동적 컨텐츠를 제공하는 유무이다. 항상 똑같은 파일을 제공하는 정적 컨텐츠에서 동적인 컨텐츠를 처리하기 위해 WAS가 존재한다. 보통 동적 컨텐츠가 요구되는 요청이 클라이언트로부터 왔을 때에는 웹 서버가 WAS에게 요청을 전달하지만 최근 WAS는 웹 서버의 역할도 한다. 아무튼 WAS는 정적 컨텐츠의 한계를 넘어 동적 컨텐츠를 제공하고, 동적 컨텐츠를 제공하기 위해 우리같은 웹 개발자는 프로그래밍 언어를 통해 비지니스 로직을 구현하고 데이터를 DB로부터 가져와 가공해 웹 서버에게 넘긴다. 즉, 동적컨텐츠를 가공하기 위해서는 프로그래밍 언어가 수반되어야 하기 때문에 WAS에 이를 위한 애플리케이션을 올리고 요청을 처리하는 구조가 된다.

 

cf. WAS는 OS와 애플리케이션 사이에서 역할을 하는 미들웨어이다.

 

cf. 동적 컨텐츠를 다루는 스크립트 언어로 JSP라는 것이 있다. 이 JSP 파일은 HTML내에 자바 코드를 삽입해서 동적인 컨텐츠를 제공할 수 있다. 이 JSP 파일을 컴파일해서 HTML 파일로 만들어 클라이언트에게 제공하는 역할을 수행하는 것이 WAS이다.

 


 

자바에서는 이러한 웹 서버를 Web container 또는 Servlet container라고 한다. 전통적인 자바 웹 애플리케이션 구동 방식은 다음과 같다.

 

오픈소스 WAS인 톰켓을 물리적인 서버 컴퓨터에 설치하고 WAS를 구동시켜서 우리가 개발한 웹 어플리케이션을 war로 빌드를 해서 deploy하게 되면 WAS가 우리가 작업한 코드를 로드해서(컨테이너가 로드됨) 동작시킨다. 그러면 클라이언트로부터 HTTP 요청이 오면 웹 서버가 받아서 web container(was container, servlet container)에 있는 서블릿을 구동시킨다. 서블릿은 웹 개발자가 구현한 비지니스 로직(자바코드)을 호출하고 여기서 DB와 연동해 특정 데이터를 가져와 가공한다. 가공한 데이터를 이용해 JSP 파일을 만들면 WAS가 컴파일해 HTML파일을 만들고 이를 HTTP 응답으로 보낸다. 또는 JSOM, XML 등 다양한 동적인 파일들을 제공할 수 있다. 

 


 

보통의 경우에는 웹 서버를 앞단에 빼두어 클라이언트로부터 요청을 우선적으로 받도록 한다. 그리고 public한 성격을 갖는 정적인 컨텐츠를 웹 서버에서 바로 응답해 처리한다. 그리고 동적 컨텐츠 요청에 대해서는 웹 서버가 WAS 컨테이너에 메소드를 호출시키는 구조가 된다. 최근 WAS는 웹서버의 역할도 함께 하는 구조로 변경되었고 아파치 웹 서버나 nginx와 같이 웹 서버는 로드 밸런서 역할을 수행해 WAS로의 트래픽을 조절하는 구조가 되었다.

 


 

[서블릿 생기초]

서블릿은 서버 역할을 하는 자바 소프트웨어 컴포넌트이다. HTTP 요청에 대해 특정한 기능을 수행하고 HTML 문서를 생성하는 등의 응답처리를 하는 자바 소프트웨어 컴포넌트(자바 클래스) 라고 볼 수 있다. 서블릿은 WAS 컨테이너 안에서 동작하고, 서블릿을 구동하려면 WAS 컨테이너가 필요하다. 

 

https://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html

 

서블릿은 “자바 인터페이스”로 존재하고 있고 위 자바독을 확인해 보면 웹 서버 안에서 동작하는 자바 프로그램이다. 서블릿은 CLIENT로부터 HTTP요청을 받고 이에 대한 응답을 처리한다. 서블릿은 인터페이스로써 스펙(명세사항, specification)일 뿐이다. 서블릿 인터페이스의 구현체로 HttpServlet이 있는데 이를 우리가 직접 확장해서 로직을 작성하게 된다. 이렇게 확장해 구현한 서블릿이 동작하기 위해서는 WAS컨테이너가 필요하다. WAS는 다양한 벤더사가 만들고, 이러한 다양한 벤더사가 지켜야할 규약, 규칙이 서블릿이다.

 

즉, 웹 개발자가 서블릿 스팩에 맞게 웹 애플리케이션을 개발하고 WAS개발자도 서블릿 스펙사항을 맞춰 WAS컨테이너(서블릿컨테이너)를 개발한다. "따라서 서블릿은 스펙사항으로써 양쪽(웹 개발자와 WAS벤더사) 간에 충돌이 없게 해준다." 서블릿을 상속받아서 특정 메소드들을 구현하면 HTTP요청이 왔을 때 WAS가 이 메소드들을 호출해주는 구조기 된다. 만약 우리가 WAS를 직접 만들어야 한다면, TCP 커넥션을 맺고 끊는 과정, HTTP 요청 메시지 파싱작업, 파싱한 데이터를 특정 클래스에 맞게 인스턴스화, 다시 클라이언트로 HTTP 응답처리 등과 같은 반복적인 작업들을 직접 구현해야 한다.

 

따라서 서블릿을 통해 비지니스 로직을 담는 부분과, 클라이언트로부터 요청을 받는 부분을 구분시킬 수 있으며, 웹 개발자는 서블릿 스팩에 맞게 개발된 WAS에 로직 코드를 배포시킴으로써 네트워크 연결 등 하위 레벨의 구현 사항을 신경쓰지 않고 개발할 수 있다.

 

[서블릿을 등록하기 위한 3가지 방법]
1. web.xml 파일에 작성하거나
2. 스프링에서 제공하는 WebApplicationInitializer를 상속한 클래스를 만들어 설정하기
3. spring-boot-starter-web으로 내장 톰켓 사용

 

 

cf. 자바 서블릿은 최근에 자카르타 서블릿으로 이름이 바뀌었다.

 

 

https://myblog.opendocs.co.kr/archives/425

 

 

보통 WAS컨테이너는 쓰레드 풀(Tomcat 기본값 200개)을 두고 HTTP 요청 당 쓰레드를 할당하는데(spring MVC에만 해당, 이와 상반된 spring WebFlux도 있음), 여기서 스프링 빈이 기본적으로 싱글톤 스콥을 갖는 이유가 있다. 자바 인스턴스는 힙 메모리에 상주되기 때문에 쓰레드 간 공유할 수 있도록 구성해 다발적으로 들어오는 요청에 대해 객체를 생성하지 않고도 빠르게 처리할 수 있도록 한다. 따라서 스프링 빈은 대체로 stateless하게 구현함으로써 빈이라는 공유 자원을 동시 다발적으로 들어오는 쓰레드들이 사용할 수 있도록 한다. 따라서 필드에 final 키워드를 적극적으로 사용하는 것이 권장되는 이유이다.

 

cf. 인스턴스는 힙 메모리에 저장되 쓰레드 간 공유하지만, 스택은 쓰레드마다 갖는 구조이므로 Bean 의 메소드들은 공유되지 않는다. 따라서 메소드 안에서 조작되는 변수는 stateless와 상관없다.

 

 

 

https://www.oreilly.com/library/view/head-first-servlets/9780596516680/ch04s02.html

 

 

위 그림과 같이 WAS 컨테이너가 띄워지면 WAS 컨테이너에서는 서블릿을 생성하며 객체가 initialized 상태가 되도록 한다. 

 

 

https://gmlwjd9405.github.io/2018/10/28/servlet.html

 

서블릿은 위 그림과 같이 크게 3개의 생명 주기를 갖는다. 첫 번째로 init은 WAS 컨테이너가 처음 띄워질 때 서블릿 객체를 initialize하기 위한 것이고, 서블릿 객체가 initialized되면 이후 클라이언트로 부터 요청을 WAS 컨테이너가 서비스 객체에게 service() 메소드를 호출함으로써 처리한다. 이때, WAS 컨테이너는 쓰레드 풀에서 요청 당 쓰레드를 할당해 서블릿의 service()를 호출한다.

 

그리고 서블릿의 service() 메소드 안에서는 HTTP 요청 메소드(get, post, delete. etc,) 등을 확인하고 해당 함수를 호출한다. 요청에 대한 처리가 완료될 때까지 쓰레드는 활성화 상태이고, 응답이 반환되면 쓰레드도 쓰레드 풀에 반환된다. 

 

마지막으로 웹 애플리케이션이 종료되 WAS 컨테이너가 메모리에서 내려갈 때 서블릿 객체는 destory된다. 이런 전반적인 서블릿 라이프 사이클을 웹 개발자가 직접 처리해주지 않고 스프링이 제공하는 Spring Web MVC가 처리한다.

 

https://gmlwjd9405.github.io/2018/10/28/servlet.html

 

 

주의할 것은 init()과 destroy()는 한 번씩만 호출된다는 것이다. 즉, 매번의 요청마다 서블릿 객체가 생성되는 것은 아니다. WAS 컨테이너가 띄워지고 WAS 컨테이너가 서블릿의 init()을 한 번만 호출하며 그 후에는 요청 당 쓰레드가 service() 메소드를 호출하는 구조이다. 서블릿 객체는 쓰레드 간 공유되지만 service()는 쓰레드 별로 개별 스택 영역에서 관리된다.

 

 

cf.스프링에서는 서블릿을 추상화시킨 Spring MVC를 제공한다.(PSA)

cf. 스프링부트를 사용할 경우 내장 톰켓(spring-boot-starter-web)을 이용하면 서버 컴퓨터에 WAS를 따로 설치하지 않아도 된다. 또한 임베디드 톰켓을 사용하면 WAS에 배포할 일이 없다. WAS에 배포하기 위해서는 자바 애플리케이션을 war로 빌드에 배포하지만 임베디드 WAS를 포함해 빌드하면 jar파일로 한 번에 빌드해 패키징할 수 있다.

 

[배포 방법]

1. 서버 컴퓨터에 WAS를 설치하고, 자바 애플리케이션을 war로 패키징해 WAS에 배포(전통적인 방식)

2. 내장 WAS를 애플리케이션에 포함시켜 한 번에 jar로 패키징해 서버에 배포

 

https://jsho8834.tistory.com/262

 

이렇듯 서블릿을 이용해 model, view, controller를 나눠 각각의 역할분담을 명확하게 분리한 구조를 servlet MVC 구조라고 한다. 컨트롤러 영역에서는 서블릿을 포함시켜 WAS 컨테이너에서 서블릿 객체를 호출하고 서블릿 객체가 요청에 맞는 컨틀롤러 메소드를 호출시킨다. 모델 영역에서는 웹 개발자가 구현한 데이터 가공을 위한 로직 코드가 포함되며 뷰 영역에서는 JSP 파일, 또는 HTML 파일 등이 포함되며 만들어진 HTML 파일 등이 클라이언트로 응답되는 구조이다.

 

Reference 

- http://index-of.es/Programming/O'Reilly%20Desining%20Series/O'Reilly%20Head%20First%20Servlets%20and%20JSP%20(2nd%20Edition).pdf