일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- tanstackquery
- JPA
- component
- JavaSpring
- Chakra
- designpatterns
- javascript
- typescript
- Gin
- hook
- frontend
- go
- Redux
- css
- RTK
- 웹애플리케이션서버
- storybook
- React
- satisfiles
- Spring
- ReactHooks
- 티스토리챌린지
- test
- backend
- react-hook-form
- java
- 오블완
- golang
- springboot
- Today
- Total
bkdragon's log
[go] http 통신 과정 본문
net/http를 활용한 golang 서버에서 요청을 받아서 처리하는 순서를 간단하게(디테일 X) 살펴보려고 한다.
http 패키지의 HandleFunc로 request handler를 등록하고 ListenAndServe로 실행하면 요청에 맞는 handler가 실행된다.
http.HandleFunc("/helloworld", helloWorldHandler)
http.ListenAndServe(fmt.Sprintf(":%v", port), nil)
ListenAndServe 함수는 다음과 같다.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
여기서 나오는 Handler 인터페이스는 잘 기억해두자.
Server 구조체를 생성해서 ListenAndServe를 호출한다. (Server는 HTTP 서버의 설정과 동작을 정의하는 구조체)
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
net.Listen을 통해 TCP 리스너를 생성하고, 요청을 받아들이기 위한 Serve 함수를 호출한다.
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
if srv.shuttingDown() {
return ErrServerClosed
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
Serve 함수는 HTTP 설정, Context 생성 등을 작업한 후, for 문을 통해 지속적으로 새로운 요청을 받아들이는 작업을 수행한다.
l.Accept()에서 블로킹 되어 요청을 기다리고 있다가, 요청이 들어오면 처리한다.
go c.serve(connCtx)
각 연결은 고루틴으로 처리된다. 고루틴은 가벼운 스레드로, 각 연결을 비동기적으로 처리하게 된다.
func (c *conn) serve(ctx context.Context) {
// ... 생략
serverHandler{c.server}.ServeHTTP(w, w.req)
// ... 생략
}
핵심은 ServeHTTP 함수다. 이 함수에서 실제로 핸들러가 실행되며, 요청을 처리한다.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
serverHandler라는 구조체를 생성해서 ServeHTTP를 실행한다. 여기서 Server 구조체에 있는 Handler를 확인하고, 없다면 DefaultServeMux를 사용하게 된다. 그리고 이 handler의 ServeHTTP를 실행한다.
맨 위로 돌아가서 ListenAndServe 호출할 때 handler를 nil로 넘겼으므로, sh.srv.Handler는 nil이고, 결국 DefaultServeMux를 사용하게 된다.
DefaultServeMux는 ServeMux 타입의 전역 인스턴스다. net/http에서 제공하는 기본 라우터 역할을 한다.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
var h Handler
if use121 {
h, _ = mux.mux121.findHandler(r)
} else {
h, r.Pattern, r.pat, r.matches = mux.findHandler(r)
}
h.ServeHTTP(w, r)
}
ServeMux의 ServeHTTP는 들어온 요청에 맞는 request handler를 찾아서 ServeHTTP를 실행한다. 여기서 라우팅이 일어난다.
지금까지 Handler라는 용어가 계속 등장했는데, Handler 인터페이스가 어떻게 동작하는지 살펴보자.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
serverHandler, ServeMux 둘 다 ServeHTTP를 가지고 있으므로, 이들은 Handler 인터페이스를 만족하는 것이다. 그리고 ServeMux는 우리가 등록한 request handler의 ServeHTTP를 실행한다.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
여기서 request handler는 HandlerFunc 타입의 함수인데, 이 함수도 ServeHTTP 메서드를 가지고 있고, 자기 자신을 호출하는 역할을 한다.
h.ServeHTTP(w, r)
이제 이 코드가 우리가 작성한 request handler를 실행하는 이유가 명확해졌다.
gin에서의 동작
gin 프레임워크는 net/http 기반으로 만들어졌고, 요청을 처리하기 전 사용할 미들웨어와 라우팅을 더욱 편하게 해준다.
Run 메서드를 통해 서버를 실행할 수 있다.
r := gin.Default()
r.Run(":8000")
Run 메서드는 다음과 같다.
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
ListenAndServe를 볼 수 있다. 근데 이번에는 라우터 역할을 하는 핸들러를 넘긴다.
func (engine *Engine) Handler() http.Handler {
if !engine.UseH2C {
return engine
}
h2s := &http2.Server{}
return h2c.NewHandler(engine, h2s)
}
이 Handler는 engine을 반환하는데, 이 engine이 결국 ServeHTTP를 구현하는 구조체다.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
engine.handleHTTPRequest(c)에서 요청을 처리한다.
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
if engine.HandleMethodNotAllowed {
allowed := make([]string, 0, len(t)-1)
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
allowed = append(allowed, tree.method)
}
}
if len(allowed) > 0 {
c.handlers = engine.allNoMethod
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
여기서 중요한 부분은 Next() 함수다. 이 함수는 미들웨어와 핸들러 체인을 순차적으로 실행시킨다.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
gin.Context의 handlers에 미들웨어와 request handler가 함께 들어있고, Next()는 이들을 순서대로 호출하며 요청을 처리한다.
'golang' 카테고리의 다른 글
[gin] Clean Architecture (1) | 2024.09.20 |
---|---|
[gorm] 다형성 관계 (0) | 2024.09.11 |
[gin] JSON (1) | 2024.09.08 |
[gin] ShouldBindJSON (0) | 2024.09.08 |
[go] io 패키지 (5) | 2024.09.07 |