본문 바로가기
프로젝트

*.do 없애고 깔끔한 HTTP API 만들기

by 멍멍구리 2022. 5. 9.

0. *.do 방식에 대한 지극히 개인적인 소견

 

요즘 생긴 취미는, 웹서핑을 할 때 URI를 유심히 보는 것이다. 쿼리스트링이 몇 개나 붙어있는지 리소스는 어떤 방식으로 표기하는지 보다 보면 은근 재밌다. 단순한 쿼리스트링에도 암호화가 돼있는 걸 보면 신기하고, 가끔 URI 만 봐도 어떤 프레임워크로 개발했는지도 보인다.

 

예비군 사이트 주소가 참 재밌는데,  yebigun 이라는 네이밍도 재밌고 뒤에 붙은 1 때문에 피씽 사이트 같기도 하다. 그리고 게시글의 주제인 .do extension이 URI에 포함되어 있다.

 

https://www.yebigun1.mil.kr/dmobis/index_main.do

 

이 .do 확장자는, 취미를 즐기다보면 꽤 자주 보이는데 뭔가 굉장히 보기 껄끄럽다. 단순히 RESTful하지 못하다던가, 오래되었기 때문이라는 이유만은 아닌 것 같은데, 이 꺼림직한 느낌은 왜 느껴지는 걸까? 그 이유에 대해서 나름대로 생각을 해봤다.

 

우선 extension을 통해 요청을 분기하다 보면 식별하기 어렵다. 계층화가 깔끔하게 이루어지기 어려워서 일관성이나 규칙이 있는 요청을 만들기도 어렵다.

 

 

하나의 사이트에 market 도메인과 city 도메인이 있을 경우 extension 방식이라면 이렇게 표현된다.

 

  • tistory.com/board.market
  • tistory.com/board.city 

 

extension 방식을 제거하면 아래처럼 표현된다.

 

  • tistory.com/city/board
  • tistory.com/market/board 

 

딱 봐도 두 번째 방식이 깔끔하다.

 

사람이 단어를 인지할 때는 당연히 앞에서부터 읽어가게 되고 city를 읽고 board를 읽으면, city 내부에 있는(계층적으로) 게시판이구나 하고 생각하게 되는데 extension 의 방식의 경우 대분류와 소분류 사이의 순서가 역전되어 있어 읽기 어려운 느낌이 있다.

 

혹은 servlet을 분기하는 목적이 아니라 리소스를 계층화 한 후에도 extension 방식을 쓴다면?

 

  • tistory.com/city/board.do
  • tistory.com/market/board.do

 

아무런 의미도 없는 do 확장자가 붙어 URI가 지저분하고 혼란스럽게 된다. 전혀 의미 없는 단어가 우리의 중요한 주소창에 들어가는 것이 얼마나 큰 손해일까? 수많은 사용자들이 저 의미 없는 do 때문에 사이트에 다시 접속하지 않을 수도 있을 것 같다.

 

10 line이 있는 메소드에서도 의미 없는 구문 한 줄을 줄이기 위해 공수를 들이는 개발자라면 특히나 사용자에게 바로 보여지는 한 줄의 주소에서 필요 없는 글자가 추가되기를 바라지 않을 것이다.

 

그렇다면, 내 개인적인 생각말고 이 extension 방식이 사용되는 이유는 무엇일까?

 

https://okky.kr/article/262795

 

OKKY | 초보자를 위한 .do 확장자의 역사

계정 짤릴 각오로 올립니다. 이유는 아래에 설명하겠습니다. 초보자분들이 자바웹(스프링/정부표준 등)에 입문하실 때 강사나 선배 개발자분들이 표준을 가르칩니다. 그때 가장 질리게 들을 확

okky.kr

 

이 게시글에 의하면 관습처럼 이어져왔다는 것이다. 생각보다 큰 이유는 없었다.

 

1. extension 방식 제거하기

회사에서 맡은 프로젝트에도 .do가 붙어 있었다. 정확히는 extension을 통해서 servlet을 분기하는 방식이었다. *.user로 들어오는 요청은 user_servlet에서 처리하고, *.do 는 admin_servlet 에서 처리하는.

 

프로젝트를 맡자마자 들었던 생각은 이걸 개선해서 "우아한" http api로 바꾸고 싶다는 것이었다. 거창하게 RESTful API라고 말하고 싶지만, 괜히 로이 아저씨한테 혼나고 싶지는 않다.

