본문 바로가기

Go

Go 웹서버관련 - Render, Pat, Negroni

 

https://www.youtube.com/watch?v=TjbUnpW7wyA

 

  • net/http 의 NewServeMux를 사용한 웹서버 구축 
package main

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

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

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

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

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("/users", getUserInfoHandler)

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

 

 

 

 

 

 

 

  • gorilla/mux를 사용한 웹서버 구축 
  • main 소스를 보면 mux.NewRouter()를 통하여 gorilla/mux를 사용한다. 위의 소스와 달리 같은 경로인데 Get,Post로 구분하였다. 
func main() {
	mux := mux.NewRouter()

	mux.HandleFunc("/users", getUserInfoHandler).Methods("GET")
	mux.HandleFunc("/users", postUserInfoHandler).Methods("POST")

	http.ListenAndServe(":8000", mux)

}

 

 

package main

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

	"github.com/gorilla/mux"
)

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

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

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

func postUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "tucket", Email: "tucket@naver.com"}

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

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

	mux.HandleFunc("/users", getUserInfoHandler).Methods("GET")
	mux.HandleFunc("/users", postUserInfoHandler).Methods("POST")

	http.ListenAndServe(":8000", mux)

}

 

 

 

 

 

  • gorilla/pat를 설치한다. 
go get github.com/gorilla/pat

 

 

  • gorilla/pat을 사용하면 아래와 같이 main()쪽만 수정하면된다. 아래와 같이 좀더 간단하게 표시가능하다.
func main() {
	mux := pat.New()

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

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

 

 

  • 아래는 gorilla/pat 의 전체 코드이다.
package main

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

	"github.com/gorilla/pat"
)

type User struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

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

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

func postUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "tucket_pat_post", Email: "tucket@naver.com"}

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

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

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

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

 

 

 

 

  • 위의  gorilla/pat 의 코드를 좀더 수정해보자

 

  • User 구조체에 CreatedAt을 추가하였다.
type User struct {
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

 

 

  • postUserInfoHandler 함수를 아래와 같이 변경하였다. Post전송시 입력은 json데이타를 보내야한다.
func postUserInfoHandler(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.Fprintf(w, string(data))
}

 

 

 

 

 

 

  • getHelloHandler 함수를 추가하였다. 해당 함수는 hello.tmpl파일을 사용한다.
func getHelloHandler(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", "Tucker")
}

 

 

  • 아래는 풀소스이다.
package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"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: "tucket_pat_get", Email: "tucket@naver.com"}

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

func postUserInfoHandler(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.Fprintf(w, string(data))
}

func getHelloHandler(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", "Tucker")
}

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

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", postUserInfoHandler)
	mux.Get("/hello", getHelloHandler)

	http.ListenAndServe(":8000", mux)

}

 

 

  • templates폴더의 hello.tmpl
<html>
<head>
<title>Hello Go in Web</title>
</head>
<body> 
Hello World {{.}}
</body>
</html>

 

 

 

 

 

 

 

 

  • REST api 를 하나 만들때마다 작업하는 코드가 많다. 해당 작업을 편하게 하기 위해서 render를 사용해보자
  • 일단 해당 패키지를 설치하자.
go get github.com/unrolled/render

 

 

아래의 코드를 ,

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

 

이렇게 한줄로 처리할수있다.

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

 

 

  • render를 사용하면 아래처럼 코드를 정말 간단하게 처리할수있다.
  • 위의 소스와 비교해보면 상당히 많이 코드가 줄었다.
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: "tucket_pat_get", Email: "tucket@naver.com"}
	rd.JSON(w, http.StatusOK, user)
}

func postUserInfoHandler(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 getHelloHandler(w http.ResponseWriter, r *http.Request) {
	rd.HTML(w, http.StatusOK, "hello", "Tucker")
}

func main() {
	rd = render.New()

	mux := pat.New()

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", postUserInfoHandler)
	mux.Get("/hello", getHelloHandler)

	http.ListenAndServe(":8000", mux)

}

 

 

  • 만약에 폴더가 templates가 아니고 template이고,  hello.tmpl 대신에 hello.html를 사용하려고 하면, 아래와 같이 rd를 수정하면 된다.
rd = render.New(render.Options{
    Directory:  "template",
    Extensions: []string{".html", ".tmpl"},
})

 

 

 

 

 

 

 

 

 

  • hello는 레이아웃이라 body로 이름을 바꾼다. 문자열 대신에 user객체를 보내서 Name,Email을 사용한다.
func getHelloHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "tucket_hello_get", Email: "tucket@naver.com"}
	rd.HTML(w, http.StatusOK, "body", user)
}

 

  • render.New()에서 기본 Layout을 지정한다.
	rd = render.New(render.Options{
		Directory:  "template",
		Extensions: []string{".html", ".tmpl"},
		Layout:     "hello",
	})

 

 

  • body.html이다. 
Name: {{.Name}}
Email: {{.Email}}

 

 

  • hello.html이다.
<html>
<head>
<title>{{ partial "title" }}</title>
</head>
<body> 
Hello World
{{ yield }}
</body>
</html>

 

 

 

  • 아래는 전체 소스이다.
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: "tucket_pat_get", Email: "tucket@naver.com"}
	rd.JSON(w, http.StatusOK, user)
}

func postUserInfoHandler(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 getHelloHandler(w http.ResponseWriter, r *http.Request) {
	user := User{Name: "tucket_hello_get", Email: "tucket@naver.com"}
	rd.HTML(w, http.StatusOK, "body", user)
}

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

	mux := pat.New()

	mux.Get("/users", getUserInfoHandler)
	mux.Post("/users", postUserInfoHandler)
	mux.Get("/hello", getHelloHandler)

	http.ListenAndServe(":8000", mux)

}

 

 

 

 

 

 

  • title-body.html을 생성하여 아래와 같이 내용을 넣는다.
Partial Go in Web

 

 

  • hello.html의  {{ partial "title" }} 부분에서 해당 title-body.html을 사용하여 아래와 같은 결과가 나온다.

 

 

  • main() 함수에 mux.Handle("/", http.FileServer((http.Dir("public")))) 를 추가하고,
  • public/index.html에 아래의 내용을 넣어서 실행해보자.
<html>
    <head>
        <title>Go in Web 11</title>
    </head>
    <body>
        <h1>Hello Go in Web</h1>
    </body>
</html>

 

 

 

 

 

 

Negroni 패키지

 

  • main()에서 mux.Handle("/", http.FileServer((http.Dir("public")))) 를 삭제하고 아래와 같이 변경한다.
  • 해당 파일을 실행하면 위와 결과가 같음을 알수있다.
  • negroni에서 파일서버의 기능을 기본으로 지원하기 때문이다.
n := negroni.Classic()
n.UseHandler(mux)

http.ListenAndServe(":8000", n)