초기 웹서버(Web Server)는 정적 페이지
만 요청/응답할 수 있었다. 한마디로 웹 서버(Web Server)는 단순히 정해진 HTML파일을 주고 받는 그냥 무식한 서버라고 생각하면 된다. 현재 우리가 자주 접하는 웹 페이지는 대부분 동적 페이지
로, 예를 들어 우리가 네이버에 로그인을 하면 우리 이름이 "***님 안녕하세요" 라고 뜨는 것은 실제로 웹 서버의 뒤에 WAS(Web Application Server)
가 동적 페이지를 처리해줬기 때문이다.
심지어 개발자들이 위와 같은 요청 텍스트를 직접 처리하고 파싱하여, 다시 규약에 맞게 응답 텍스트를 만들어 보내야 한다면 매우 번거롭다.
(앞으로 나올 Servlet은 이러한 과정을 대신 처리해주어 개발자는 비즈니스 로직
에 더 신경쓸 수 있게 됨)
더 자세한 설명은 생략하고, WAS
가 등장하기 이전에 사실은 CGI(Common Gateway Interface)
라는 인터페이스가 등장하여 동적 데이터의 처리를 가능하게 해주었다.
CGI
등장 이후 웹 서버인 Apache
와 그 뒤에 CGI
사이에 규약을 만들어 동적 웹 페이지를 구현할 수 있었다. CGI
에는 문제가 있었다. 바로 요청이 들어올 때마다 Process
를 생성하여 처리한다는 것이었다. 심지어 좀 전과 같은 요청이 들어와도 이를 재사용하지 못하고 새로운 Process
를 생성하는 등 비효율적인 동작이 이루어졌다.
이를 개선하기 위해, 드디어 Servlet
이 등장한다.
이전의 매 요청마다 Process를 생성하는 CGI를 개선하여, 매 요청마다 Process 생성보다 비용이 작은 Thread를 생성하는 방식으로 바뀌었다. 생성된 Thread는 Servlet 구현체와 연결되고, Servlet 인터페이스를 구현받은 구현체의 메소드를 실행한다.
👉이때 각 메소드는 Servlet Container에 의해 호출되고, Servlet Container는 서블릿 인스턴스의 생명주기를 관리한다.
Tomcat은 WAS중 하나로, Servlet Container 기능을 제공한다. Tomcat을 서블릿 컨테이너라고 부르긴 하지만, 엄밀히 말하면 내장 웹 서버 등의 부가 기능도 제공하므로 WAS라고 부르는 것이 더 타당하다.
Servlet ⊂ Tomcat
HttpRequest
, HttpResponse
객체를 생성한다.하지만 멀티 스레딩
역시 오버헤드는 존재한다.(스레드 생성 비용/컨텍스트 스위치 등등)
멀티 스레드의 문제는 Thread Pool(스레드 풀)
로 개선을 하였고, 이에 대한 포스팅은 추가로 작성할 예정이다.
또한 각 Servlet마다 중복 코드가 존재하게 된다. 이를 해결하기 위해 드디어 마지막 Spring MVC
가 등장한다.
Spring MVC가 없던 과거에는, URL마다 서블릿을 생성하고 web.xml
로 서블릿을 관리했다. URL마다 서블릿이 필요하다 보니, 매번 서블릿 인스턴스를 만들어야했다. 또한, 각 서블릿마다 공통 기능을 하는 코드들이 중복해서 발생하기도 했다.
<web-app>
<!-- 1. aliases 설정 -->
<servlet>
<servlet-name>welcome</servlet-name>
<servlet-class>servlets.WelcomeServlet</servlet-class>
</servlet>
<!-- 2. 매핑 -->
<servlet-mapping>
<servlet-name>welcome</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
</web-app>
특정 url 요청이 오면 서블릿 컨테이너가 xml
설정 파일을 통해 해당 서블릿이 동작하게 해준다.
public class WelcomeServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username"); // 요청 값 꺼내기
String authorizationHeader = request.getHeader("authorization"); // 헤더 값 꺼내기
/* do something */
/*
* Request Method에 따라
* doGet(), doPost(), doPut(), doDelete()를 실행한다.
*/
response.setContentType("text/plain"); // 응답 형식 설정
response.setCharacterEncoding("utf-8"); // 응답 인코딩 설정
response.getWriter().write("hello " + username); // HTTP Response 메시지 작성
}
}
하지만 여전히 서블릿 간의 코드 중복이 발생하기 때문에, 이를 해결하기 위해 Front Controller 패턴
이 등장하게 된다.
Front Controller 패턴
은 모든 요청을 프론트 컨트롤러라는 하나의 서블릿에게 보내고, 프론트 컨트롤러는 각 요청에 맞는 Controller
를 찾아서 호출하는 역할을 한다. 그래서 공통 기능은 프론트 컨트롤러에서 처리하고, 서로 다른 코드들만 각 컨트롤러에서 처리하도록 할 수 있다.
이것은 Spring MVC
의 Dispatcher Servlet
과 같다.
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
</web-app>
이전의 순수 Servlet에서는 여러 개의 서블릿을 모두 web.xml에서 관리하였지만, Front Contrlloer 패턴
을 적용한 Spring MVC에서는 Dispatcher Servlet
1개만 web.xml에 관리해주면된다.
결국 모든 요청은 이 Dispatcher Servlet
으로 보내지게 된다.
DispatcherServlet
으로 간다.Handler(Controller)
를 탐색한다. (보통은 DefaultAnnotationHandlerMapping 방식으로 어노테이션으로 url과 매핑하는 방식 사용)Handler(Controller)
를 실행할 수 있는 HandlerAdapter
를 탐색한다.Handler Adapter
가 실제 Handler의 process 메소드를 실행한다.DispatcherServlet
에게 전달한다.DispatcherServlet
은 View 이름을 ViewResolver
에게 전달하고, ViewResolver
는 해당하는 실제 View
객체를 전달한다.DispatcherServlet
은 View
에게 Model
을 전달하고 화면 표시를 요청한다. (Model은 Controller에서 만든 데이터이다.) 이때, Model이 null이면 View를 그대로 사용하고, 그렇지 않으면 View에 Model 데이터를 렌더링한다.DispatcherServlet
은 View 결과(HttpServletResponse)를 클라이언트에게 반환한다.Hadnler(=Controller) 매핑에서 찾은 Handler로 바로 Controller를 실행하지 않고, 굳이 Handler Adapter를 통해 Controller를 실행하는 이유는 인터페이스가 다른 Controller 구현체들을 return 값이
ModelandView
로 통일된 Adapter로 실행하기 위해서이다.
즉, 인페이스가 다른 어떤 Controller이든, Adapter를 통해서 위 사진의 구조를 유지할 수 있다.
위 흐름은 @Controller 기준이며, @RestController의 경우 6번과 7번 과정이 생략된다. 즉, ViewResolver를 타지 않고 반환 값에 알맞는 MessageConverter를 찾아 응답 본문을 작성한다.