프론트 컨트롤러(=디스패처 서블릿)

Jan 31, 2024
프론트 컨트롤러(=디스패처 서블릿)

프론트 컨트롤러가 필요한 이유

`<%@ page import="java.time.LocalDateTime" %>` `<%@ page contentType="text/html;charset=UTF-8" language="java" %>` 같은 공통 로직을 다룰 때, 서블렛을 이렇게(앞에서처럼) 다 따로 만들면 유지보수가 너무너무 힘들다. (ex. 서블렛이 100개정도 있다고 가정해보면... 100개를.. 하나하나 다...? 너무 힘들겠지) 때문에 프론트 컨트롤러를 만들어 단일 진입점 역할을 수행하도록 시킨다. 외부에서 요청이 들어올 때, 프론트 컨트롤러를 통해 클라이언트로부터 들어오는 모든 요청을 처리하고(받아들이고), 필요에 따라 리다이렉션(상태코드 302) or 라우팅할 것! 때문에 프론트 컨트롤러는 굳이 페이지(디자인)가 있을 필요가 없다. 서블릿으로 만들 것임! (다 리다이렉션 or 라우팅해서 다른 페이지로 이동시킬 거니까?) 리턴할 화면이 없기 때문에 jsp로 만들 필요가 없다! 그래서 서블렛으로 만든다!!
 

프런트 컨트롤러 (=디스패처 서블렛) 만들기

