-
[Servlet] 포워딩(Forward)과 리다이렉트(Redirect)JAVA 2022. 7. 11. 22:53
현재 작업중인 페이지에서 다른 페이지로 이동할 때, 포워드 방식과 리다이렉트 2가지의 방법이 있다.
각각의 방식을 어떻게 구현하고, 둘은 어떤 차이가 있는지 알아보았다.
1. 포워딩 Forward
포워딩은 응답을 만들어 낼 웹컴포넌트에 요청을 위임하는 방식이다.
여기서 웹 컴포넌트는 서블릿이 될 수도 있고, JSP가 될 수도 있고, 웹 컴포넌트라면 포워딩할 수 있다.
상황을 예를들어, 웹에서 사용자가 버튼을 클릭하면 내용을 변경하여 화면에 보여주려고 할 때,
비즈니스 로직을 수행 후 View로 포워딩하면 View에서는 해당 비즈니스 데이터를 가지고 응답문서를 만들어 내보낸다.
포워딩방식의 가장 큰 특징은 원래의 url을 변경하지 않는다는 점이다.
응답이 끝날 때 까지 요청정보를 그대로 가지고 있는다. 따라서 Request Scope에 올려놓은 데이터를 공유할 수 있다.
포워딩 방식 구현
... protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ... req.setAttribute("username", "Shu"); req.setAttribute("useraddress", "서울"); RequestDispatcher dis = req.getRequestDispatcher("/Response"); // 이동할 url dis.forward(req, res); } // service } // end class
RequestDispatcher 객체를 사용하고, forward메소드를 통해 전달할 수 있다.
요청을 보낼 서블릿에서 비즈니스 로직을 수행하고, 해당 요청을 알맞은 scope에 등록한 후 forward메소드의 매개변수에 지정하면 해당 url로 포워딩된다. 해당 코드는 request scope에 데이터를 등록했다.
... @WebServlet(name = "Response", urlPatterns = { "/Response" }) public class ResponseServlet extends HttpServlet { ... protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ... String username = (String) req.getAttribute("username"); String useraddress = (String) req.getAttribute("useraddress"); // 응답문서 내보내기 ... } // service } // end class
요청을 보낸 서블릿의 url로 매핑한 응답 서블릿에서 request scope의 데이터들을 그대로 쓸 수 있다. (아직 요청이 끝나지 않았기 때문)
이 서블릿에서 응답을 내보내면 하나의 요청이 완료된다.
2. 리다이렉트 Redirect
다음은 리다이렉트 방식이다. 이름에서부터 무언가 재전송할 것이라는 느낌이 든다.
리다이렉트 방식으로 페이지 이동 요청을 보내면, url을 해당 url로 바꿔서 이동한다. 즉, url의 변경이 일어난다.
따라서 req, res 객체도 새롭게 생성된다. 처음 요청을 보낸 req, res 객체와 완전히 다른 것이다. 이 점이 포워딩 방식과 가장 큰 차이점이다. 따라서 리다이렉트 방식을 사용하면 request scope에 등록된 데이터를 사용할 수 없다.
리다이렉트 방식 구현
... protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { req.setAttribute("username", "Shu"); req.setAttribute("useraddress", "서울"); res.sendRedirect("/ResponseRedirect"); } // service } // end class
리다이렉트방식은 response 객체에 sendRedirect()메소드를 사용한다. 매개변수로 이동할 페이지의 url을 입력하면 해당 url로 전송된다.
@WebServlet("/ResponseRedirect") public class ResponseRedirectServlet extends HttpServlet { ... protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // req.scope : null String username = (String) req.getAttribute("username"); String useraddress = (String) req.getAttribute("useraddress"); // 응답문서 보내기 ... } // service } // end class
응답을 받는 서블릿에서 요청받았을 때 수행할 로직을 작성한다.
해당 코드는 request scope에 올린 데이터를 가져올 수 있는지를 테스트하는 코드인데, 이전의 요청은 종료되고 새로운 객체를 생성하기 때문에 결과는 null이 된다.
// in requestServlet ... // session scope HttpSession sess = req.getSession(); sess.setAttribute("username", "Shu"); sess.setAttribute("useraddress", "서울"); // 전송파라미터 String queryStr = String.format("?name=%s&address=%s", URLEncoder.encode("홍길동", "utf8"), URLEncoder.encode("서울", "utf8")); log.info("queryStr: {}", queryStr); res.sendRedirect("/ResponseRedirect" + queryStr); // in responseServlet ... // session scope HttpSession sess = req.getSession(); String username = (String) sess.getAttribute("username"); String useraddress = (String) sess.getAttribute("useraddress"); // 전송파라미터 req.setCharacterEncoding("utf8"); String username = req.getParameter("name"); String useraddress = req.getParameter("address");
리다이렉트 방식에서도 공유데이터를 사용하고 싶으면 이렇게 하는 방법도 있긴 하다.
첫번째는 session scope에 올려놓는 것이고, 두번째는 아예 전송파라미터로 넘기는 방식이다.
리다이렉트 방식으로 요청을 보내면 특정한 http 상태코드를 가진다. 301(Moved Permanently)과 302(Found) 이다.
301(Moved Permanently)는 영구적으로 페이지 url을 이동시킨다는 뜻이고, 302(Found)는 일시적 이동이다.
작성한 코드는 sendRedirect()메소드가 302방식으로 보내기 때문에 302코드로 뜬다.
그러나 대부분의 상황에서는 검색엔진 최적화를 위해 301 방식을 사용하는 것이 맞고, 특수한 경우에만 302방식을 사용한다. 구글에서는 302방식을 너무 많이 사용하면 악용으로 간주해 패널티를 부과한다.
포워딩 VS 리다이렉트, 언제 써야할까?
두 방법의 차이는 얼추 알았는데, 그럼 이 방식들을 언제 써야할까?
우선 포워드 방식은 시스템에 변화를 주지 않는 단순 로직(조회, 검색 등)에 적합하다.
이 이유는 최초의 요청이 계속 유지되는 특징 때문이다.
만약 시스템에 변화를 주는 로직(예를들어 게시판 글 쓰기)을 수행할 때 포워드 방식으로 처리하면 사용자가 글을 쓰는 도중에 새로고침을 누를 경우 누를 때마다 요청이 전달되어 동일한 글이 여러개 생길 수 있다. 결제를 포워드 방식으로 구현할 경우엔 중복결제가 발생할 수 있다.
또는, url을 변경하지 않는다는 특징을 이용해 url이 외부로 노출되어서는 안되는 경우에도 포워드 방식을 사용한다.
리다이렉트 방식은 반대로 시스템에 변화를 주는 로직(게시판 글 쓰기, 회원가입, 결제 등)을 수행하는 데에 적합하다.
최초의 요청이 다음 url에서 유지되지 않기 때문에, 위와 같은 상황에서 새로고침을 여러번 누른다 해도 처음의 요청이 영향을 주지 않기 때문에 글이 여러번 등록되거나하는 잘못된 결과가 발생하지 않는다.
+) URL Encoding 유의사항
전송 파라미터로 값을 넘길 때, 한글은 url encoding처리를 해야한다고 위에 잠깐 언급했다.
영어와 숫자를 제외한 모든 문자들을 깨지지 않고 넘기려면 인코딩 처리가 필요하다. req의 setCharacterEncoding()이나 res의 setContentType()과 관계없이 따로 처리해줘야한다.
URLEncoder클래스의 정적메소드로 encode()함수가 있는데,
원래는 매개변수 없는 메소드까지 3개의 함수가 오버로딩 되어있었지만, 매개변수없는 메소드는 deprecated되었다.
첫번째 방법은 URLEncoder.encode("인코딩할 문자열", Charset charset);
String urlencodedStr = URLEncoder.encode(str, StandardCharsets.UTF_8);
두번째 방법은 URLEncoder.encode("인코딩할 문자열", "인코딩형식을 문자열로 입력 ");
String urlencodedStr = URLEncoder.encode(str, "utf8");
하면된다.
주의사항은 url 전체를 통채로 인코딩함수에 넣으면 url의 특수기호들(//, :, & 등등)까지 모두 인코딩시켜버리므로,
한글이 들어있는 부분만 인코딩시켜준 후 url에 합쳐서 전달해야한다.
import ... @Log4j2 @NoArgsConstructor @TestInstance(Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class URLEncodingDecodingTests { @Test @Order(1) @DisplayName("testURLEncoder") @Timeout(value=1000, unit=TimeUnit.MILLISECONDS) void testURLEncoder() throws UnsupportedEncodingException { log.trace("uestURLEncoder() invoked."); // String str = "한글"; // String str = "ABCdefg1234567"; // 영어나 숫자는 변동이없음 // String str = "ABC EFG"; // 공백은 +로 변함 // url전체를 한번에 인코딩하면 안됨 -> 특수기호들도 모두 인코딩시켜버림 String str = "http://localhost:8080/servlet?name=홍길동&age=23&address=서울"; String urlencodedStr = URLEncoder.encode(str, "utf8"); log.info("\t+ urlencodedStr: {}", urlencodedStr); // String urldecodedStr = URLDecoder.decode(urlencodedStr, "utf8"); String urldecodedStr = URLDecoder.decode(urlencodedStr, StandardCharsets.UTF_8); log.info("\t+ urldecodeStr: {}", urldecodedStr); } // testURLEncoder } // end class
영어와 숫자를 제외한 모든 문자를 인코딩하고, 공백은 +로 변경되는 것을 확인할 수 있다.
(ex. ABC DEF -> ABC+DEF)
인코딩한 문자열을 원래대로 디코딩 시켜주는 메소드도 있다.
URLDecoder클래스의 decode()함수를 사용하면 된다. 사용법은 인코드 메소드와 동일하고, 이때는 전체 url을 지정해줘도 제대로 디코딩된다.
'JAVA' 카테고리의 다른 글
[Servlet] 파일업로드시 현재날짜로 디렉토리 생성하기, UUID로 파일명 저장하기 (0) 2022.07.14 [Servlet] Scope 단위 (Application, Session, Request, Page) (0) 2022.07.12 이클립스 메이븐 프로젝트 초기세팅 (0) 2022.06.27 이클립스에서 Maven 프로젝트 생성하고 WAS 올리기 (0) 2022.06.21 Eclipse JEE 버전 환경설정 (0) 2022.06.21