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)
}
しかし、このアプローチには多くの欠点があります:
- 柔軟性の欠如:新しいハンドラを追加するたびに、この時間計測のコードを追加する必要があり、これらのコードは実際のハンドラロジックとは直接関係がありません。
- 見落としやすい:ハンドラを書くときに、このようなコードを忘れやすく、特にすべての可能な戻り経路を考慮するときには、コーディングの負荷が増えます。
- 修正しにくい:統計コードにエラーがある場合や、調整が必要な場合には、関係するすべてのハンドラを修正する必要があります。
- 新しいロジックの追加が難しい:他の統計ロジックを追加する必要がある場合、すべてのハンドラのコードも変更する必要があります。
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()
はいくつかの一般的なミドルウェアを提供します:
-
negroni.Recovery:プログラムの実行中に発生する
panic
から復旧するために使用されます。ハンドラコードでpanic
が発生した場合、このミドルウェアは例外をキャッチし、プログラムの終了を防ぎます。 - negroni.Logger:リクエストとレスポンスの基本情報を記録し、ロギング機能を実装します。
-
negroni.Static:
public
ディレクトリ内の静的ファイルサービスを提供することができます。
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.HandlerFunc
は negroni.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.txt
や localhost: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)
}
しかし、このアプローチには多くの欠点があります:
- 柔軟性の欠如:新しいハンドラを追加するたびに、この時間計測のコードを追加する必要があり、これらのコードは実際のハンドラロジックとは直接関係がありません。
- 見落としやすい:ハンドラを書くときに、このようなコードを忘れやすく、特にすべての可能な戻り経路を考慮するときには、コーディングの負荷が増えます。
- 修正しにくい:統計コードにエラーがある場合や、調整が必要な場合には、関係するすべてのハンドラを修正する必要があります。
- 新しいロジックの追加が難しい:他の統計ロジックを追加する必要がある場合、すべてのハンドラのコードも変更する必要があります。
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()
はいくつかの一般的なミドルウェアを提供します:
-
negroni.Recovery:プログラムの実行中に発生する
panic
から復旧するために使用されます。ハンドラコードでpanic
が発生した場合、このミドルウェアは例外をキャッチし、プログラムの終了を防ぎます。 - negroni.Logger:リクエストとレスポンスの基本情報を記録し、ロギング機能を実装します。
-
negroni.Static:
public
ディレクトリ内の静的ファイルサービスを提供することができます。
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.HandlerFunc
は negroni.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.txt
や localhost: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 を紹介します。
🚀 好きな言語で構築する
JavaScript、Python、Go、または Rust で簡単に開発できます。
🌍 無料で無制限のプロジェクトをデプロイする
使用する分だけ支払います — リクエストがなければ、請求もありません。
⚡ 使った分だけ支払い、隠された費用はありません
アイドル料はなく、シームレスなスケーラビリティが保証されます。
🔹 Twitter でフォローしてください:@LeapcellHQ