*그런 REST API로 괜찮은가(http://slides.com/eungjun/rest#/30)

 

전체 프로젝트를 개선하고 싶었지만 당장에는 어려운 일이었고, 내가 추가로 개발하는 feature 에 우선 도입해보려고 했는데 생각보다 신경 써야 할 부분이 많았다. web.xml 파일의 url-pattern mapping 만 바꿔주면 될 줄 알았는데! 

 

	<servlet-mapping>
		<servlet-name>some_servlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

 

우선 수정해야 할 부분은 servlet mapping을 등록하는 web.xml 임은 명확했다. 추가적으로 servlet을 생성하는 건 애초에 틀린 길일 것 같았고 추후에는 하나의 서블릿으로 모든 요청을 통합하고 싶은 생각도 있었기 때문에, 우선은 이 servlet-mapping에 내가 생각하는 요청을 추가하고자 했다.

 

Case 1 :

url-pattern mapping : "/"

 

"/"로 url을 연결하는 경우에는 js 파일이나 css 같은 정적 리소스 파일을 탐색하지 못했다. 그 이유는 톰캣 서버에 기본 설정으로 정적 리소스 파일을 처리하는 default servlet mapping이 "/"로 지정되어 있기 때문에 충돌해서 정적 리소스를 탐색할 수 없기 때문이다. 위의 경우 default servlet을 재정의 하여 사용하기 때문에 *.js나 *.img *.png 등을 다시 정의해주어야 한다.

(https://tomcat.apache.org/tomcat-9.0-doc/default-servlet.html)

 

<!-- defaultServlet "/"로 덮어쓰기 때문에 재정의 -->
	<servlet-mapping>
		<servlet-name>default</servlet-name>
		<url-pattern>*.js</url-pattern>
		<url-pattern>*.css</url-pattern>
		<url-pattern>*.jpg</url-pattern>
		<url-pattern>*.gif</url-pattern>
		<url-pattern>*.png</url-pattern>
		<url-pattern>*.html</url-pattern>
		<url-pattern>*.ico</url-pattern>
		<url-pattern>*.swf</url-pattern>
	</servlet-mapping>

 

! 웹 브라우저의 요청이 들어오면 Dispatcher Servlet이 URL을 처리할 Controller를 찾고, 없으면 해당되는 Default Servlet을 찾아서 예외적인 파일을 처리해주는 방식으로 동작한다고 한다.

(https://wiki.metawerx.net/wiki/DefaultServlet)

 

tomcat/conf/web.xml 을 열어보면 default servlet을 확인할 수 있다.

    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

 

하지만 위처럼 default servlet에 정적 리소스 확장자를 기입할 경우 모든 정적 리소스의 확장자를 예상할 수 없기 때문에 등록하지 않은 확장자를 가지는 리소스 파일은 찾을 수 없다.

 

그에 대한 해법으로는 servlet mapping을 지정한 해당 servlet의 xml 파일에서 mvc:resource를 이용해서 정적 파일의 디렉토리를 표기할 수 있으나, 회사 프로젝트에서는 정적 리소스들의 디렉토리가 흩어져 있어서 적용이 불가 했다. mvc:default-servlet-handler 로 정적 파일이 흩어져 있을 경우 자동 처리가 가능하다는 게시글을 보기도 했는데, 해당 프로젝트에서는 정상적으로 작동하지 않았다.

 

Case 2 : 

url-pattern mapping : "/*"

 

Case 1 과 비슷하게, 톰캣 서버의 기본 설정으로 jsp page를 처리하는 jsp servlet이 있는데, 해당 servlet의 mapping이 "*.jsp" 이기 때문에 이것이 충돌하여 jsp 페이지가 미응답 한다.

 

tomcat/conf/web.xml 을 열어보면 jsp servlet을 확인할 수 있다.

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

 

Case 3 : 

url-pattern mapping : "/market/*"

 

Controller에서 계층의 최상위 RequestMapping을 (value="market")으로 설정한 뒤 url mapping을 저렇게 설정하면 될 듯 했다. 

 

하지만 큰 문제가 있었다. url-pattern mapping은 servlet path를 mapping 하는 것이기 때문에 localhost/market/board 로 요청을 전송할 경우 404를 응답한다.

 

만약 Controller의 market/board의 정상 응답을 받고 싶다면 해당 설정에서 실제 요청은 localhost/market(서블릿 경로)/market(리퀘스트매핑 경로)/board 여야 한다. 

 

즉 servlet path와 request mapping 간의 간극을 해결해주어야 하는데, alwaysUseFullPath 라는 property 를 사용하면 이를 해결할 수 있다.

 

해당 요청을 처리하는 servlet의 xml 파일에서 위의 property 값을 true로 설정해준다.

 

	<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
		<beans:property name="alwaysUseFullPath" value="true"/>
	</beans:bean>

 

이 설정을 통해 localhost/market/board 요청에 정상적으로 해당 servlet이 mapping 되고, 동시에 RequestMapping도 동작한다.

 

2. 더 나아간다면,

최종적인 목표는 요청 마다 servlet을 분기해서 처리하는 것이 아니라 모든 servlet을 하나의 servlet으로 합친 뒤에  "/"로 servlet을 mapping 하고, 정적 리소스 파일의 디렉토리를 정리한 뒤 mvc:resource 로 디렉토리를 등록해서 운영하는 것이다.

 

요청 마다 servlet을 구분은 하지만 각각의 servlet을 까보면 결국 같은 동작을 하는 설정이 파일만 여러 개로 나뉘어져 있어 분기 하는 목적이 크게 보이지 않고 관리 포인트만 늘어나기 때문에. 실제로 이번에 한 servlet의 설정을 바꿔서 주소 형태를 바꾸는데 성공했지만, 다른 feature 개발 시에도 똑같이 설정을 하는 작업을 반복해야 한다. 

 

하지만 이 부분을 개선한다고 해서 눈에 보이는 성능 차이가 있는 것이 아니라 내가 온전한 기간 동안 개선에 리소스를 투자하는 것이 효율적인가? 하고 되묻는다면 생각의 여지가 있다.

 

그래도 모든 부분에서 개선하지는 못하더라도, 내가 유지보수하거나 새롭게 개발하는 부분에 있어서는 깔끔한 HTTP API로 만들어서 지속적으로 비율을 늘려가야겠다.

댓글