본문 바로가기
Java

[Java] 톰캣(WAS)에서의 URL 패턴과 관련된 이슈 / 자동으로 마지막 슬래시(/)가 추가되어서 리다이렉트 되는 경우

by bkuk 2023. 3. 26.

이슈 설명

제대로된 url 맵핑을 했는데도, 에러가 발생하는 경우
예) http://localhost:8080/qna 로 요청http://localhost:8080/qna/ 리다이렉트 되는 경우

 

URL 매핑 클래스

public class RequestMapping {
    private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
    private Map<String, Controller> mappings = new HashMap<>();
    
    void initMapping() {
        mappings.put("/", new HomeController());
        mappings.put("/users/form", new ForwardController("/user/form.jsp"));
        mappings.put("/users/loginForm", new ForwardController("/user/login.jsp"));
        mappings.put("/users", new ListUserController());
        mappings.put("/qna", new ForwardController("/qna/form.jsp"));

        logger.info("Initialized Request Mapping!");
    }

 

일반적인 각 URL 매핑에 따른 Controller 객체를 매핑하는 RequestMapping 클래스이다.

  • http://localhost:8080
  • http://localhost:8080/user/form
  • http://localhost:8080/user

위 요청에 대한 Controller 객체를 생성 후 해당 로직이 잘 작동하는 것을 확인했다.

문제는 /qna 에 대한 요청이다.

 

 

 

http://localhost:8080/qna 을 요청하면 url이 http://localhost:8080/qna/ 로 변경된다. 

 

 

무슨일 일까?

500 에러이다. 해당 요청에 대한 Controller가 없다는 에러가 발생했다.

 

 

 

개발자 도구를 통해서 더 자세히 확인해보자.

 

 

 

요청 URL은 제대로인데, 상태 코드를 보면 Redirect 되었다는 것을 확인할 수 있다. 분명 나는 ForwardController를 통해서 /qna/form.jsp을 응답으로 전달하라고 했다.. 하지만 /qna/로 리다이렉트 되었고 당연히 /qna/를 매핑하지 않았기 때문에 500에러가 발생했다.

 

 

 

웹 개발을 하다보면 URL 패턴과 관련된 이슈가 발생할 수 있다.
따라서 디버깅과 로그 분석 등을 통해 문제를 찾아내고 수정하는 능력도 필요하다. 

 

그렇다면 어떻게 해결할까?

 

URL 패턴이 중요한 역할을 하므로, URL 패턴을 명확하게 정의하는 것이 좋다. 예를 들어, 모든 요청에 대해서 마지막 슬래시(/)를 제거하고, 매핑하는 것이 좋은 방법 중 하나이다.

 

예를 들면, openai.com/chat과 openai.com/chat/ 요청에 대한 응답이 같게 하도록 구현하는 것이 좋다.이렇게 함으로써 사용자들은 요청하는 URL의 슬래시 유무에 상관없이 항상 일관된 응답을 받을 수 있기 때문입니다. 또한, 검색 엔진 등에서도 슬래시 유무에 관계없이 동일한 페이지로 간주할 수 있기 때문에 SEO 측면에서도 이점이 있습니다.

 

Java 코드로 구현한다면 아래와 같은 흐름에 정규식을 활용하면 된다.

  1. 사용자로부터 요청 URL을 받는다.
  2. 받은 요청의 마지막 슬래쉬를 제거한다. 제거하는 방법으로는 replaceAll() 메서드를 활용한다. 이를 정규식으로 구현한다.
  3. 정규식을 거친 URL에 대한 Controller를 맵핑한다.

 

DisPatcherServlet 클래스의 service 메서드 

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestUri = req.getRequestURI();
        
        if( requestUri.charAt( requestUri.length()-1 ) == '/' ) {
        	requestUri = requestUri.substring(0, requestUri.length() - 1);
        }
        
        logger.debug("Method : {}, Request URI : {}", req.getMethod(), requestUri);

        Controller controller = rm.findController(requestUri);
        try {
            String viewName = controller.execute(req, resp);
            move(viewName, req, resp);
        } catch (Throwable e) {
            logger.error("Exception : {}", e);
            throw new ServletException(e.getMessage());
        }
    }

댓글