Go 언어로 웹 서버를 구현하던 중 문득 라우팅을 어떻게 해주는지에 대해 궁금해졌다.
코드뚝딱 프로젝트를 진행할 때는 ExpressJS 프레임워크를 사용했고 라우팅 코드를 작성하는 경우 아래와 같이 작성하였다.
router.get('/javascript', function(...) {...});
최근 관심 있어 공부하게 된 Go 언어와 Gin 프레임워크로 웹 서버를 구현할 땐 라우팅 코드를 다음과 같이 작성하였다.
router.GET("/go", func(c *gin.Context) {...})
프레임워크를 사용하지 않을 땐 다음과 같이 해주었다. 프레임워크를 사용하지 않을 경우 Method를 구분하는 것은 직접 구현해야 했다.
http.HandleFunc("/go", func(w http.ResponseWriter ...) {...})
관련 자료를 찾아보기 전에 나의 생각은 이랬다.
들어온 요청 url path를 확인하여 /
로 split 한 후 맵핑해주면 끝 아닌가?
결론부터 말하면 그것도 여러 방법 중 하나였다.
자료를 찾아본 결과 웹 프레임워크나 언어별로도 다른 로직으로 처리를 하고 있었다는 것을 알게 되었고 성능과 기능 또한 조금씩 다른 것으로 보였다.
Go의 net/http 라이브러리의 코드를 살펴보며 어떤 방식으로 라우팅을 하고 있는지 알아보았다.
먼저 http.HandleFunc("/go", func ...)
코드에서 HandleFunc는 pattern string
파라미터와 handler func
파라미터를 받는다.
HandleFunc 함수에서 handler
가 nil
이 아닐 때 Handle 함수를 호출하여 defaultServeMux에 파라미터로 받은 pattern
과 handler
를 muxEntry로 등록해 준다. (해당 함수 코드는 링크를 통해 확인할 수 있다.)
defaultServeMux의 타입은 ServeMux로 아래 첨부한 코드를 보면 구조체라는 것을 알 수 있다.
- 코드를 자세히 보고 싶은 사람은 해당 링크로 들어가면 볼 수 있습니다.
HandleFunc 함수를 통해 등록을 마친 후 HTTP 요청이 들어오면 handler 함수 등 여러 함수를 거쳐 (해당 과정은 아직 이해를 못 했다) 등록해놓은 pattern과 요청이 들어온 path와 비교를 하여 handler를 알맞게 찾아주는 match 과정을 거친다. 해당 과정은 아래에서 볼 수 있다.
코드를 자세히 보고 싶은 사람은 해당 링크로 들어가면 볼 수 있습니다.
이때 mux.es은 위에 ServeMux 구조체를 선언할 때 주석으로 slice of entries sorted from longest to shortest.
라고 적어놓은 것을 볼 수 있는데 접두사인 pattern 중 가장 많이 일치하는 Entry를 반환하기 위해 긴 문자열부터 비교를 하는 것이다.
이때 mux.es에는 /
로 끝나는 pattern만 등록이 된다.
example ) http.HandleFunc("/golang/", handler)
- /golang/abcde 요청이 들어오면 /golang/ 패턴에 등록된 핸들러가 호출된다.
Reference