
웹이라는 것은 HTTP 기반으로 통신한다. 웹 브라우저에서 URL을 쳐서 접속하려고 하면 인터넷을 통해 서버에 접근하고, 그 서버에서는 HTML을 만들어서 클라이언트에게 보내준다. 이 모든 과정에서 HTTP 프로토콜 기반으로 동작한다는 것이다. 오늘날은 거의 모든 형태의 데이터를 HTTP 프로토콜 기반으로 주고 받는다. 심지어 서버 간에 데이터를 주고 받을 때도 HTTP를 대부분 사용한다. 바야흐로 HTTP의 시대인 것이다…
그럼 웹 서버가 뭐냐? 웹 서버는 HTTP 기반으로 동작하는 서버로, 정적 리소스와 부가 기능을 제공한다. 정적 리소스란 HTML, CSS, 자바스크립트 파일, 이미지, 영상 등과 같이 서버에 저장된 파일을 의미하며, 웹 서버는 클라이언트가 이를 요청하면 단순히 HTTP 프로토콜을 통해 응답해준다. 대표적인 웹 서버로는 Nginx와 Apache가 있다.
그 다음으로 WAS라고도 불리는 웹 애플리케이션 서버라는 것도 있다. 얘도 HTTP를 기반으로 동작한다. 얘는 앞서 말한 웹 서버의 기능을 대부분 포함하고 있다. 차이점은 프로그램 코드를 실행해서 애플리케이션 로직을 수행할 수 있다는 것이다. WAS는 단순 웹 서버와는 다르게 HTTP 요청이 오면 사용자에 따라 다양한 화면을 보여줄 수 있다. 서블릿, JSP, 스프링 MVC 모두 이 WAS에서 동작한다. 대표적으로 Tomcat, Jetty, Undertow 같은 것들이 WAS 서버다.
차이점에 대해 간단하게 정리해보자면, 웹 서버는 정적 리소스 제공하는 친구고, WAS는 애플리케이션 로직까지 실행할 수 있는 친구로 이해하면 된다. 근데 사실 둘의 경계가 굉장히 모호하다. 웹 서버도 사실 프로그램을 실행하는 기능이 포함되기도 하고, 웹 애플리케이션 서버도 웹 서버의 기능을 제공하기도 하기 때문이다.
그냥 웹 서버는 정적 리소스, WAS는 애플리케이션 로직을 실행하는 서버라고 생각하도록 하자.
그럼 실무에서는 용도가 어떻게 나눠질까? 실제 웹 시스템 구성을 해야 한다고 하면, 가장 간단하게는 WAS랑 데이터베이스로 구성할 수 있다.

하지만 이런 구성이면 WAS에 너무 많은 부담을 주게 된다. 서버 과부화 우려가 있고, 비용이 큰 애플리케이션 로직이 정적 리소스 때문에 수행이 어려워질 수 있다. WAS 장애가 발생한다면 클라이언트는 오류 화면조차 볼 수 없다.

그래서 해결책은 웹 서버를 앞에 두는 거다. 앞에서 정적 리소스를 다 처리해버리고, 동적인 처리가 필요한 경우에만 WAS에 넘기면 된다. 그럼 WAS는 동적 처리를 하고 DB를 뒤져서 데이터를 가져와서 내려오는 이런 흐름... 깔끔하다. WAS는 중요한 애플리케이션 로직만을 담당하고, 웹 서버는 단순 정적 리소스들만 처리하면서 업무 부담이 분산되었다. 그리고 또 좋은 점은 시스템 리소스를 굉장히 효율적으로 쓸 수 있다는 점이다.

이제 정적 리소스가 많아지면 웹 서버만 증설하고, 애플리케이션 리소스가 많이 사용되면 WAS만 증설하면 된다.
이제 서블릿에 대해 알아보자. 이름이랑 나이를 입력하고 "전송" 버튼을 누르면, 회원이 가입되는 간단한 폼이 있다고 해보자.

전송을 누르면, 웹 브라우저가 요청 HTTP 메시지를 만든다. 내가 만약 웹 애플리케이션 서버를 직접 밑바닥부터 구현한다고 하면, HTTP 메시지를 풀어 헤쳐야 한다.

