0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goでのミドルウェア開発:Negroniの使い方

Posted at

Group2156.png

Leapcell: The Best of Serverless Web Hosting

Negroni ライブラリの紹介

Negroni は、HTTP ミドルウェアに焦点を当てたライブラリです。そのサイズは小さく、非侵襲的であり、標準ライブラリ net/http のハンドラの使用を奨励しています。この記事では、このライブラリについて詳細に紹介します。

I. なぜミドルウェアを使うのか

開発の過程で、統計、ロギング、デバッグなどの共通のロジックコードがあり、これらのロジックは各ハンドラで必要となる場合があります。これらのコードを各ハンドラに一つずつ追加すると、面倒ばかりでなく、エラーや見落としが発生しやすくなります。

ハンドラの実行にかかる時間を計測する例を挙げます。時間計測のコードを各ハンドラに追加する場合、以下のようになります:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func index(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    fmt.Fprintf(w, "leapcell page")
    fmt.Printf("index elasped:%fs", time.Since(start).Seconds())
}

func greeting(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    name := r.FormValue("name")
    if name == "" {
        name = "world"
    }
    fmt.Fprintf(w, "hello %s", name)
    fmt.Printf("greeting elasped:%fs\n", time.Since(start).Seconds())
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", index)
    mux.HandleFunc("/greeting", greeting)
    http.ListenAndServe(":8000", mux)
}

しかし、このアプローチには多くの欠点があります:

  1. 柔軟性の欠如:新しいハンドラを追加するたびに、この時間計測のコードを追加する必要があり、これらのコードは実際のハンドラロジックとは直接関係がありません。
  2. 見落としやすい:ハンドラを書くときに、このようなコードを忘れやすく、特にすべての可能な戻り経路を考慮するときには、コーディングの負荷が増えます。
  3. 修正しにくい:統計コードにエラーがある場合や、調整が必要な場合には、関係するすべてのハンドラを修正する必要があります。
  4. 新しいロジックの追加が難しい:他の統計ロジックを追加する必要がある場合、すべてのハンドラのコードも変更する必要があります。

Go 言語のクロージャ機能を利用することで、実際のハンドラコードを関数にカプセル化し、この関数内で追加のロジックを実行することができます。以下に示します:

func elasped(h func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        path := r.URL.Path
        start := time.Now()
        h(w, r)
        fmt.Printf("path:%s elasped:%fs\n", path, time.Since(start).Seconds())
    }
}

func index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "leapcell page")
}

func greeting(w http.ResponseWriter, r *http.Request) {
    name := r.FormValue("name")
    if name == "" {
        name = "world"
    }
    fmt.Fprintf(w, "hello %s", name)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", elasped(index))
    mux.HandleFunc("/greeting", elasped(greeting))
    http.ListenAndServe(":8000", mux)
}

この例では、ハンドラと関係のない追加コードを elasped 関数に配置しています。ハンドラ関数を登録するときには、元のハンドラ関数を直接使用するのではなく、elasped 関数でカプセル化しています。実際、elasped のような関数はミドルウェアです。元のハンドラ関数をカプセル化し、新しいハンドラ関数を返します。これにより、実際の処理ロジックの前後にコードを挿入することができ、追加、修正、保守がしやすくなります。

II. Negroni のクイックスタート

(I) インストール

以下のコマンドを実行して、Negroni ライブラリをインストールします:

$ go get github.com/urfave/negroni

(II) 使用例

package main

import (
    "fmt"
    "net/http"
    "github.com/urfave/negroni"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.Classic()
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

Negroni は比較的使いやすく、http.Handler と簡単に連携することができます。negroni.Classic() はいくつかの一般的なミドルウェアを提供します:

  1. negroni.Recovery:プログラムの実行中に発生する panic から復旧するために使用されます。ハンドラコードで panic が発生した場合、このミドルウェアは例外をキャッチし、プログラムの終了を防ぎます。
  2. negroni.Logger:リクエストとレスポンスの基本情報を記録し、ロギング機能を実装します。
  3. negroni.Staticpublic ディレクトリ内の静的ファイルサービスを提供することができます。

n.UseHandler(mux) を呼び出すことで、これらのミドルウェアがマルチプレクサ mux に適用されます。プログラムを実行した後、ブラウザに localhost:3000 を入力すると、コンソールに以下のような出力が表示されます:

[negroni] 2025-03-22T18:48:53+08:00 | 200 |      10.9966ms | localhost:8080 | GET /

III. Negroni の詳細な機能

(I) negroni.Handler インターフェイス

negroni.Handler インターフェイスは、ミドルウェアの実行プロセスに対してより柔軟な制御を提供します。インターフェイスは以下のように定義されています:

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

書かれたミドルウェアのシグネチャは func(http.ResponseWriter, *http.Request, http.HandlerFunc) でなければならず、または negroni.Handler インターフェイスを実装する必要があります。例えば:

func RandomMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    if rand.Int31n(100) <= 50 {
        fmt.Fprintf(w, "hello from RandomMiddleware")
    } else {
        next(w, r)
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n.Use(negroni.HandlerFunc(RandomMiddleware))
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

上記のコードは、ランダムなミドルウェアを実装しています。RandomMiddleware ミドルウェアから直接レスポンスを返す確率は 50% で、実際のハンドラ関数を実行する確率も 50% です。プログラムを実行した後、ブラウザで localhost:8080 を繰り返しリフレッシュして、その効果を観察してください。

実際、func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) は便利な書き方です。n.Use を呼び出すときに、negroni.HandlerFunc でカプセル化され、negroni.HandlerFuncnegroni.Handler インターフェイスを実装しています。以下は関連するコードです:

// src/github.com/urfave/negroni/negroni.go
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)
}