notion image
config - DispatcherServlet 생성
 

 
notion image
("/*")는 모든 요청을 일치시키는 패턴이다. 리다이렉션이 적용되지 않고 모든 요청이 해당 패턴에 일치하게 되어버린다. 때문에 '특정한 요청'을 처리하기가 어렵다. (원하는 경로(?)를 잡을 수 없는 문제가 발생!) "main.jsp" 요청이 다른 핸들러(컨트롤러)로 전달되지 않고 계속해서 "/*" 패턴에 일치하게 될 수 있다. 이런 경우에는 "/*" 패턴을 사용하지 않고, "main.jsp"와 같은 특정한 요청을 처리하는 패턴을 사용해야 한다.
/join-form 해도 /* 가 잡히고, /main.jsp 해도 /* 가 잡히고...

그래서 우리는…

notion image
("*.do") 로 잡았다. 만약, http://localhost:8080/user.do 라고 하면 이쪽으로 연결될 것!
 

모든 요청은 이제 디스패처 서블렛이 받을 것

join-form.do 든 join.do 든... 주소창에 때리면 무조건 디스패처 서블릿이 때려짐
notion image
이렇게... 디스패처 서블릿(프론트 컨트롤러)가 라우터 역할을 하는 모습
 

[ 주소창에 join-form.jsp로 요청해보자 ]

notion image
notion image
공통 로직 나오고, if문 때려졌죠? 모든 요청이 디스패처 서블렛으로 들어오고, jsp를 찾아주는 것 코드!!
notion image
서블렛을 다 나눴던 걸, 디스패처 서블렛 (프런트 컨트롤러)에 다 모아서 라우팅 시킴
 

다이렉트 요청 막아버리기 (WEB-INF)

다이렉트 요청은 웹 애플리케이션의 공통 로직을 우회하여 특정한 기능이나 데이터에 직접 접근하는 것을 의미. 누군가 악의적으로 이용할 수 있기 때문에... (=문지기가 필터 역할을 제대로 못함) 이를 방지하기 위해 다이렉트 요청을 막고, 공통 로직을 거쳐야만 웹 애플리케이션의 기능을 사용할 수 있도록 해야한다
notion image
프런트 컨트롤러를 거치지 않고 직접 JSP 파일로 요청이 되었다. 다이렉트 요청...! 안되게 막아야한다.
 

[ 프런트 컨트롤러를 제외한 나머지는 보안 폴더 WEB-INF에 넣어라 ]

notion image
WEB-INF에 board랑 user 패키지를 넣는다. 그러면 이제 외부에서 접근이 불가능해진다! (WEB-INF가 보안 폴더이기 때문)
notion image
notion image
똑같이 localhost:8080/board/main.jsp라고 요청을 보냈지만... 다이렉트 요청이 막혔다! 모든 외부 접근을 막아버리기 때문에 sendRedirect도 막혀버렸다.
💡
jsp를 바로 호출하지 못하게 하려고 보안 폴더인 web-inf에 넣음
무조건 디스패처를 통해서 가라고. (거기서 공통 로직 처리할거니까) web-inf에 안 넣으면 공통 로직을 안 거치고 호출할 수 있으니… 막아버리는 것 외부의 접근을 모두 막기 때문에 .do 접근과 경로 접근을 모두 막아버린다. → 외부 접근이 불가능하니, 외부 요청인 리다이렉션 안먹음~!! 그래서, WEB-INF에 넣었으면 getRequestDispatcher()을 사용
 

("*.do") 있는 전체 코드

package com.example.userapp.config; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; // FrontController @WebServlet("*.do") public class DispatcherServlet extends HttpServlet { // /join.do @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 공통로직 System.out.println("common logic ~~~~~"); resp.setHeader("Content-Type", "text/html; charset=utf-8"); // 2. 분기 String uri = req.getRequestURI(); System.out.println(uri); // 3. 라우터 if(uri.equals("/join-form.do")){ //resp.sendRedirect("/WEB-INF/user/join-form.jsp"); req.getRequestDispatcher("/WEB-INF/user/join-form.jsp").forward(req, resp); }else if(uri.equals("/join.do")){ //resp.sendRedirect("/WEB-INF/user/join.jsp"); req.getRequestDispatcher("/WEB-INF/user/join.jsp").forward(req, resp); }else if(uri.equals("/main.do")){ //resp.sendRedirect("/WEB-INF/board/main.jsp"); req.getRequestDispatcher("/WEB-INF/board/main.jsp").forward(req, resp); }else{ resp.setStatus(404); resp.getWriter().println("잘못된 페이지를 요청하셨어요"); } } }
 

Redirection = 외부 요청 (SendRedirect)

notion image
notion image
샌드리다이렉트(SendRedirect)는 외부 요청이다. 클라이언트가 서버에 어떤 요청을 하면 서버에서 클라이언트에게 특정 URL로 리다이렉트 요청을 보내고, 클라이언트는 이 리다이렉트 요청을 받고, 해당 URL로 다시 요청을 보낸다 ex) 웹 페이지에서 로그인이 필요한 페이지에 접근하려고 할 때, 로그인되지 않은 상태라면 서버는 클라이언트에게 로그인 페이지로 리다이렉트 요청을 보낸다. 클라이언트는 이 요청을 받으면 로그인 페이지로 이동하여 로그인을 하고, 다시 원래의 페이지로 돌아와서 요청을 다시 보낸다.
즉, 샌드리다이렉트는 호출이 2번 일어난다. 최초에 클라이언트가 요청 -> 아파치가 받은 후, 내가 처리할 일이 아니다 판단 -> 톰캣에게 위임 -> HTTPServletRequest, HTTPServletResponse 객체 생성 -> 클라이언트에게 응답 (이쪽 url로 들어오세요, 라거나...?) -> 요청과 응답이 완료되면 1차 통신 종료 -> HTTPServletRequest, HTTPServletResponse는 반이중이라 사라짐 -> 다시 샌드리다이렉트로 클라이언트가 요청 보냄 -> 반이중이라 기존의 리퀘스트, 리스폰스는 사라지고 없음 (새로운 요청으로 처리)
💡
즉, 요청할 때마다 HTTPServletRequest, HTTPServletResponse를 만든다는 말
💡
요청을 받으면 응답에 location을 담아서 보내고
client는 받은 location으로 다시 요청을 보내는 형식
 

DispatcherRedirection = 내부 요청 (RequestDispatcher) = 포워딩

notion image
notion image
notion image
getRequestDispatcher 메서드는 서버 안에서 다른 자원(서블릿, JSP, 혹은 URL)에 대한 요청을 만들어준다. 이 요청은 클라이언트에 직접 전송되지 않고, 서버 내부에서만 처리된다. 그래서 이를 "내부 요청"이라고 부른다. 내부 요청을 생성하기 위해 getRequestDispatcher 메서드는 요청할 자원의 경로나 이름을 받는다. ex) 다른 서블릿에 대한 내부 요청을 만들고 싶으면, 그 서블릿의 경로나 이름을 getRequestDispatcher 메서드에 전달하면 됨 내부 요청을 생성하면, 서버는 해당 자원에 대한 처리를 진행하고 응답을 생성한다. 그러나 클라이언트는 이 내부 요청에 대해서는 알지 못하며, 최종적인 응답만을 받는다.
notion image
💡
때문에, 이런 네트워크 헤더창을 보면, 요청이 join 한번만 간 걸 확인할 수 있다 외부 요청시에는 302 페이지, 200 페이지 둘 다 뜨는데 내부 요청으로 하면 바로 … join 저런 창이 한번만 뜬다구요 …
 

 
리퀘스트,리스폰스 다 만들어지면 내부적으로 WEB_INF파일에 있는 JSP파일을 다시 찾아야한다 그럼 다시 가서 식별자로만 다시 때림 포워드에다가 내용을 들고 가서 다시 만들어지는 애한테 덮어씌워버림 즉, 내부에서 재요청을 해서 http 리퀘스트, http 리스폰스를 덮어씌우는 것 이게 포워드. 그래서 마치 한번 요청한 것 처럼 되는 것
 

[ 내부 요청 시 장점 ]

클라이언트에게 내부 구조를 감추고 최종 응답만을 전송함으로써 보안을 강화할 수 있다. (내부적으로 어떤 자원이 사용되었는지에 대한 정보를 감출 수 있어서!) getRequestDispatcher 메서드를 통하면 클라이언트는 서버에 요청을 보내고, 서버는 내부적으로 디스패처를 통해 요청을 처리한다. 때문에 클라이언트는 디스패처의 존재를 알지 못하고, 디스패처에 직접적인 접근을 할 수 없다.
💡
"디스패처에 직접적인 접근을 할 수 없다" = 클라이언트가 디스패처에 대해 직접적인 다이렉트 요청을 할 수 없다는 것을 의미
 

언제 리다이렉션을 쓰고 언제 디스패처 리다이렉션을 쓸 지?

[ 샌드 리다이렉션의 경우 ]

주로 외부 URL이나 다른 도메인으로의 이동, 다른 웹 애플리케이션으로의 이동에 사용 (클라이언트의 요청이 완전히 새로운 URL로 전환)

[ 디스패처 리다이렉션 ]

서버 내부에서 다른 서블릿이나 JSP로의 이동 같은 웹 애플리케이션 내에서의 페이지 전환에 사용
 
💡
둘의 차이는… DispatcherRedirection은 요청이 한방에 끝난다는 것!
 

Dispatcher Redirection 으로 바꾸기

join-form.do로 회원가입을 요청하면 내부적으로는 해당 요청을 처리하기 위해 서블릿이나 컨트롤러가 호출된다. (공통로직은 서블릿이나 컨트롤러에서 처리) 이후, 회원가입이 완료되면 join.jsp는 main.do로 리다이렉션을 수행한다. 이렇게 하면 회원가입 후에는 사용자가 메인 페이지(main.do)로 이동하게 된다. 그러니까...
notion image
이 코드 바꾸고, join.jsp에서
notion image
/main.do 로 바꿔줘야겠지...?
 

프론트 컨트롤러 패턴 (=CV 패턴) / MVC 패턴

웹 애플리케이션에서 사용되는 디자인 패턴 중 하나 이 패턴에서 프론트 컨트롤러(Front Controller)는 클라이언트의 모든 요청을 중앙 집중적으로 처리(공통된 로직을 한 곳에서 처리함)하는 컨트롤러 역할을 수행한다. 프론트 컨트롤러는 클라이언트의 요청을 받고, 공통적인 작업을 처리한 뒤에 해당 요청에 맞는 적절한 뷰(View)를 찾아서 응답을 생성한다. (이때 뷰 파일은 HTML 파일이나 JSP 파일 등을 의미)
💡
프런트 컨트롤러가 뷰를 찾는 패턴이라서 컨트롤러-뷰(Controller-View) 패턴 또는 CV 패턴이라고도 함
💡
여기서 데이터 베이스까지 연결되면 MVC(Model-View-Controller) 패턴 MVC에서 리퀘스트 디스패처(디스패처 리다이렉션)는 프론트 컨트롤러에서 컨트롤러를 찾아주는 역할을 수행한다. 즉, 리퀘스트 디스패처가 없으면 클라이언트의 요청을 적절한 컨트롤러에게 전달할 수 없으므로, 리퀘스트 디스패처는 MVC 패턴에서 필수적인 기술! M = 데이터베이스 접근하는 DAO 코드. MVC
💡
강제성이 부여되야만 패턴이라고 한다
 

TIP!

💡
프런트 컨트롤러 → 스프링에서는 디스패처 서블릿이라고 함
FrontController(라우팅 역할만 하는 서블릿)는 jsp로 만들 필요가 없다. return할 화면이 없기 때문. 그래서 servlet으로 만든다 jsp를 쓰는 이유는 html 코드를 편하게 쓰기 위함 즉, 서블릿은 html코드를 쓸 필요 없으니 디자인을 할 필요도 없다
응답이 되면 리퀘스트가 날아감 > stateless 안날아가면 statefool
[ 리다이렉션 & 리퀘스트 디스패처를 헬스장으로 생각해보자 ] 클라이언트가 헬스장 가서 도착 내가 신발이랑 수건(=바디데이터) 들고 헬스장 도착 헬스장에서 가방을 하나 주잖아. 이게 바로 리퀘스트 객체 이거 들고 다 놀고 집에 갈 때 되면 헬스장에서 가방 회수하잖아? 내가 아까 쓴 그 가방 주세요 하면 날 기억못함 니가 누군데ㅠㅠ 하면서 새로운 가방을 줄것임 (리다이렉션) 만약 내부적으로 위치를 이동시키고 싶으면 b헬스장에 너네 헬스장 가방 들고 가도 되겠니? 하는게 포워딩 ...그런 느낌
 
 
Share article

codingb