서버에서 일단 TCP/IP 연결을 대기하도록 코드를 짜야 하고, 파싱을 해서 잘라 읽고, 파싱한 내용에 맞춰 프로세스를 실행하고, 그것에 대한 비즈니스 로직을 실행하고 필요에 따라 DB에 요청도 해야 할 것이다. 그리고 나서 HTTP 응답 메시지도 내가 직접 생성해야 한다. 마지막으로 TCP/IP에 응답을 전달하고 소켓을 종료한다. 아니… 그냥 회원 이름이랑 나이 입력받아서 DB 저장밖에 안 하는 건데, 이렇게 복잡하다고?
그래서 서블릿이라는 게 등장한 것이다. 서블릿은 "의미있는 비즈니스 로직" 을 제외한 모든 것을 지원해준다. 서블릿을 지원하는 WAS들이 TCP/IP 연결하는 것부터 HTTP 요청 파싱하고 HTTP 응답 메시지 생성하는 작업을 다 해준다.
최근의 서블릿의 특징으로는 다음과 같다
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) {
// 애플리케이션 로직
}
}
URL에 “/hello” 가 붙으면, 클라이언트에서 서버로 요청이 왔을 때 이 서블릿 코드에 있는 service() 메서드가 실행된다. 그래서 아까 말했던 작업들을 다 해준다. 내가 할 일은 그냥 HttpServlet을 상속받는 HelloServlet 클래스를 만들고, 거기에 애플리케이션 로직만 작성하면 끝이다. 지금 보면, service() 메서드에 HttpServletRequest, HttpServletResponse 파라미터 2개가 있다. 아까 HTTP 메시지를 내가 직접 파싱하려고 할 때 큰 어려움이 있었다. 그래서 HTTP에 대한 요청 정보를 굉장히 편리하게 사용할 수 있도록 서블릿이 다 만들어준다. 난 그냥 request 객체를 쓰면 된다. 응답 메시지도 마찬가지다. 그냥 response 객체를 받아서 내가 원하는 데이터를 넣으면 된다.
이제 전체 흐름을 정리해보자.

request, response 객체를 새로 만든다.request, response 객체를 파라미터로 같이 넘기면서 helloServlet을 실행한다.response 객체에 담겨있는 내용을 바탕으로 HTTP 응답 정보를 편리하게 입력한다.
WAS 안에는 서블릿을 지원하는 서블릿 컨테이너라는 것이 있다. 서블릿 컨테이너는 서블릿 객체를 자동으로 생성해주고 호출도 해준다. WAS가 종료될 때 서블릿 컨테이너에 있는 서블릿 객체도 같이 종료해준다. 다시 말해, 서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.

서블릿 객체는 싱글톤으로 관리한다. request, response는 요청마다 들어가는 데이터가 다 다르기 때문에 항상 새로 생성된다. 근데 helloServlet은 새로 생성할 필요가 있을까? 그냥 service() 메서드 안에 애플리케이션 로직만 들어가면 되기 때문에 충분히 재사용할 수 있다. 그리고 진짜 중요한 사실이 있는데, 그건 바로 서블릿을 지원하는 WAS는 동시 요청을 위한 멀티 쓰레드를 지원한다는 사실이다. 덕분에 서버가 1000명, 10000명의 요청을 동시에 처리할 수 있는 것이다.
백엔드 개발자에게 있어서 반드시 이해하고 넘어가야 할 가장 중요한 부분 중 하나다. 이걸 이해 못 하면 트래픽이 많을 때 어떻게 해결할지 갈피도 못 잡을 수도 있다.

클라이언트와 WAS가 요청과 응답을 주고 받는다고 해보자. 클라이언트가 요청을 하면 먼저 WAS와 TCP/IP 연결이 된다. 그리고 서블릿을 호출한다. 근데 서블릿 객체를 도대체 누가 호출하는거지? 바로 스레드다. 애플리케이션 코드를 하나 하나 순차적으로 실행하는 걸 스레드라고 한다. 동시 처리가 필요하면 스레드를 추가로 생성해줘야 한다.
만약 스레드가 하나 있다고 생각해보자.

클라이언트로부터 요청이 오면 TCP/IP 연결할 때 스레드를 할당한다. 그 스레드가 서블릿을 호출하는 것이다. 응답까지 마치게 되면 해당 스레드는 휴식을 하는 것이다.
이번엔 스레드는 하나인데 여러 개의 요청이 들어온다면?