同様に、net/http では、func(http.ResponseWriter, *http.Request)http.HandlerFunc でカプセル化されて、http.Handler インターフェイスを実装しています。

(II) negroni.With メソッド

複数のミドルウェアがある場合、n.Use() メソッドを使って一つずつ追加するのは面倒です。Negroni は With() メソッドを提供しており、一つ以上の negroni.Handler パラメータを受け取り、新しいオブジェクトを返します。例えば:

func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("Middleware A begin")
    next(w, r)
    fmt.Println("Middleware A end")
}

func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("Middleware B begin")
    next(w, r)
    fmt.Println("Middleware B end")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n = n.With(
        negroni.HandlerFunc(Middleware1),
        negroni.HandlerFunc(Middleware2),
    )
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

(III) Run メソッド

Negroni オブジェクトは Run() メソッドを提供しており、サーバプログラムを簡単に実行するために使用されます。このメソッドは http.ListenAndServe() と同じアドレス (Addr) パラメータを受け取ります。例えば:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n.UseHandler(mux)
    n.Run(":8080")
}

ポートが指定されていない場合、Run() メソッドは PORT 環境変数を使用しようとします。PORT 環境変数も設定されていない場合、デフォルトのポート :8080 が使用されます。

(IV) http.Handler としての使用

Negroni は net/http プログラムで簡単に使用することができ、negroni.Negroni オブジェクトを直接 http.Handler として対応するメソッドに渡すことができます。例えば:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.Classic()
    n.UseHandler(mux)
    s := &http.Server{
        Addr:           ":8080",
        Handler:        n,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

IV. 組み込みミドルウェア

(I) Static

negroni.Static ミドルウェアは、指定されたディレクトリ内のファイルサービスを提供することができます。例のコードは以下の通りです:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello world")
    })
    n := negroni.New()
    n.Use(negroni.NewStatic(http.Dir("./public")))
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

プログラムの実行ディレクトリ内に public ディレクトリを作成し、その中にいくつかのファイル (例えば 1.txt, 2.jpg) を配置してください。プログラムを実行した後、ブラウザで localhost:8080/1.txtlocalhost:8080/2.jpg# Negroni ライブラリの紹介
Negroni は、HTTP ミドルウェアに焦点を当てたライブラリです。そのサイズは小さく、非侵襲的であり、標準ライブラリ net/http のハンドラの使用を奨励しています。この記事では、このライブラリについて詳細に紹介します。

I. なぜミドルウェアを使うのか

開発の過程で、統計、ロギング、デバッグなどの共通のロジックコードがあり、これらのロジックは各ハンドラで必要となる場合があります。これらのコードを各ハンドラに一つずつ追加すると、面倒ばかりでなく、エラーや見落としが発生しやすくなります。

ハンドラの実行にかかる時間を計測する例を挙げます。時間計測のコードを各ハンドラに追加する場合、以下のようになります:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func index(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    fmt.Fprintf(w, "leapcell page")
    fmt.Printf("index elasped:%fs", time.Since(start).Seconds())
}

func greeting(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    name := r.FormValue("name")
    if name == "" {
        name = "world"
    }
    fmt.Fprintf(w, "hello %s", name)
    fmt.Printf("greeting elasped:%fs\n", time.Since(start).Seconds())
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", index)
    mux.HandleFunc("/greeting", greeting)
    http.ListenAndServe(":8000", mux)
}

しかし、このアプローチには多くの欠点があります:

  1. 柔軟性の欠如:新しいハンドラを追加するたびに、この時間計測のコードを追加する必要があり、これらのコードは実際のハンドラロジックとは直接関係がありません。
  2. 見落としやすい:ハンドラを書くときに、このようなコードを忘れやすく、特にすべての可能な戻り経路を考慮するときには、コーディングの負荷が増えます。
  3. 修正しにくい:統計コードにエラーがある場合や、調整が必要な場合には、関係するすべてのハンドラを修正する必要があります。
  4. 新しいロジックの追加が難しい:他の統計ロジックを追加する必要がある場合、すべてのハンドラのコードも変更する必要があります。

Go 言語のクロージャ機能を利用することで、実際のハンドラコードを関数にカプセル化し、この関数内で追加のロジックを実行することができます。以下に示します:

func elasped(h func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        path := r.URL.Path
        start := time.Now()
        h(w, r)
        fmt.Printf("path:%s elasped:%fs\n", path, time.Since(start).Seconds())
    }
}

func index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "leapcell page")
}

func greeting(w http.ResponseWriter, r *http.Request) {
    name := r.FormValue("name")
    if name == "" {
        name = "world"
    }
    fmt.Fprintf(w, "hello %s", name)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", elasped(index))
    mux.HandleFunc("/greeting", elasped(greeting))
    http.ListenAndServe(":8000", mux)
}

この例では、ハンドラと関係のない追加コードを elasped 関数に配置しています。ハンドラ関数を登録するときには、元のハンドラ関数を直接使用するのではなく、elasped 関数でカプセル化しています。実際、elasped のような関数はミドルウェアです。元のハンドラ関数をカプセル化し、新しいハンドラ関数を返します。これにより、実際の処理ロジックの前後にコードを挿入することができ、追加、修正、保守がしやすくなります。

II. Negroni のクイックスタート

(I) インストール

以下のコマンドを実行して、Negroni ライブラリをインストールします:

$ go get github.com/urfave/negroni

(II) 使用例

package main

import (
    "fmt"
    "net/http"
    "github.com/urfave/negroni"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.Classic()
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

Negroni は比較的使いやすく、http.Handler と簡単に連携することができます。negroni.Classic() はいくつかの一般的なミドルウェアを提供します:

  1. negroni.Recovery:プログラムの実行中に発生する panic から復旧するために使用されます。ハンドラコードで panic が発生した場合、このミドルウェアは例外をキャッチし、プログラムの終了を防ぎます。
  2. negroni.Logger:リクエストとレスポンスの基本情報を記録し、ロギング機能を実装します。
  3. negroni.Staticpublic ディレクトリ内の静的ファイルサービスを提供することができます。

n.UseHandler(mux) を呼び出すことで、これらのミドルウェアがマルチプレクサ mux に適用されます。プログラムを実行した後、ブラウザに localhost:3000 を入力すると、コンソールに以下のような出力が表示されます:

[negroni] 2025-03-22T18:48:53+08:00 | 200 |      10.9966ms | localhost:8080 | GET /

III. Negroni の詳細な機能

(I) negroni.Handler インターフェイス

negroni.Handler インターフェイスは、ミドルウェアの実行プロセスに対してより柔軟な制御を提供します。インターフェイスは以下のように定義されています:

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

書かれたミドルウェアのシグネチャは func(http.ResponseWriter, *http.Request, http.HandlerFunc) でなければならず、または negroni.Handler インターフェイスを実装する必要があります。例えば:

func RandomMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    if rand.Int31n(100) <= 50 {
        fmt.Fprintf(w, "hello from RandomMiddleware")
    } else {
        next(w, r)
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n.Use(negroni.HandlerFunc(RandomMiddleware))
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

上記のコードは、ランダムなミドルウェアを実装しています。RandomMiddleware ミドルウェアから直接レスポンスを返す確率は 50% で、実際のハンドラ関数を実行する確率も 50% です。プログラムを実行した後、ブラウザで localhost:8080 を繰り返しリフレッシュして、その効果を観察してください。

実際、func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) は便利な書き方です。n.Use を呼び出すときに、negroni.HandlerFunc でカプセル化され、negroni.HandlerFuncnegroni.Handler インターフェイスを実装しています。以下は関連するコードです:

// src/github.com/urfave/negroni/negroni.go
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)
}

同様に、net/http では、func(http.ResponseWriter, *http.Request)http.HandlerFunc でカプセル化されて、http.Handler インターフェイスを実装しています。

(II) negroni.With メソッド

複数のミドルウェアがある場合、n.Use() メソッドを使って一つずつ追加するのは面倒です。Negroni は With() メソッドを提供しており、一つ以上の negroni.Handler パラメータを受け取り、新しいオブジェクトを返します。例えば:

func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("Middleware A begin")
    next(w, r)
    fmt.Println("Middleware A end")
}

func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("Middleware B begin")
    next(w, r)
    fmt.Println("Middleware B end")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n = n.With(
        negroni.HandlerFunc(Middleware1),
        negroni.HandlerFunc(Middleware2),
    )
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

(III) Run メソッド

Negroni オブジェクトは Run() メソッドを提供しており、サーバプログラムを簡単に実行するために使用されます。このメソッドは http.ListenAndServe() と同じアドレス (Addr) パラメータを受け取ります。例えば:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.New()
    n.UseHandler(mux)
    n.Run(":8080")
}

ポートが指定されていない場合、Run() メソッドは PORT 環境変数を使用しようとします。PORT 環境変数も設定されていない場合、デフォルトのポート :8080 が使用されます。

(IV) http.Handler としての使用

Negroni は net/http プログラムで簡単に使用することができ、negroni.Negroni オブジェクトを直接 http.Handler として対応するメソッドに渡すことができます。例えば:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!")
    })
    n := negroni.Classic()
    n.UseHandler(mux)
    s := &http.Server{
        Addr:           ":8080",
        Handler:        n,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

IV. 組み込みミドルウェア

(I) Static

negroni.Static ミドルウェアは、指定されたディレクトリ内のファイルサービスを提供することができます。例のコードは以下の通りです:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello world")
    })
    n := negroni.New()
    n.Use(negroni.NewStatic(http.Dir("./public")))
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

プログラムの実行ディレクトリ内に public ディレクトリを作成し、その中にいくつかのファイル (例えば 1.txt, 2.jpg) を配置してください。プログラムを実行した後、ブラウザで localhost:8080/1.txtlocalhost:8080/2.jpgをアクセスすることで、これらのファイルをリクエストすることができます。特に注意すべきは、対応するファイルが見つからない場合、Static ミドルウェアはリクエストを次のミドルウェアまたはハンドラ関数に渡すことです。例えば、ブラウザで localhost:3000/none - exist.txt を入力すると、hello world のレスポンスが表示されます。

(II) Logger

「クイックスタート」のセクションでは、negroni.Classic() を通じてこのミドルウェアを使用しました。また、単独で使用してリクエスト情報を記録することもでき、SetFormat() メソッドを呼び出してログ形式を設定することができます。例えば:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello world")
    })
    n := negroni.New()
    logger := negroni.NewLogger()
    logger.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}")
    n.Use(logger)
    n.UseHandler(mux)
    http.ListenAndServe(":8080", n)
}

上記のコードは、ログ形式を [{{.Status}} {{.Duration}}] - {{.Request.UserAgent}} に設定しており、すなわちレスポンスステータス、実行時間、UserAgent を記録します。

V. サードパーティのミドルウェア

組み込みのミドルウェアに加えて、Negroni には多数のサードパーティのミドルウェアも存在します。完全なリストについては、https://github.com/urfave/negroni?tab=readme-ov-file#third-party-middleware を参照してください。

VI. まとめ

Negroni はミドルウェア機能に焦点を当てており、あまり面倒でまれにしか使われない派手な機能はありません。その非侵襲的な設計の特徴により、標準ライブラリ net/http や他の Web ライブラリ(例えば gorilla/mux)とシームレスに連携することができます。この特徴は、開発者が HTTP サービスを構築する際に大きな利便性をもたらします。開発者は実際のニーズに応じて柔軟にミドルウェアを選択し、組み合わせることで、ロギング、エラーハンドリング、リクエスト統計などの様々な機能を実現することができ、既存のコードアーキテクチャを大規模に変更する必要はありません。

全体として、Negroni は Go 言語の HTTP サービス開発において非常に価値のあるライブラリです。効率的で、柔軟で、保守しやすい Web アプリケーション開発を追求する人にとって、Negroni は間違いなく深く理解し、幅広く適用する価値のある優れたツールです。

Leapcell: The Best of Serverless Web Hosting

最後に、Go サービスをデプロイするための最高のプラットフォーム:Leapcell を紹介します。

brandpic7.png

🚀 好きな言語で構築する

JavaScript、Python、Go、または Rust で簡単に開発できます。

🌍 無料で無制限のプロジェクトをデプロイする

使用する分だけ支払います — リクエストがなければ、請求もありません。

⚡ 使った分だけ支払い、隠された費用はありません

アイドル料はなく、シームレスなスケーラビリティが保証されます。

Frame3-withpadding2x.png

📖 ドキュメントを参照する

🔹 Twitter でフォローしてください:@LeapcellHQ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?