Go - HTTP 핸들러 쉽게 사용하기(Pat, Render, Negroni)

김영한·2021년 3월 17일
1

Go

목록 보기
5/6

출처


📚 Gorilla mux

기존에 gorilla mux를 사용하여 핸들러 처리를 해줄 때 코드를 살펴보자
Gorilla mux
go get github.com/gorilla/mux

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

type User struct {
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "soosungp33", Email: "soosungp33@gamil.com"}

	w.Header().Add("Content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

func addUserHandler(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}
	user.CreatedAt = time.Now()
	w.Header().Add("Content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

func main() {
	mux := mux.NewRouter()

	mux.HandleFunc("/users", getUserInfoHandler).Methods("GET")
	mux.HandleFunc("/users", addUserHandler).Methods("POST")
    
	http.ListenAndServe(":3000", mux)
}

이 코드를 pat을 사용한 코드와 비교해 차이점을 봐보자

📚 Gorilla pat

Gorilla pat
go get github.com/gorilla/pat

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/pat"
)

type User struct {
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "soosungp33", Email: "soosungp33@gmail.com"}

	w.Header().Add("Content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

func addUserHandler(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, err)
		return
	}
	user.CreatedAt = time.Now()
	w.Header().Add("Content-type", "application/json")
	w.WriteHeader(http.StatusOK)
	data, _ := json.Marshal(user)
	fmt.Fprint(w, string(data))
}

func main() {
	mux := pat.New()

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", addUserHandler)

	http.ListenAndServe(":3000", mux)
}

mux와 차이점은 메소드를 뒤에 붙이냐 앞에 붙이냐 차이이다.

📚 pat에서 템플릿 사용

템플릿을 사용해 핸들러를 전달해보자

templates 폴더를 하나 만들어주고 폴더 안에 hello.tmpl이라는 파일을 만들어준다.

다음 main.go 코드에 템플릿을 사용하는 핸들러를 추가해보자

...
func helloHandler(w http.ResponseWriter, r *http.Request) {
	tmpl, err := template.New("Hello").ParseFiles("templates/hello.tmpl")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(w, err)
		return
	}
	tmpl.ExecuteTemplate(w, "hello.tmpl", "soosungp33")
}
...
func main() {
	mux := pat.New()

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", addUserHandler)
	mux.Get("/hello", helloHandler)

	http.ListenAndServe(":3000", mux)
}

이렇게 템플릿을 사용해봤는데 매번 json쓰고 템플릿쓰고 하는게 반복되는 코드도 많고 귀찮다.
간단하게 해주는 패키지를 사용해보자

📚 Render

Render는 JSON, XML, HTML templates responses를 쉽게 해주는 패키지이다.
Render
go get github.com/unrolled/render

사용법은 되게 간단하다.
전역 변수로 render.Render를 포인터로 선언 후 사용하면 된다.
var rd *render.Render


핸들러 부분(addUserHandler)
(getUserInfoHandler도 비슷한 맥락, 에러부분도 확인해보기)

w.Header().Add("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))

->

rd.JSON(w, http.StatusOK, user)

템플릿 부분(helloHandler)

tmpl, err := template.New("Hello").ParseFiles("templates/hello.tmpl")
	if err != nil {
		rd.Text(w, http.StatusBadRequest, err.Error())
		return
	}
rd.HTML(w, http.StatusOK, "hello.tmpl", "soosungp33")
tmpl.ExecuteTemplate(w, "hello.tmpl", "soosungp33")

->

rd.HTML(w, http.StatusOK, "hello", "soosungp33")
// render가 등록할 때 확장자를 빼고 등록함 -> 따라서 hello.tmpl이 아닌 hello

위와 같이 엄청 간단하게 구현한 것을 볼 수 있다.


💊 추가 정보

⭐️ render 패키지는 기본적으로 확장자를 tmpl로 읽는다.

html로 된 파일을 읽고 싶으면 옵션을 넣어줘야한다.
main.go

rd = redner.New(render.Optioins{
	Extensions: []string{".html", ".tmpl"},
})

html확장자와 tmpl확장자를 둘다 읽어라 라고 옵션을 주면 된다.

⭐️ render 패키지는 기본적으로 templates에서 찾는다.

폴더명을 templates에서 template로 변경하고 싶을 때
main.go

re = render,New(render.Optioins{
	Directory: "template",
})

라고 옵션을 주면 된다.


전체 코드(템플릿 폴더 명을 template로 바꾼 상태)

package main

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/gorilla/pat"
	"github.com/unrolled/render"
)