처리가 지연되고 있는 와중에 다른 요청이 들어오게 되면 그 요청은 기존의 요청이 처리될 때까지 기다려야 한다. 이렇게 되면 요청 2개 다 죽어버릴 수 있다. 이때는 그냥 쓰레드를 하나 더 만들면 된다.

요청이 올 때마다 쓰레드를 새로 만드는 것이다. 근데 이렇게 요청이 올 때마다 쓰레드를 생성하도록 WAS를 설계하면…
그래서 WAS는 쓰레드 풀이라는 걸 쓴다.

말 그대로 수영장 안에 쓰레드가 놀고 있다가 쓰레드 풀에게 요청이 오면 놀고 있는 쓰레드를 갖다 쓰는거다. 그리고 쓰레드를 다 쓰면 죽이는게 아니라 다시 쓰레드 풀에 반납한다. 풀 안에 미리 쓰레드를 만들어서 넣어 놓고, 빌려 쓰고, 반납하는 메커니즘이다. 만약 쓰레드 풀에 쓰레드가 없는 상태에서 요청이 왔다면, 거절하거나 특정 숫자만큼만 대기하도록 설정할 수 있다.
WAS의 멀티스레드 지원의 핵심은 개발자가 멀티 스레드 관련 코드를 신경쓰지 않아도 된다는 것이다. 다만, 유념할 점은 멀티 스레드 환경이기 때문에 싱글톤 객체(서블릿, 스프링 빈)는 주의해서 사용해야 한다는 것이다.
HTTP API는 HTML이 아니라 데이터를 전달하는 방식이다. 예를 들어, 주문 내역을 요청하면 기존 웹 서버는 HTML을 내려주었지만, HTTP API는 주문 정보를 JSON 형태의 데이터로 응답한다. 단, 웹 브라우저는 JSON을 직접 렌더링하지 못하므로 클라이언트 측에서 별도로 처리해야 한다.
HTTP API는 주로 다음과 같은 경우에 사용된다.
데이터만 전달하고 UI는 클라이언트에서 처리하는 경우: 앱이나 클라이언트 개발자가 직접 UI를 구성하고, 서버(WAS)는 DB에서 조회한 데이터를 JSON으로 전달한다.
웹 클라이언트가 데이터를 요청해 자바스크립트로 동적으로 화면을 구성하는 경우: 서버는 필요한 데이터를 JSON으로 보내고, 브라우저의 자바스크립트가 이를 활용해 HTML을 생성한다.
서버 간 통신(Server to Server): 주문 서버와 결제 서버처럼 서로 데이터를 교환할 때 HTML은 필요 없고, JSON 데이터를 주고받아 결과만 전달하면 된다.
즉, HTTP API는 데이터 중심의 통신 방식으로, 다양한 환경(앱, 웹, 서버 간)에 맞게 활용된다.
서버 사이드 렌더링이란…

만약 "주문 내역 주세요" 라고 서버에 요청을 한다고 해보자. 그럼 서버는 주문 DB 조회해서 HTML을 동적으로 생성한 다음, 웹 브라우저의 HTTP 응답에 HTML 코드를 실어서 보내주는 것이다. 정리하면, HTML을 만드는 과정은 서버에서 다 끝내고, 클라이언트는 완전히 생성된 HTML을 보여주기만 한다는 것이다.

자바스크립트를 사용해서 웹 브라우저에서 동적으로 HTML을 생성하는 방식이다. 일단, 웹 브라우저에서 서버에 요청을 한다. 그럼 서버는 HTML을 주긴 하는데, 백지 상태의 HTML을 준다. 대신, 자바스크립트 링크를 하나 내려준다. 그럼 웹 브라우저가 그 자바스크립트를 서버에 요청한다. 그 자바스크립트 코드 안에는 클라이언트의 로직도 들어 있고, HTML을 어떻게 자바스크립트로 렌더링 할 지에 대한 로직도 다 들어있다. 그럼 웹 브라우저가 HTTP API를 가지고 서버 호출을 한다. 서버에서 주문 정보를 조회해서 해당 데이터를 JSON으로 딱 내려주는 것이다.