본문 바로가기
프로그래밍/Go

[채팅-1] go의 라우터, 미들웨어, 컨트롤러

by 동네로봇 2021. 4. 28.

개인 프로젝트로 go를 사용한 채팅 프로그램을 만들었다.

아래 블로그를 참고하였고, 필요한 부분은 수용하고 나와 맞지 않는 부분은 수정하여 프로젝트에 적용해보았다. 

 

thebook.io/006806/ch09/01/

 

Go 언어 웹 프로그래밍 철저 입문: 9.1 채팅 애플리케이션 만들기

 

thebook.io

 

프로젝트에서 사용한 주요 기술 및 언어

  • 서버 : golang
  • 회원 정보 관리 : mysql
  • 채팅방, 메세지 정보 관리 :  elastic search, kibana

서버에서 사용한 외부 오픈소스 

  • 라우터 : github.com/julienschmidt/httprouter
  • 미들웨어 : github.com/urfave/negroni
  • 뷰(렌더링) : github.com/unrolled/render
  • 세션 : github.com/goincremental/negroni-sessions
  • 웹소켓 : github.com/gorilla/websocket
  • 엘라스틱 서치 : github.com/olivere/elastic

프로젝트의 첫 작업으로, 프로그램을 실행시킬 수 있는 main.go 를 생성하고, REST API 를 사용할 수 있도록 먼저 라우터와 미들웨어를 구현하여 준다.  

 

라우터, 미들웨어, 컨트롤러를 기능별로 구분하기 전, 일단 main.go 에 모든 기능을 다 때려넣은 코드를 한 번 살펴보자.

주석에 쓰여진 번호대로 기능을 분리할 수 있다. 

func main() {
    router := httprouter.New()
    
    // 1번
    router.GET("/test", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
      /////// 2번 start////////
      var msg string
      var tmpl string

      for {
          if strings.HasPrefix(req.URL.RequestURI(), "/login") {
              goto CON_LABEL
          }

          u := GetCurrentUser(req)
          if u == nil {
              tmpl = "login"
              msg = "로그인이 필요한 서비스 입니다."
              break
          }

          if !u.Valid() {
              tmpl = "login"
              msg = "로그인 세션이 만료되었습니다. 다시 로그인 해주세요."
              DeleteSession(req)
              break
          }

          SetCurrentUser(req, u)
          goto CON_LABEL
	}

        ren := r.GetInstance()
        ren.HTML(rw, http.StatusBadRequest, tmpl, map[string]string{"alert": msg})
        return
        /////// 2번 end//////// 
        
CON_LABEL:
        fmt.Println("test 컨트롤러 입니다.")	// 3번
    })
}

 

 

 

// 1번

- 라우터 : 웹에서 호출한 url 주소가 어떤 코드를 실행시킬 것인지를 연결해주는 라우팅 기능

- java 의 servlet

 

// 2번

- 미들웨어 : 기능이 실행되기 전/후에 처리하려는 로직 (위 코드에서는 세션 확인)

- java 의 aop

 

// 3번

- 컨트롤러 : 각 api 에 대응하여 실행되는 컨트롤러 코드

- java 의 controller

 

 

/test 라는 하나의 api 에 대한 코드가 저렇게 길다.

만약 api가 수십, 수백개가 되는데도 위에 처럼 구현을 한다면 main.go 코드가 엄청나게 길어지고, 2번 미들웨어 코드는 모든 기능마다 반복되어 쓸데없는 코드의 반복을 야기한다.

 

작업의 편리성, 코드의 가독성, 모듈의 효율성을 높이기 위하여 각각의 기능을 분리한다. 

각 폴더의 구조는 아래와 같고, 이해하기 쉽도록 기존 코드를 조금 수정하였다.  

 

**** 참고 사항 ****

- 가독성을 높이기 위해 import 와 일부 코드는 글에 포함시키지 않았다. 

- GetInstance() 함수나 auth.go 파일의 ren 변수, session 등록 및 삭제 코드 설명은 제외하였다.

- 또한 negroni를 실행시키는 코드를 main.go 에 구현해주어야 한다.***

 

 

폴더 구조

  • routing
    • negro
      • negro.go
    • router
      • Controller
        •  test.go
        •  auth.go
      • router.go
  • main.go

 

router.go

///// 라우터 설정  //////
func startRoute() {
	router = httprouter.New()
	
    // api 등록
	router.GET("/test", Controller.Test)
}

 

negro.go

type negroniWrapper struct {
	negr      *negroni.Negroni
	webServer *http.Server
}

func (nw *negroniWrapper) InitNegroni() {

	nw.negr = negroni.Classic()
    
    /* 
    * 컨트롤러 코드 전후에 실행할 미들웨어 함수 설정
    * Controller 패키지의 Auth 를 핸들러 함수로 설정한다.
    */
	nw.negr.Use(negroni.HandlerFunc(Controller.Auth))

    /* 
    * negroni에 router.go 파일의 router를 핸들러로 등록함
    * router.go 의 router를 가져오는 것은 각자 코드 구조에 맞춰서 구현하면 된다.
    */
	router := r.GetInstance().Router
	nw.negr.UseHandler(router)

    /*
    * 웹서버 설정
    */
	nw.webServer = &http.Server{
		Addr:    ":8889",
		Handler: nw.negr,
	}

	go func() {
		nw.webServer.ListenAndServe()
	}()
}

 

test.go

func Test(w http.ResponseWriter, req *http.Request, p httprouter.Params) {
	fmt.Println("test 컨트롤러 실행!")
}

 

auth.go

func Auth(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
	var msg string
	var tmpl string

	for {
		if strings.HasPrefix(req.URL.RequestURI(), "/login") {
			next(rw, req)	// 컨트롤러를 실행한다.
			return
		}

		u := GetCurrentUser(req)
		if u == nil {
        	// 세션이 존재하지 않는 경우 컨트롤러를 실행하지 않고 login.tmpl 로 이동한다.
			tmpl = "login"
			msg = "로그인이 필요한 서비스 입니다."
			break	
		}

		if !u.Valid() {
        	// 세션 타임아웃인 경우 컨트롤러를 실행하지 않고 login.tmpl 로 이동한다.
			tmpl = "login"
			msg = "로그인 세션이 만료되었습니다. 다시 로그인 해주세요."
			DeleteSession(req)
			break
		}

		SetCurrentUser(req, u)
		next(rw, req)	// 컨트롤러를 실행한다.
		return
	}

	ren := r.GetInstance()
	ren.HTML(rw, http.StatusBadRequest, tmpl, map[string]string{"alert": msg})
}

 

 

위 코드의 실행 순서는 다음과 같다.

 

1. 웹에서 localhost:8889/test 호출

2. negroni 에 등록된 Auth 함수 실행하여 세션 확인

3-1. 세션 확인 성공 시, router 에 연결된 Test 함수 실행

3-2. 세션 확인 실패 시 'login.tmpl'로 이동하며, 등록한 msg 의 alert 가 뜬다.