Gorilla - Golang learning step 5-4
- 公開日
- カテゴリ:WebFrameworks
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Web Frameworks > Gorilla の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
- 開発環境
- 参考 URL
- Gorilla
- Gorilla の主要なパッケージと用途
- 2. gorilla/handlers: HTTP リクエスト/レスポンスのためのミドルウェア
- 3. gorilla/sessions: セッション管理のためのパッケージ
- 4. gorilla/websocket: WebSocket 通信のためのパッケージ
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
参考 URL
Gorilla
Gorilla は、Go 言語での Web アプリケーション開発を簡単にするための軽量なフレームワーク (FW) です。Gorilla は「フルスタックのフレームワーク」ではなく、必要な機能ごとに独立したパッケージを提供しているのが特徴です。そのため、必要な機能だけを選んで使うことができます。
事前準備
プロジェクトを作成しておきます。
mkdir myapp
cd myapp
go mod init myapp
Gorilla の主要なパッケージと用途
1. gorilla/mux: 高機能なルーター
gorilla/mux は、Go の標準ライブラリの net/http パッケージを拡張したルーターで、以下のような機能を提供します。
- 動的なルーティング: URL パラメーター (/user/{id}) をサポート
- メソッド制限: HTTP メソッド(GET, POST など)に基づくルーティング
- リクエストのホスト名やスキームに基づくルーティング
- クエリパラメーターのマッチング
インストール
go get -u github.com/gorilla/mux
実装例
gorilla/mux
を使った簡単なルーティング実装例です。
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
// 新しいルーターを作成
r := mux.NewRouter()
// ルートの定義
r.HandleFunc("/", HomeHandler).Methods("GET")
r.HandleFunc("/users/{id:[0-9]+}", UserHandler).Methods("GET")
// サーバーの起動
http.ListenAndServe(":8080", r)
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func UserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userId := vars["id"]
fmt.Fprintf(w, "User ID: %s", userId)
}
以下のコマンドでサーバーを起動します。
go run main.go
ブラウザで http://localhost:8080
にアクセスすると、「Hello, World!」が表示されます。
ブラウザで http://localhost:8080/users/123
にアクセスすると、「User ID: 123」が表示されます。
2. gorilla/handlers: HTTP リクエスト/レスポンスのためのミドルウェア
gorilla/handlers は、HTTP リクエスト/レスポンスを処理するための便利なミドルウェアを提供します。一般的な Web アプリケーションで必要となる以下のような機能を実装できます。
- ロギング: HTTP リクエストのログ出力
- CORS: クロスオリジンリソース共有の設定
- 圧縮: レスポンスの自動圧縮
- リカバリー: パニック発生時の処理
インストール
go get -u github.com/gorilla/handlers
実装例
gorilla/handlers
を使った基本的なミドルウェア実装例です。
package main
import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"net/http"
"os"
)
func main() {
// 新しいルーターを作成
r := mux.NewRouter()
// 基本的なルートの設定
r.HandleFunc("/", HomeHandler).Methods("GET")
// ミドルウェアの適用
// 1. ログ出力
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
// 2. CORS設定
corsRouter := handlers.CORS(
handlers.AllowedOrigins([]string{"http://localhost:3000"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE"}),
handlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
)(loggedRouter)
// 3. レスポンス圧縮
compressedRouter := handlers.CompressHandler(corsRouter)
// 4. パニックリカバリー
finalRouter := handlers.RecoveryHandler()(compressedRouter)
// サーバーの起動
http.ListenAndServe(":8080", finalRouter)
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the home page!"))
}
以下のコマンドでサーバーを起動します。
go run main.go
このサーバーは以下の機能を備えています。
- すべてのリクエストがコンソールにログ出力されます
- 指定したオリジンからのクロスオリジンリクエストを許可します
- レスポンスが自動的に圧縮されます
- パニックが発生してもサーバーがクラッシュしません
ブラウザで http://localhost:8080
にアクセスすると、「Welcome to the home page!」が表示され、同時にコンソールにリクエストログが出力されます。
::1 - - [05/Dec/2024:23:33:54 +0900] "GET / HTTP/1.1" 200 25
各ミドルウェアは必要に応じて個別に使用することもできます:
// ロギングだけ使用する場合
http.ListenAndServe(":8080", handlers.LoggingHandler(os.Stdout, r))
// CORSだけ使用する場合
http.ListenAndServe(":8080", handlers.CORS()(r))
// 圧縮だけ使用する場合
http.ListenAndServe(":8080", handlers.CompressHandler(r))
3. gorilla/sessions: セッション管理のためのパッケージ
gorilla/sessions は、Cookieベースのセッション管理を提供するパッケージです。以下のような機能を備えています。
- 複数のセッションタイプ: Cookie または FileStore でのセッション管理
- セッションの暗号化
- フラッシュメッセージ
- 有効期限の設定
インストール
go get -u github.com/gorilla/sessions
実装例
gorilla/sessions
を使った基本的なセッション管理の実装例です。
package main
import (
"fmt"
"github.com/gorilla/sessions"
"github.com/gorilla/mux"
"net/http"
)
// セッションストアの作成(安全な鍵管理を推奨)
var store = sessions.NewCookieStore([]byte("secret-key-32-bytes-long-password"))
// 注意: 上記の鍵はサンプルのための簡易例です。
// 本番環境では、以下のように環境変数や AWS Secrets Manager のような
// 秘密管理サービスを使用して安全に管理してください。
//
// 例:
// var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))
//
// 環境変数の設定例(.env ファイル):
// SESSION_SECRET=your-strong-secret-key
func main() {
r := mux.NewRouter()
// ルートの定義
r.HandleFunc("/login", LoginHandler).Methods("GET")
r.HandleFunc("/profile", ProfileHandler).Methods("GET")
r.HandleFunc("/logout", LogoutHandler).Methods("GET")
http.ListenAndServe(":8080", r)
}
// ログイン処理
func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// セッションへの値の設定
session.Values["authenticated"] = true
session.Values["user"] = "John Doe"
// セッションの保存
session.Save(r, w)
fmt.Fprintln(w, "You have been logged in!")
}
// プロフィール表示(セッションチェック付き)
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// 認証チェック
auth, ok := session.Values["authenticated"].(bool)
if !ok || !auth {
http.Error(w, "Please log in first", http.StatusUnauthorized)
return
}
// ユーザー名の取得
username := session.Values["user"].(string)
fmt.Fprintf(w, "Welcome, %s!", username)
}
// ログアウト処理
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// セッション値のクリア
session.Values["authenticated"] = false
delete(session.Values, "user")
session.Save(r, w)
fmt.Fprintln(w, "You have been logged out!")
}
go run main.go
このサーバーは以下の機能を備えています。
/login
にアクセスするとセッションが作成されます/profile
では認証済みユーザーのみ情報を表示します/logout
でセッションがクリアされます
セッションのオプション設定
セッションの動作をカスタマイズすることもできます。
func main() {
store := sessions.NewCookieStore([]byte("secret-key"))
// セッションの設定
store.Options = &sessions.Options{
Path: "/", // Cookieのパス
MaxAge: 86400, // 有効期限(秒)
HttpOnly: true, // JavaScriptからのアクセスを防止
Secure: true, // HTTPS接続のみ
SameSite: http.SameSiteStrictMode, // SameSite属性
}
}
ファイルベースのセッション
メモリ使用量を抑えるために、ファイルベースのセッションストアを使用することもできます。
package main
import (
"github.com/gorilla/sessions"
"encoding/gob"
)
// カスタム型をセッションに保存する場合は登録が必要
func init() {
gob.Register(&User{}) // カスタム構造体を登録
}
var store = sessions.NewFilesystemStore("./sessions", []byte("secret-key"))
type User struct {
ID int
Name string
Email string
}
gorilla/sessions
は、セッション管理の基本機能を簡単に実装できる一方で、セキュアなアプリケーションを作るためには、開発者自身が適切なセキュリティ設定を行う必要があります。
4. gorilla/websocket: WebSocket 通信のためのパッケージ
gorilla/websocket
は、WebSocket プロトコルを実装したパッケージです。リアルタイム通信を実現するために以下のような機能を提供します。
- WebSocket サーバーの実装
- クライアントとの双方向通信
- メッセージの送受信
- 接続管理
インストール
go get -u github.com/gorilla/websocket
実装例
gorilla/websocket を使った基本的なチャットサーバーの実装例です。
ディレクトリ・ファイル構造
myapp
├── chat-server
│ └── main.go
└── chat-client
└── main.go
chat-server/main.go
:
package main
import (
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"log"
"net/http"
"sync"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type Client struct {
conn *websocket.Conn
send chan []byte
username string
}
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
mutex sync.Mutex
}
func newHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.mutex.Lock()
h.clients[client] = true
message := []byte("System: " + client.username + " joined the chat")
for c := range h.clients {
select {
case c.send <- message:
default:
close(c.send)
delete(h.clients, c)
}
}
h.mutex.Unlock()
case client := <-h.unregister:
h.mutex.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
message := []byte("System: " + client.username + " left the chat")
for c := range h.clients {
select {
case c.send <- message:
default:
close(c.send)
delete(h.clients, c)
}
}
}
h.mutex.Unlock()
case message := <-h.broadcast:
h.mutex.Lock()
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
h.mutex.Unlock()
}
}
}
func handleWebSocket(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
username := r.URL.Query().Get("username")
if username == "" {
username = "Anonymous"
}
client := &Client{
conn: conn,
send: make(chan []byte, 256),
username: username,
}
hub.register <- client
go func() {
defer func() {
hub.unregister <- client
conn.Close()
}()
for {
_, message, err := conn.ReadMessage()
if err != nil {
break
}
formattedMessage := []byte(client.username + ": " + string(message))
hub.broadcast <- formattedMessage
}
}()
go func() {
defer conn.Close()
for {
select {
case message, ok := <-client.send:
if !ok {
conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
if err := w.Close(); err != nil {
return
}
}
}
}()
}
func main() {
hub := newHub()
go hub.run()
r := mux.NewRouter()
r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
handleWebSocket(hub, w, r)
})
log.Println("Server starting at :8080")
http.ListenAndServe(":8080", r)
}
chat-client/main.go
:
package main
import (
"bufio"
"flag"
"fmt"
"github.com/gorilla/websocket"
"log"
"os"
"os/signal"
)
var addr = flag.String("addr", "localhost:8080", "server address")
var username = flag.String("username", "Anonymous", "username for chat")
func main() {
flag.Parse()
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
url := fmt.Sprintf("ws://%s/ws?username=%s", *addr, *username)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer conn.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("Error:", err)
return
}
fmt.Printf("%s\n", message)
}
}()
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
message := scanner.Text()
err := conn.WriteMessage(websocket.TextMessage, []byte(message))
if err != nil {
log.Println("Error:", err)
return
}
}
}()
for {
select {
case <-done:
return
case <-interrupt:
err := conn.WriteMessage(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
return
}
select {
case <-done:
}
return
}
}
}
サーバーを起動します
cd chat-server
go run main.go
2 つのターミナルでそれぞれクライアントを起動します
ターミナル 1: User1
cd chat-client
go run main.go -username "User1"
ターミナル 2: User2
cd chat-client
go run main.go -username "User2"
それぞれで websocket を使用したチャットが動作します。
ターミナル 1: User1
% go run main.go -username "User1"
System: User1 joined the chat
System: User2 joined the chat
こんにちは!
User1: こんにちは!
User2: やあ、こんにちは!
ターミナル 2: User2
% go run main.go -username "User2"
System: User2 joined the chat
User1: こんにちは!
やあ、こんにちは!
User2: やあ、こんにちは!
まとめ
- Gorilla は必要な機能ごとに独立したパッケージを提供する軽量なフレームワーク
gorilla/mux
は高機能なルーティングを提供するパッケージgorilla/handlers
は HTTP リクエストやレスポンスの処理を簡単にするミドルウェアを提供gorilla/sessions
は Cookie を利用したセッション管理を可能にするパッケージgorilla/websocket
は WebSocket を利用したリアルタイム通信を実現するパッケージ- 各パッケージの活用により、安全かつ効率的な Web アプリケーションの構築が可能
[Next] Step 6-1: log/slog
[Prev] Step 5-3: Fiber