Go Web Programming: [03/04] Get into http Package
Get Into http Package
앞 절에서 Go 언어에서 Web 작업환경을 제공하는 것과 개발 흐름에 대해서 설명했습니다.
이 절에서는 http
패키지에 대해서 좀더 알아보도록 하겠습니다.
net/http
패키지의 핵심 프로세스는 어떻게 구현되어있는지 알아 보겠습니다.
Go의 net/http
에는 2개의 핵심적인 기능이 있습니다. 바로 Conn 과 ServeMux
입니다.
Conn의 goroutine
현재 일반적으로 쓰이는 http 기반의 웹서버와는 다르게, Go 언어는 멀티스레드와 고성능을 실현하기 위해
goroutines을 사용하여 Conn 이벤트
를 읽고 처리 합니다.
이 기능을 통해서 각각의 요청은 독립성을 유지 할 수 있습니다. 서로 차단하지 않고 효율적으로 네트워크 이벤트에 응답
할 수 있습니다. 이것은 Go 언어의 높은 효율을 보장 합니다.
Go가 클라이언트의 요청을 대기할 때는 다음과 같이 사용합니다.
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
클라이언트의 각 요청은 Conn
객체 하나를 생성하고 사용하는 것을 알 수 있습니다.
이 Conn 객체에는 해당 요청 정보가 저장되어 있습니다. 이것은 주요 목적의 handler로 전달하는 것이고,
해당 handler에서 원하는 정보를 읽을 수 있습니다. 이처럼 각 요청별 독립성을 보장 합니다.
ServeMux의 정의
앞 절에서 conn.server
대해 설명했을 때 내부적으로 http
패키지의 기본 루트를 호출하고 있었습니다.
이번에는 라우터
를 통해서 각 요청에대한 데이터를 백엔드 처리함수에 전달 합니다.
라우터
는 어떻게 구현되어 있을까요? 구조는 다음과 같습니다.
type ServeMux struct {
mu sync.RWMutex // 뮤텍스 요청이 멀티스레드로 처리 됩니다.
m map[string]muxEntry // 라우팅 규칙 하나의 string이 하나의 mux 엔티티에 대응 합니다.
hosts bool // 어떤 규칙에 host 정보가 포함되어 있는지
}
다음으로 muxEntry를 살펴보겠습니다.
type muxEntry struct {
explicit bool // 일치 여부 검증
h Handler // 라우팅 식과 연결된 handler
pattern string // 매칭 문자열
}
다음으로 Handler의 정의에 대해서 알아 보겠습니다.
type Handler interface {
ServeHTTP(ResponseWriter *Request) // 라우팅 기능 구현
}
Handler는 인터페이스
지만, 앞부분 중에서 sayhelloName()
함수는 ServeHTTP라는 인터페이스를
구현하지는 않았습니다. 그런데 어떻게 추가 할 수 있을까요?
원래 http 패키지에는 HandlerFunc
라는 형식이 정의되어 있습니다. 우리가 정의한 sayhelloName
함수는
바로이 HandlerFunc
가 호출 된 결과이며,이 형태는 기본적으로 ServeHTTP 인터페이스를 구현하고있는 것입니다.
즉, HandlerFunc(f)를 호출해서 강제로 f
를 HandlerFunc 타입으로 형변환
하고있는 것입니다.
이런 과정을 거쳐서 f
는 ServeHTTP 메소드를 갖게됩니다.
type HandlerFunc func (ResponseWriter *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r * Request) {
f(w, r)
}
라우터는 해당 라우팅 규칙을 저장 한 후에는 실제적으로는 어떻게 요청을 분류할까요?
다음 코드를 참고 하시기 바랍니다. 기본 라우터는 ServeHTTP
를 구현 합니다.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r * Request) {
if r.RequestURI == "*" {
w.Header()Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h , _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
위에서 보았듯이 라우터는 요청을받은 후 이값이 *
면 연결을 해제하고, 그렇지 않으면
mux.handler(r).ServeHTTP(w, r)
를 호출하여 해당 설정처리 Handler를 반환해서,
h.ServeHTTP(w, r)
를 실행 합니다.
즉, 원하는 라우팅 handler의 ServerHTTP 인터페이스에 대한 호출입니다.
mux.Handler(r)
는 어떻게 처리 하는것 일까요?
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
return RedirectHandler(p, StatusMovedPermanently), pattern
}
}
return mux.handler (r.Host, r.URL.Path)
}
func (mux *ServeMux) handler (host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
원래 이것은 사용자의 요청 된 URL과 라우터에 저장되는 map 따라 일치하고 있습니다. 매칭에 의해 저장되는
handler가 반환 될 즈음이 handler의 ServeHTTP 인터페이스가 호출되어 원하는 기능을 수행 할 수 있습니다.
위의 소개를 통해 우리는 라우팅의 전체 프로세스를 이해했습니다. Go는 사실 외부에서 구현 된 라우터를 지원합니다.
ListenAndServe
의 두번째 인자가 외부의 라우터를 설정하기 위해 사용됩니다.
이것은 Handler 인터페이스의 하나로 외부 라우터 Handler 인터페이스를 구현하고 ServeHTTP에
사용자 정의 라우팅 기능을 구현할 수 있습니다.
아래 코드를 통해 자신 간단한 라우터를 구현하려고합니다.
package main
import (
"fmt"
"net / http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r * http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r * http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main () {
mux := &MyMux{}
http.ListenAndServe(": 9090", mux)
}
Go 코드의 실행 과정
http 패키지에 대한 분석을 통해서 전체 코드의 실행 과정을 정리해 보겠습니다.
먼저
Http.HandleFunc()
함수를 호출 합니다.
순서에 따라서 몇 가지 작업을 실행 합니다.- DefaultServeMux의 HandlerFunc를 호출 합니다.
- DefaultServeMux의 Handle을 호출 합니다.
- DefaultServeMux의 map[string]muxEntry에서 원하는 handler 및 라우팅 규칙을 추가 합니다.
- DefaultServeMux의 HandlerFunc를 호출 합니다.
다음으로
http.ListenAndServe(": 9090", nil)
함수를 호출 합니다.
이 과정도 몇가지 순서에 따라 처리합니다.- Server의 엔티티화
- Server의 ListenAndServe() 함수를 호출
- net.Listen(“tcp”, addr)함수를 호출한 후 포트를 감시
- for 루프를 시작하고 요청을 Accept
- 각 요청에 대해서 Conn을 하나 생성해 엔티티화하고, 각 요청에 대해서 goroutine 하나를 실행한 후 go c.serve() 서비스를 실행
- 각 요청의 내용을 로드한 후 w, err := c.readRequest()
- 처리 handler가 존재하는지 판단. 만약 handler가 설정되어 있지 않으면
(예제에서는 handler는 설정하지 않습니다) handler는 DefaultServeMux로 설정. - handler의 ServeHttp를 호출
- 이후 DefaultServeMux.ServeHttp 모드에 돌입
- request에따라 해당 handler를 선택하고, handler의 ServeHTTP에 돌입
mux.handler®.ServeHTTP(w, r)
- handler를 선택
A. 라우터가 request를 처리했는지 판단(루프에 의해 ServerMux의 muxEntry를 검사)
B. 만약 라우팅되면 해당 라우팅 handler의 ServeHttp를 호출
C. 라우팅되지 않으면 NotFoundHandler의 ServeHttp를 호출
- Server의 엔티티화