Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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 の組み合わせが主流になるのかもしれない。

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

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

参考資料

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした