15
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Negroni + Gorilla/Muxで簡単なサーバーアプリケーション作る

Negroni + Gorilla/Muxで簡単なサーバーアプリケーション作る

使用するライブラリは以下のとおり
- Negroni
- Gorilla/Mux

サンプルコード

作った機能としては、
- GET "/" で名前の書き込みを依頼し
- POST "/"で送信された名前を受け取り表示させる。
- GET "/admin" でちょっとした認証機構

main.go
package main

import (
    "fmt"
    "net/http"
    "html/template"

    "github.com/gorilla/mux"
    "github.com/codegangsta/negroni"
)

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

    // Root への処理
    r.HandleFunc("/", getRoot).Methods("GET")
    r.HandleFunc("/", postRoot).Methods("POST")

    // アドミン周りの処理
    authBase := mux.NewRouter()
    r.PathPrefix("/admin").Handler(negroni.New(
        negroni.NewRecovery(),
        negroni.NewLogger(),
        negroni.HandlerFunc(authenticate),
        negroni.Wrap(authBase)))
    auth := authBase.PathPrefix("/admin").Subrouter()
    auth.Path("/").HandlerFunc(getAdmin)


    n := negroni.Classic()
    n.UseHandler(r)
    n.Run(":3000")
}

func getRoot(w http.ResponseWriter, r *http.Request) {
    t := template.Must(template.ParseFiles("root.tmpl"))
    t.Execute(w, nil)
}

func postRoot(w http.ResponseWriter, r *http.Request) {
    name := r.FormValue("name")
    t := template.Must(template.ParseFiles("root.tmpl"))
    t.Execute(w, struct{Name string} {Name : name})
}

func getAdmin(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Authenticate Success!!")
}


func authenticate(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    // ダメダメな実装例なので、真似ダメゼッタイ
    name := r.FormValue("name")
    if(name == "auth"){
        next(w,r)
    } else {
        fmt.Fprintf(w,"Please Authenticate!!")
    }

}

Negroni についての解説

Negroni の構造について

Negroni コード内に存在する重要な定義は、ほんの少ししか存在しません。

negroni.go
// Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler.
// Negroni middleware is evaluated in the order that they are added to the stack using
// the Use and UseHandler methods.
type Negroni struct {
    middleware middleware
    handlers   []Handler
}

func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    n.middleware.ServeHTTP(NewResponseWriter(rw), r)
}

まず、negroni.Classci()もしくは、negroni.New()を実行すると返されるNegroniインスタンスにフィールドは二つしか無く、ハンドラの配列とミドルウェアだけです。

ハンドラって何やねん。

ハンドラとは、「ユーザーからの要求(GET /posts/ みたいなリクエスト)をどう処理して応答するか」を操作する何かの事。

まず、Golang には標準でnet/httpパッケージが用意されており、コレを使うだけで、以下のようにリクエストをハンドリングする事が出来ます。

godocより引用
http.Handle("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(":8080", nil))

このことを踏まえた上で、Negroni はこのhttp/netと互換性を持たせるように設計されており、http/netのハンドラを、Negroni のハンドラとして利用することが出来るようになっています。それを実現しているのがNegroni.go の以下の部分。

Negroni.go
type Handler interface {
    ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}

// HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers.
// If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f.
type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)

func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    h(rw, r, next)
}

ミドルウェアって何やねん!?

この概念を理解するのに結構時間がかかりました。参考文献にも同じ図が載っていますが、この図が全てだと思います。
https://mattstauffer.co/blog/laravel-5.0-middleware-filter-style#what-is-middleware
オニオン

コアロジックであるApp に対して、前処理と後処理を提供するものが、ミドルウェアです。セッション管理や認証などがそれに当たります。ではコードを見てみましょう。

negroni.go
type middleware struct {
    handler Handler
    next    *middleware
}

middleware の定義はたったこれだけで、実行するハンドラと、次に実行するミドルウェアへのポインタが定義されています。negroni.Classicを呼び出すと、外側から順に、Recovery、Logging、Staticのミドルウェアが自動的に登録され、negroni.Newを呼び出すと、ミドルウェアが一つもない状態でNegroniインスタンスが作成されます。

また、n.Useを用いると、自作のミドルウェアを登録することが出来ます。(セッション管理とか認証系とか自作するか別ライブラリを使う必要がある) useを使ってミドルウェアを登録する時、たまねぎ構造の最も内側にミドルウェアが登録されます。

公式README曰く、以下の形で定義してねって書いてあるので、これに準拠しましょう。

README.md
func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  // do some stuff before
  next(rw, r)
  // do some stuff after
}

使ってみた雑感

Martini のREADME.md にThe martini framework is no longer maintained.と記述されているので、これからはNegroni/GorilalMux の組み合わせが主流になるのかもしれない。

けど、色々と面倒くさいところが多いし、開発速度を重視する人には向いていないかも……。

ここもっと良い書き方出来るとかココ間違ってる等のご指摘がありましたら、いただけると幸いです。

参考資料

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
15
Help us understand the problem. What are the problem?