오늘날의 웹 프레임워크들은 사용하기 너무 편해서, 기저에서 무슨 일이 일어나는 지 알고자 하지 않으면 사실상 모르고도 개발이 가능하다.
그러나 처음부터 그 흐름을 잘 이해하고 공부하는 편이 훨씬 낫다고 생각한다.
필자는 일단 돌아가는 앱을 만들면 만족하는 식으로 공부를 시작했었는데, 돌이켜보면 프레임워크의 기능을 익힐 때 붕 뜨는 느낌이 든 적이 많았다. 큰 그림에 대해 이해하고 있지 않으니, 반쪽짜리 시야와 사고에서 출발하게 되는 것이다.
세상에 마법은 없다는 것을 알고, 서버는 무엇을 하는 것인지, 서버를 작동시키기 위해 무엇이 필요한 지 제대로 이해해두어야 한다. 그렇게 하면 웹 프레임워크들이 (대체로 닮아있는) 제각각의 방식으로 어떻게 애플리케이션 서버 구축을 돕는지 이해가 훨씬 빠를거고, 개발자로서 성장하는 속도도 분명 더 빠를거다.
말하자면 근본 같은 것..
웹 애플리케이션 서버 개발의 근본을 한번 정리해보았다.
html 기반의 정적 콘텐츠만 서빙하던 시절이 있었다는 것은 아주 유명하다.
정적 컨텐츠 서빙
을 수행할 수 있으면 웹 서버다.
(웹 서버 = 정적 컨텐츠 서빙만을 수행하는 서버 ❌ 가 아니고, 초기 웹 서버들이 정적 컨텐츠 서빙만을 수행할 수 있는 Static Web Server들이었다고 표현하는 것이 적절한 것 같다. 아래 mozilla 사진 참고)
대표적인 웹 서버로는 Apache, NginX 등이 있다.
모두가 알고 있듯이, 이후 동적으로 파일을 서빙할 필요가 생겼고, 이를 수행하기 위해 정적 웹 서버들에 웹 애플리케이션 서버 (WAS)라는 것이 등장하게 된다.
웹 애플리케이션 서버는 앞서 언급한 대로 동적 파일 서빙이 가능한 서버다.
WAS는 독립적으로도 운용 가능하고, 웹 서버와 붙어서, 웹 서버가 처리 못하는 동적인 요청을 다루어주는 방식으로도 운용 가능하다. (이러한 역할 분담에 대한 설명은 뒤에서 더 자세히 다루겠다.)
어떻게 전달하겠어.. 프로그램으로 전달하지. 당연한 이야기지만,
php든, 파이썬이든, 자바든,, 뭐든 써서 데이터를 만들어내면 된다.
그런데 말처럼 호락호락하지만은 않았던 것 같다.
기존의 정적인 웹 서버 체계는 리눅스와 C언어를 기반으로 구축되어있었는데, 동적 파일 서빙을 위한 애플리케이션 프로그램은 개발자들마다 만들기 나름이었을 테다.
따라서 웹 서버 <=> 애플리케이션 프로그램
간의 표준 인터페이스가 필요했고, 가장 먼저 CGI가 탄생했다. 웹 서버가 필요한 프로그램을 호출하는 원시적인 인터페이스다.
CGI를 통해 웹 서버는 스크립트 파일 (.py, .php)이나 기계어로 컴파일된 프로그램을 실행하고, 실행 결과를 받아오는 표준 방식을 가질 수 있게 되었다.
그런데 이렇게 외부 프로세스를 하나 띄워서 실행 결과를 받아오는 방식에는 한계가 아주 많다.
.class
파일은 바이트코드로 컴파일되고, JVM이 기계어로 번역한다.앞서 설명한 것보다 훨씬 많은 이유로, CGI는 개선될 필요가 있었다.
어떻게 개선할 수 있을지 3초 정도 고민해보자..
아래와 같은 답을 생각해볼 수 있을 것이다.
프로세스를 매번 띄우는 대신, 미리 프로세스를 하나 띄워두면 어떨까?
그리고 실제로 cgi는 이렇게 개선되었다.
이 때 미리 떠있는 프로세스 == 우리가 구동하고 싶은 애플리케이션과 쉽게 호환이 되는 서버
라고 생각하면 될 것 같다.
파이썬 진영에서는 wsgi라는 인터페이스가 등장하고 그 구현체로 uwsgi, gunicorn 등이 등장하면서 훨씬 효율적인 동적 서버 서빙이 가능해졌다. (장고와 같은 웹 애플리케이션을 만들면, uwsgi랑 붙여서 WAS를 운용하게 됨)
자바 진영에서도 문제를 비슷하게 해결한다.
애초에 cgi로 자바 프로그램을 돌릴 필요 없이, HTTP request를 그대로 자바 프로세스 안에서 다 처리하는 것이다.
자바에서는 Servlet & Web Container를 통해 이러한 목표를 달성했다.
서블릿은 HTTP request & response를 Java Class로 표현한 하나의 객체다.
각 요청에 대한 처리는 곧 하나의 서블릿에 대한 처리로 치환된다.
GCI vs Servlet 글을 보면, CGI 방식과의 비교를 통해Servlet을 통한 자바 진영의 HTTP 요청 대응을 잘 이해할 수 있다.
주요한 차이점은 아래와 같이 정리할 수 있을 것 같다.
요청을 처리하는 단위 | HTTP Request | 생명주기 | |
---|---|---|---|
Servlet | 스레드 | 알아듣고 처리 가능 | destroy()를 통해 명시적으로 제거 |
CGI | 프로세스 | 읽을 줄 모름 | 요청을 처리하는 즉시 소멸 |
서블릿은 POJO다. 따라서 외부의 요청을 누군가 듣고, 대응되는 Servlet 클래스를 만들어서, 필요한 처리를 따로 취해주어야 한다. (Servlet은 능동적으로 무언가를 하는 친구가 아니다.) Oracle에서 제공하는 서블릿의 처리 명세만이 존재할 뿐이다.
해당 액션은 서블릿 컨테이너
들에 의해 수행된다.
서블릿 컨테이너는 JVM 위에서 동작하며, 서블릿을 바탕으로 요청을 처리하기 위한 다양한 클래스들이 미리 로드되어있는 프로세스다.
아주 대표적인 서블릿 컨테이너로 Apache Tomcat이 존재한다.
여기서 간단한 서블릿을 톰캣에 로드해서 WAS를 구동하는 예제를 해볼 수 있다.
우리가 사용하는 스프링 MVC 아키텍처 역시 서블릿 컨테이너 위에서 동작한다.
DispatcherServlet이 톰캣의 Servlet으로 등록되고, 우리가 작성한 로직에 의해 요청이 처리된다.
(서블릿과 스프링의 DispatcherServlet에 대한 추가 설명은 별도의 포스팅으로 분리하는게 나을 것 같다.)
여기까지 이해하면, 웹 서버가 뭔지, WAS는 뭔지, Django & Spring 생태계에서는 어떤 방식으로 이들을 처리하는지 이해할 수 있다.
아래 이미지가 아주 적절한 것 같아서 긁어와봤다.
** Tomcat은 wsgi와 다르게, 웹 서버가 안에 내장되어있다. 따라서 사실 굳이 앞에 웹 서버를 둘 필요가 없다. 다만 무중단 배포나, 리버스 프록시의 다양한 활용을 위한 use-case가 존재하긴 하는 것 같다.
동적 파일 서빙이 가능하면, 정적 파일 서빙도 당연히 될 것이다.
그렇기 때문에, 사실 wsgi 서버나 servlet container만 돌려도 요청을 처리하는 '서버'를 구동하는 데에는 문제가 없다.
그런데 왜 우리는 여전히 NginX, Apache httpd 같은 웹 서버를 같이 돌릴까?
짧은 식견일 수 있지만,,
이제 웹 서버랑 WAS를 굳이 분리할 필요 없는 것 같다.
특히 REST API에서는 정적 파일을 서빙할 일이 많지도 않고,
심지어 톰캣은 웹 서버이기도 해버리니까.. 구분이 더더욱 모호하다.
그럼에도 불구하고 웹 서버를 같이 쓰는 케이스를 정리해두었지만, 이러한 것들(로드밸런싱이나 무중단배포)도 클라우드 환경이 발전하면서 다른 좋은 방법들이 많이 생겼다고 생각한다.
글 쓰다보니 Netty는 서블릿이랑 아예 상관 없는 것인지 궁금해졌다.
Netty 공부해야 하는데..
https://scshim.tistory.com/28
https://stg0123.github.io/study/41/
https://stackoverflow.com/questions/7213541/what-is-java-servlet