var rd *render.Render

type User struct {
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "soosungp33", Email: "soosungp33@gmail.com"}

	rd.JSON(w, http.StatusOK, user)
}

func addUserHandler(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		rd.Text(w, http.StatusBadRequest, err.Error())
		return
	}
	user.CreatedAt = time.Now()
	rd.JSON(w, http.StatusOK, user)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	rd.HTML(w, http.StatusOK, "hello", "soosungp33")
	// render가 등록할 때 확장자를 빼고 등록함 -> 따라서 hello.tmpl이 아닌 hello
}

func main() {
	rd = render.New(render.Options{
		Directory:  "template",
		Extensions: []string{".html", ".tmpl"},
	})
	mux := pat.New()

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", addUserHandler)
	mux.Get("/hello", helloHandler)

	http.ListenAndServe(":3000", mux)
}

❗️ 옵션으로 레이아웃 사용하기

template 폴더안에 hello.html과 body.html을 만들어준다.
hello.html

<html>
    <head>
        <title>{{ partial "title" }}</title>
    </head>
    <body>
        Hello World {{ yeild }}
    </body>
</html>

템플릿 핸들러가 rd.HTML(w, http.StatusOK, "body", user)이기 때문에 body라는 이름에 영향을 받는다.
따라서 yeild 부분에는 그냥 body.html이 들어가고
partial "title" 부분은 title 뒤에 body가 붙어서 title-body.html이 들어간다.

body.html

Name: {{.Name}}
Email: {{.Email}}

title-body.html

Partial Go In web

이 3개를 읽어서 레이아웃을 넣자.
main.go 변경

...
func helloHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "soosungp33", Email: "soosungp33@gmail.com"}
	rd.HTML(w, http.StatusOK, "body", user)
}
...
func main() {
	rd = render.New(render.Options{
		Directory:  "template",
		Extensions: []string{".html", ".tmpl"},
		Layout:     "hello",
	})
	...
}

이런식으로 부분 템플릿을 만들어서 레이아웃에다가 조합할 수 있다.

📚 Negroni

마지막으로 Negroni는 HTTP 미들웨어이다.
Negroni
go get github.com/urfave/negroni
기본적으로 많은 부가기능을 제공한다.
ex) FileServer를 기본적으로 제공

public 폴더에 index.html을 생성
index.html

<html>
    <head>
        <title>Go in Web 11</title>
    </head>
    <body>
        <h1>Hello Go in Web</h1>
    </body>
</html>

main.go

func main() {
	mux := pat.New()

	mux.Handle("/", http.FileServer(http.Dir("public")))
    
	http.ListenAndServe(":3000", mux)
}

핸들러의 파일명이 생략되어 있으면 index.html or default.htm이라는 파일에 액세스한다.
따라서 실행하게 되면 public에 있는 index.html이 기본적으로 나타나게 된다.

이 코드를 negroni를 사용하면 어떻게 변경되는지 보자.

main.go

func main() {
	mux := pat.New()
    
	n := negroni.Classic()
	n.UseHandler(mux)

	http.ListenAndServe(":3000", n)
}

Classic은 기본 파일서버를 가지고 있고 추가로 로그 기능도 제공한다.
실행하면 FileServer를 사용할 때와 같은 결과를 얻고 추가로 로그도 터미널에 출력되는 걸 볼 수 있다.

0개의 댓글