๋งํฐ๋(Martini)๋ ๊ฐ๋ ฅํ๊ณ ์์ฌ์ด ์น์ ํ๋ฆฌ์บ์ด์ / ์น์๋น์ค๊ฐ๋ฐ์ ์ํ Golang ํจํค์ง์ ๋๋ค.
Go ์ธ์คํจ ๋ฐ GOPATH ํ๊ฒฝ๋ณ์ ์ค์ ์ดํ์, .go
ํ์ผ ํ๋๋ฅผ ๋ง๋ค์ด ๋ณด์ฃ ..ํ ... ์ผ๋จ server.go
๋ผ๊ณ ๋ถ๋ฅด๊ฒ ์ต๋๋ค.
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello, ์ธ๊ณ!"
})
m.Run()
}
๋งํฐ๋ ํจํค์ง๋ฅผ ์ธ์คํจ ํฉ๋๋ค. (go 1.1 ํน์ ๊ทธ ์ด์ ๋ฒ์ ผ ํ์):
go get github.com/go-martini/martini
์ด์ ์๋ฒ๋ฅผ ๋๋ ค ๋ด ์๋ค:
go run server.go
๋งํฐ๋ ์น์๋ฒ๊ฐ localhost:3000
์์ ๋์๊ฐ๊ณ ์๋ ๊ฒ์ ํ์ธํ์ค ์ ์์ ๊ฒ๋๋ค.
๋ฉ์ผ๋ง ๋ฆฌ์คํธ์ ๊ฐ์ ํด ์ฃผ์ธ์
๋ฐ๋ชจ ๋น๋์ค๋ ์์ด์.
ํน์ Stackoverflow์ ๋งํฐ๋ ํํฌ๋ฅผ ์ด์ฉํด์ ๋ฌผ์ด๋ด ์ฃผ์ธ์
GoDoc ๋ฌธ์(documentation)
๋ฌธ์ ๋ ์ ๋ถ๋ค ์์ด๋ก ๋์ด ์๋ค๋ ๊ฑด๋ฐ์ -_-;;; ๋๋ ํ๊ธ ์๋๋ฉด ๋ณด๊ธฐ๋ค ์ซ์ด! ์ด๋ฐ ๋ถ๋ค์ ์๋ ๋งํฌ๋ฅผ ์ฐธ์กฐํ์ธ์
- golang-korea
- ํน์ (RexK)์ ์ด๋ฉ์ผ๋ก ์ฐ๋ฝ์ฃผ์ธ์.
- ์ฌ์ฉํ๊ธฐ ์์ฒญ ์ฝ์ต๋๋ค.
- ๋น๊ฐ์ญ(Non-intrusive) ๋์์ธ
- ๋ค๋ฅธ Golang ํจํค์ง๋ค๊ณผ ์ ์ด์ธ๋ฆฝ๋๋ค.
- ๋๋ด์ฃผ๋ ๊ฒฝ๋ก ๋งค์นญ๊ณผ ๋ผ์ฐํ .
- ๋ชจ๋ ํ ๋์์ธ - ๊ธฐ๋ฅ์ถ๊ฐ ์ฝ๊ณ , ์ฝ๋ ๊บผ๋ด์ค๊ธฐ๋ ์ฌ์.
- ์ธ๋ชจ์๋ ํธ๋ค๋ฌ์ ๋ฏธ๋ค์จ์ด๊ฐ ๋ง์.
- ํ๋ฅ ํ ํจํค์งํ(out of the box) ๊ธฐ๋ฅ๋ค
- http.HandlerFunc ์ธํฐํ์ด์ค์ ํธํ์จ 100%
๋ฏธ๋ค์จ์ด๋ค๊ณผ ์ถ๊ฐ๊ธฐ๋ฅ๋ค์ martini-contrib์์ ํ์ธํด ์ฃผ์ธ์.
๋งํฐ๋๋ฅผ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ์ด์ฉํ์๋ ค๋ฉด, martini.Classic()๋ฅผ ์ด์ฉํด ๋ณด์ธ์. ๋ณดํต ์น์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ๋ ์ค์ ๋ค์ด ์ด๋ฏธ ํฌํจ๋์ด ์์ต๋๋ค.
m := martini.Classic()
// ... ๋ฏธ๋ค์จ์ด์ ๋ผ์ฐํ
์ค์ ์ ์ด๊ณณ์ ์ค๋ฉด ์์ฑํ๋ฉด ๋ฉ๋๋ค.
m.Run()
์๋๋ martini.Classic()์ ์๋์ผ๋ก ์ฅ์ฐฉํ๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ๋ค์ ๋๋ค.
- Request/Response ๋ก๊ทธ ๊ธฐ๋ฅ - martini.Logger
- ํจ๋ ๋ฆฌ์ปค๋ฒ๋ฆฌ (Panic Recovery) - martini.Recovery
- ์ ์ ํ์ผ ์๋น - martini.Static
- ๋ผ์ฐํ (Routing) - martini.Router
ํธ๋ค๋ฌ(Handlers)๋ ๋งํฐ๋์ ํต์ฌ์ ๋๋ค. ํธ๋ค๋ฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ ๊ฐ๋ฅํ ๋ชจ๋ ํํ์ ํจ์๋ค์ ๋๋ค.
m.Get("/", func() {
println("hello ์ธ๊ณ")
})
ํธ๋ค๋ฌ๊ฐ ๋ฐํ์ ํ๋ ํจ์๋ผ๋ฉด, ๋งํฐ๋๋ ๋ฐํ ๊ฐ์ http.ResponseWriter์ ์คํธ๋ง์ผ๋ก ์ ๋ ฅ ํ ๊ฒ์ ๋๋ค.
m.Get("/", func() string {
return "hello ์ธ๊ณ" // HTTP 200 : "hello ์ธ๊ณ"
})
์ํ์ ๋ค๋ฉด, ์ ํ์ ์ผ๋ก ์ํ์ฝ๋๋ ํจ๊ป ๋ฐํ ํ ์ ์์ต๋๋ค.
m.Get("/", func() (int, string) {
return 418, "๋ ์ฃผ์ ์์ผ!" // HTTP 418 : "๋ ์ฃผ์ ์์ผ!"
})
ํธ๋ค๋ฌ๋ค์ ๋ฆฌํ๋ ์
์ ํตํด ํธ์ถ๋ฉ๋๋ค. ๋งํฐ๋๋ ์์กด์ฑ ์ฃผ์
์ ์ด์ฉํด์ ํธ๋ค๋ฌ์ ์ธ์๋ค์ ์ฃผ์
ํฉ๋๋ค. ์ด๊ฒ์ด ๋งํฐ๋๋ฅผ http.HandlerFunc
์ธํฐํ์ด์ค์ 100% ํธํํ ์ ์๊ฒ ํด์ค๋๋ค.
ํธ๋ค๋ฌ์ ์ธ์๋ฅผ ์ ๋ ฅํ๋ค๋ฉด, ๋งํฐ๋๊ฐ ์๋น์ค ๋ชฉ๋ก๋ค์ ์ดํด๋ณธ ํ ํ์ ํ์ธ(type assertion)์ ํตํด ์์กด์ฑ์ ํด๊ฒฐ์ ์๋ ํ ๊ฒ์ ๋๋ค.
m.Get("/", func(res http.ResponseWriter, req *http.Request) { // res์ req๋ ๋งํฐ๋์ ์ํด ์ฃผ์
๋์๋ค.
res.WriteHeader(200) // HTTP 200
})
์๋ ์๋น์ค๋ค์ martini.Classic():์ ํฌํจ๋์ด ์์ต๋๋ค.
- *log.Logger - ๋งํฐ๋์ ๊ธ๋ฌ๋ฒ(์ ์ญ) ๋ก๊ทธ.
- martini.Context - http ์์ฒญ ์ปจํ ์คํธ.
- martini.Params - ๋ฃจํธ ๋งค์นญ์ผ๋ก ์ฐพ์ ์ธ์๋ฅผ
map[string]string
์ผ๋ก ๋ณํ. - martini.Routes - ๋ฃจํธ ๋์ฐ๋ฏธ ์๋ฏธ์ค.
- http.ResponseWriter - http Response writer ์ธํฐํ์ด์ค.
- *http.Request - http ๋ฆฌํ์คํธ.
๋งํฐ๋์์ ๋ฃจํธ๋ HTTP ๋ฉ์๋์ URL๋งค์นญ ํจํด์ ํ์ด์ ๋๋ค. ๊ฐ ๋ฃจํธ๋ ํ๋ ํน์ ๊ทธ ์ด์์ ํธ๋ค๋ฌ ๋ฉ์๋๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
m.Get("/", func() {
// ๋ณด์ฌ์ค ๋ด
})
m.Patch("/", func() {
// ์
๋ฐ์ดํธ ์ข ํด
})
m.Post("/", func() {
// ๋ง๋ค์ด๋ด
})
m.Put("/", func() {
// ๊ตํํด๋ด
})
m.Delete("/", func() {
// ์์ ๋ฒ๋ ค!
})
m.Options("/", func() {
// http ์ต์
๋ฉ์๋
})
m.NotFound(func() {
// 404 ํด๊ฒฐํ๊ธฐ
})
๋ฃจํธ๋ค์ ์ ์๋ ์์๋๋ก ๋งค์นญ๋๋ค. ๋ค์ด์จ ์๊ทธ์ ์ฒซ๋ฒ์งธ ๋งค์นญ๋ ๋ฃจํธ๊ฐ ํธ์ถ๋๋ค.
๋ฃจํธ ํจํด์ martini.Params service๋ก ์ก์ธ์ค ๊ฐ๋ฅํ ์ธ์๋ค์ ํฌํจํ๊ธฐ๋ ํ๋ค:
m.Get("/hello/:name", func(params martini.Params) string {
return "Hello " + params["name"] // :name์ Params์ธ์์์ ์ถ์ถ
})
๋ฃจํธ๋ ๋ณํ์(*)์ผ๋ก ๋งค์นญ ๋ ์๋ ์์ต๋๋ค:
m.Get("/hello/**", func(params martini.Params) string {
return "Hello " + params["_1"]
})
Regular expressions can be used as well: ์ ๊ท์๋ ์ฌ์ฉ๊ฐ๋ฅํฉ๋๋ค:
m.Get("/hello/(?P<name>[a-zA-Z]+)", func(params martini.Params) string {
return fmt.Sprintf ("Hello %s", params["name"])
})
์ ๊ท์์ ๊ดํ์ฌ ๋ ์์ธํ ์๊ณ ์ถ๋ค๋ฉด Go documentation์ ์ฐธ์กฐํด ์ฃผ์ธ์.
๋ฃจํธ ํธ๋ค๋ฌ๋ ์คํ์ ์์ ์ฌ๋ฆด ์ ์์ต๋๋ค. ํนํ ์ ์ ์ธ์ฆ์์ ์ด๋, ํ๊ฐ์์ ์ ์ ์ฉํ ์ฐ์ผ ์ ์์ฃ .
m.Get("/secret", authorize, func() {
// ์ด ํจ์๋ authorize ํจ์๊ฐ resopnse์ ๊ฒฐ๊ณผ๋ฅผ ์ฐ์ง ์๋์ด์ ์คํ ๋ ๊ฑฐ์์.
})
๋ฃจํธ๊ทธ๋ฃน์ ๋ฃจํธ๋ค์ ํ ๊ณณ์ ๋ชจ์ ์ ๋ฆฌํ๋๋ฐ ์ ์ฉํฉ๋๋ค.
m.Group("/books", func(r martini.Router) {
r.Get("/:id", GetBooks)
r.Post("/new", NewBook)
r.Put("/update/:id", UpdateBook)
r.Delete("/delete/:id", DeleteBook)
})
ํธ๋ค๋ฌ์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ง์ด๋ฃ์ ์ ์์๋ฏ์ด, ๊ทธ๋ฃน์๋ ๋ฏธ๋ค์จ์ด ์ง์ด๋ฃ๋๊ฒ ๊ฐ๋ฅํฉ๋๋ค.
m.Group("/books", func(r martini.Router) {
r.Get("/:id", GetBooks)
r.Post("/new", NewBook)
r.Put("/update/:id", UpdateBook)
r.Delete("/delete/:id", DeleteBook)
}, MyMiddleware1, MyMiddleware2)
์๋น์ค๋ ํธ๋ค๋ฌ์ ์ธ์๋ชฉ๋ก์ ์ฃผ์ ๋ ์ ์๋ ์ค๋ธ์ ํธ๋ค์ ๋งํฉ๋๋ค. ์๋น์ค๋ ๊ธ๋ก๋ฒ ํน์ ๋ฆฌํ์คํธ ๋ ๋ฒจ๋จ์๋ก ์ฃผ์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
๋งํ๋ ์ธ์คํด์ค๋ ์๋น์ค ๋งตํ์ ์ฝ๊ฒ ํ๊ธฐ ์ํด์ inject.Injector ์ธํฐํ์ด์ค๋ฅผ ๋ฐํํฉ๋๋ค:
db := &MyDatabase{}
m := martini.Classic()
m.Map(db) // ์๋น์ค๊ฐ ๋ชจ๋ ํธ๋ค๋ฌ์์ *MyDatabase๋ก์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
// ...
m.Run()
๋ฆฌํ์คํธ ๋ ๋ฒจ ๋งตํ์ ํธ๋ค๋ฌ์์์ martini.Context๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค:
func MyCustomLoggerHandler(c martini.Context, req *http.Request) {
logger := &MyCustomLogger{req}
c.Map(logger) // *MyCustomLogger๋ก์ ๋งตํ ๋จ
}
์๋น์ค์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ค ํ๋๋ ์๋น์ค๋ฅผ ์ธํฐํ์ด์ค๋ก ๋งตํ์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ๋๋ค. ์๋ฅผ๋ค์ด, http.ResponseWriter๋ฅผ ์นํ(override)ํด์ ๋ถ๊ฐ ๊ธฐ๋ฅ๋ค์ ์ํํ๊ฒ ํ๊ณ ์ถ์ผ์๋ค๋ฉด, ์๋์ ๊ฐ์ ํธ๋ค๋ฌ๋ฅผ ์์ฑ ํ์๋ฉด ๋ฉ๋๋ค.
func WrapResponseWriter(res http.ResponseWriter, c martini.Context) {
rw := NewSpecialResponseWriter(res)
c.MapTo(rw, (*http.ResponseWriter)(nil)) // ResponseWriter๋ฅผ NewResponseWriter๋ก ์นํ(override)
}
martini.Classic() ์ธ์คํด์ค๋ "public" ํด๋์์ ์๋ ํ์ผ๋ค์ ์ ์ ํ์ผ๋ก์ ์๋์ผ๋ก ์๋นํฉ๋๋ค. ๋ ๋ง์ ํด๋๋ค์ ์ ์ ํ์ผ ํด๋์ ํฌํจ์ํค์๋ ค๋ฉด martini.Static ํธ๋ค๋ฌ๋ฅผ ์ด์ฉํ์๋ฉด ๋ฉ๋๋ค.
m.Use(martini.Static("assets")) // "assets" ํด๋์์๋ ์ ์ ํ์ผ ์๋น.
๋ฏธ๋ค์จ์ด ํธ๋ค๋ฌ๋ http request์ ๋ผ์ฐํ ์ฌ์ด์์ ์๋ํฉ๋๋ค. ๋ฏธ๋ค์จ์ด ํธ๋ค๋ฌ๋ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ํธ๋ค๋ฌ๋ค๊ณผ๋ ๋ค๋ฆ ๋๋ค. ์ฌ์ฉ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ์ต๋๋ค:
m.Use(func() {
// ๋ฏธ๋ค์จ์ด ์๋ฌด ์ํ!
})
Handlers
๋ฅผ ์ด์ฉํ์ฌ ๋ฏธ๋ค์จ์ด ์คํ๋ค์ ์์ ์ปจํธ๋กค์ด ๊ฐ๋ฅํฉ๋๋ค. ๋ค๋ง, ์ด๋ ๊ฒ ์ค์ ํ์๋ฉด ์ด์ ์ Handlers
๋ฅผ ์ด์ฉํ์ฌ ์ค์ ํ ํธ๋ค๋ฌ๋ค์ ์ฌ๋ผ์ง๋๋ค:
m.Handlers(
Middleware1,
Middleware2,
Middleware3,
)
๋ฏธ๋ค์จ์ด ํธ๋ค๋ฌ๋ ๋ก๊น (logging), ํ๊ฐ(authorization), ์ธ๊ฐ(authentication), ์ธ์ , ์์ถ(gzipping), ์๋ฌ ํ์ด์ง ๋ฑ ๋ฑ, http request์ ์ ํ๋ก ์คํ๋์ด์ผ ํ ์ผ๋ค์ ์ฒ๋ฆฌํ๊ธฐ ์์ฃผ ์ข์ต๋๋ค:
// API ํค ํ์ธ์์
m.Use(func(res http.ResponseWriter, req *http.Request) {
if req.Header.Get("X-API-KEY") != "๋น๋ฐ์ํธ!!!" {
res.WriteHeader(http.StatusUnauthorized) // HTTP 401
}
})
Context.Next()๋ ์ ํ์ ํจ์์ ๋๋ค. ์ด ํจ์๋ http request๊ฐ ๋ค ์๋ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.๋ฐ๋ผ์ http request ์ดํ์ ์คํ ๋์ด์ผ ํ ์ ๋ฌด๋ค์ ์ํํ๊ธฐ ์ข์ ํจ์์ ๋๋ค.
// log before and after a request
m.Use(func(c martini.Context, log *log.Logger){
log.Println("request์ ์
๋๋ค.")
c.Next()
log.Println("requestํ ์
๋๋ค.")
})
๋งํฐ๋ ํธ๋ค๋ฌ๋ค์ martini.Env
๊ธ๋ก๋ฒ ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐํ๊ฒฝ์์๋ ํ๋ก๋์
ํ๊ฒฝ๊ณผ๋ ๋ค๋ฅด๊ฒ ์๋ํ๊ธฐ๋ ํฉ๋๋ค. ๋ฐ๋ผ์, ํ๋ก๋์
์๋ฒ๋ก ๋งํฐ๋ ์๋ณด๋ฅผ ๋ฐฐํฌํ์๊ฒ ๋๋ค๋ฉด ๊ผญ ํ๊ฒฝ๋ณ์ MARTINI_ENV=production
๋ฅผ ์ธํ
ํด์ฃผ์๊ธฐ ๋ฐ๋๋๋ค.
๊นํ์์ martini-contrib ํ๋ก์ ํธ๋ค์ ํ์๋ณด์ธ์. ๋ง์ฝ์ ๋ชป ์ฐพ์ผ์๊ฒ ์ผ๋ฉด, martini-contrib ํ์๋ค์๊ฒ ์ฐ๋ฝํด์ ํ๋ ๋ง๋ค์ด ๋ฌ๋ผ๊ณ ํด๋ณด์ธ์.
- [auth](https: //github.com/martini-contrib/auth) - ์ธ์ฆ์์ ์ ๋์์ฃผ๋ ํธ๋ค๋ฌ.
- binding - request๋ฅผ ๋งตํํ๊ณ ๊ฒ์ฌํ๋ ํธ๋ค๋ฌ.
- gzip - gzip ํธ๋ค๋ฌ.
- render - HTML ํ ํ๋ ์ดํธ๋ค๊ณผ JSON๋ฅผ ์ฌ์ฉํ๊ธฐ ํธํ๊ฒ ํด์ฃผ๋ ํธ๋ค๋ฌ.
- acceptlang -
Accept-Language
HTTP ํด๋๋ฅผ ํ์ฑํ ๋ ์ ์ฉํ ํธ๋ค๋ฌ. - sessions - ์ธ์ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ํธ๋ค๋ฌ.
- strip - URL ํ๋ฆฌํฑ์ค๋ฅผ ์์ ์ฃผ๋ ํธ๋ค๋ฌ.
- method - ํด๋๋ ํผํ๋๋ฅผ ์ด์ฉํ HTTP ๋ฉ์๋ ์นํ.
- secure - ๋ช๋ช ๋ณด์์ค์ ์ ์ํ ํธ๋ค๋ฌ.
- encoder - ๋ฐ์ดํฐ ๋ ๋๋ง๊ณผ ์ปจํ ํธ ํ์ฝ์์ํ ์ธ์ฝ๋ฉ ์๋น์ค.
- cors - CORS ์ํฌํธ๋ฅผ ์ํ ํธ๋ค๋ฌ.
- oauth2 - OAuth2.0 ๋ก๊ทธ์ธ ํธ๋ค๋ฌ. ํ์ด์ค๋ถ, ๊ตฌ๊ธ, ๊นํ ์ง์.
๋งํฐ๋ ์ธ์คํด์ค๋ http.Handler
์ธํฐํ์ด์ค๋ฅผ ์ฐจ์ฉํฉ๋๋ค. ๋ฐ๋ผ์ Go ์๋ฒ ์๋ธํธ๋ฆฌ๋ก ์ฝ๊ฒ ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ์๋ ์ฝ๋๋ ๊ตฌ๊ธ ์ฑ ์์ง์์ ์๋ํ๋ ๋งํฐ๋ ์ฑ์
๋๋ค:
package hello
import (
"net/http"
"github.com/go-martini/martini"
)
func init() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello ์ธ๊ณ!"
})
http.Handle("/", m)
}
๋งํฐ๋์ Run
ํจ์๋ PORT์ HOST ํ๊ฒฝ๋ณ์๋ฅผ ์ด์ฉํ๋๋ฐ, ์ค์ ์ด ์๋์ด ์๋ค๋ฉด localhost:3000์ผ๋ก ์ค์ ๋์ด ์ง๋๋ค.
์ข๋ ์ ์ฐํ๊ฒ ์ค์ ์ ํ๊ณ ์ถ๋ค๋ฉด, http.ListenAndServe
๋ฅผ ํ์ฉํด ์ฃผ์ธ์.
m := martini.Classic()
// ...
log.Fatal(http.ListenAndServe(":8080", m))
gin and fresh ๋งํฐ๋ ์ฑ์ ๋ผ์ด๋ธ ๋ฆฌ๋ก๋๋ฅผ ๋์์ค๋๋ค.
๋งํฐ๋๋ ๊ฐ๋จํ๊ณ ๊ฐ๋ฒผ์ด ํจํค์ง๋ก ๋จ์ ๊ฒ์ ๋๋ค. ๋ฐ๋ผ์ ๋ณดํต ๋๋ถ๋ถ์ ๊ณตํ๋ค์ martini-contrib ๊ทธ๋ฃน์ ์ ์ฅ์๋ก ๊ฐ๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ ๋งํฐ๋ ์ฝ์ด์ ๊ธฐ์ฌํ๊ณ ์ถ์ผ์๋ค๋ฉด ์ฃผ์ ์์ด Pull Request๋ฅผ ํด์ฃผ์ธ์.
express ์ sinatra์ ์ํฅ์ ๋ฐ์์ต๋๋ค.
๋งํฐ๋๋ Code Gangsta๊ฐ ๋์์ธ ํ์ต๋๋ค.