概要
簡単なサンプルを紹介します
公式サンプルが基本一番わかりやすいです
環境
- macOS 26.5.1
- golang 1.26.4
- gin 1.12.0
準備
- mkdir gin-sample
- cd gin-sample
- go mod init gin-sample
- go get -u github.com/gin-gonic/gin
とりあえず動かす
ルータを作成しルーティング情報を追加します
ルーティングが行う処理は関数として渡します
gin にはレスポンスを返すためのメソッド (application/json なら JSON や text/plain なら String などなど) があります
ルーティングを登録したら Run で起動できます
- vim main.go
package main
import "github.com/gin-gonic/gin"
func main() {
// ルーター作成(デフォルト設定)
r := gin.Default()
// GET / にアクセスしたときの処理
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, Gin!",
})
})
// GET /ping
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
// サーバー起動(デフォルト: :8080)
r.Run()
}
- go run main.go
curl localhost:8080
{"message":"Hello, Gin!"}
curl localhost:8080/ping
pong
こんな感じで動作します
POST + JSON でボディを受け取る
これも王道な方法です
fastapi に近い感じですが gin の場合は struct を準備しボディ情報を自動で変換してくれます
変換できない場合はエラーを返してくれます
- vim main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// リクエスト用構造体
type User struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age"`
}
func main() {
r := gin.Default()
// GET /
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello, Gin!",
})
})
// POST /user
r.POST("/user", func(c *gin.Context) {
var user User
// JSONを構造体にバインド
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 成功レスポンス
c.JSON(http.StatusOK, gin.H{
"message": "user created",
"user": user,
})
})
r.Run()
}
正常系と異常系は以下の通りです
curl -X POST http://localhost:8080/user \
-H "Content-Type: application/json" \
-d '{
"name": "Taro",
"email": "taro@example.com",
"age": 20
}'
{"message":"user created","user":{"name":"Taro","email":"taro@example.com","age":20}}
curl -X POST http://localhost:8080/user \
-H "Content-Type: application/json" \
-d '{
"email": "taro@example.com",
"age": 20
}'
{"error":"Key: 'User.Name' Error:Field validation for 'Name' failed on the 'required' tag"}
POST + JSON でボディの生情報を扱う
c.Request.Body で取得できます
gin ではボディ情報は IO ストリームです
ストリームなので Read するのですが Read した場合次の Read ではデータがないのでエラーになります
つまり一度しか Read できないので注意しましょう
package main
import (
"encoding/json"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/raw", func(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, "read error")
return
}
// byte → string
raw := string(body)
c.JSON(http.StatusOK, gin.H{
"raw": raw,
})
})
r.POST("/raw-to-map", func(c *gin.Context) {
body, _ := c.GetRawData()
var data map[string]interface{}
if err := json.Unmarshal(body, &data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid json",
})
return
}
c.JSON(http.StatusOK, gin.H{
"map": data,
})
})
r.Run()
}
動作確認は以下です
curl -X POST http://localhost:8080/raw \
-H "Content-Type: application/json" \
-d '{"name":"taro"}'
{"raw":"{\"name\":\"taro\"}"}
curl -X POST http://localhost:8080/raw-to-map \
-H "Content-Type: application/json" \
-d '{"name":"taro"}'
{"map":{"name":"taro"}}
ルーティングの処理用の関数(ハンドラ)を切り出す
実際のプロダクションではもっと大規模になるはずなのでハンドラを切り出す必要があります
ポイントとしては切り出した関数は *gin.Context を持つ必要があります
- vim handler/user.go
package handler
import (
"net/http"
"github.com/gin-gonic/gin"
)
// 依存を持つための構造体
type UserHandler struct {
AppName string
}
// コンストラクタ
func NewUserHandler(appName string) *UserHandler {
return &UserHandler{
AppName: appName,
}
}
// GET /
func (h *UserHandler) Hello(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello from " + h.AppName,
})
}
// POST /user
func (h *UserHandler) CreateUser(c *gin.Context) {
var body map[string]interface{}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"app": h.AppName,
"user": body,
})
}
- vim main.go
package main
import (
"gin-sample/handler"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// ハンドラ生成(依存注入)
userHandler := handler.NewUserHandler("MyApp")
// ルーティング
r.GET("/", userHandler.Hello)
r.POST("/user", userHandler.CreateUser)
r.Run()
}
動作確認は以下です
curl -X POST http://localhost:8080/user \
-H "Content-Type: application/json" \
-d '{
"email": "taro@example.com",
"age": 20
}'
{"app":"MyApp","user":{"age":20,"email":"taro@example.com"}}
curl -X GET http://localhost:8080/
{"message":"Hello from MyApp"}
dispatcher パターンを使う
特定のクエリストリングで処理を分けたい場合などに使えます
REST などでは基本使いませんが / しか受けないような API でパラメータに応じて処理を分けたい場合などに使えるパターンです
特定のアクションに応じた関数を登録しておくことでアクションごとに指定の関数を実行できます
- vim handler/dispatcher.go
package handler
import (
"errors"
"net/http"
)
// Actionの関数型
type ActionFunc func(params map[string]string, method string, r *http.Request) (any, error)
type Dispatcher struct {
actions map[string]ActionFunc
}
func NewDispatcher() *Dispatcher {
return &Dispatcher{
actions: make(map[string]ActionFunc),
}
}
// Action登録
func (d *Dispatcher) Register(name string, fn ActionFunc) {
d.actions[name] = fn
}
// 実行
func (d *Dispatcher) Execute(name string, params map[string]string, method string, r *http.Request) (any, error) {
if fn, ok := d.actions[name]; ok {
return fn(params, method, r)
}
return nil, errors.New("unknown action: " + name)
}
ハンドラのメインとなる処理です
dispathcher に登録されたアクションごとの関数を実行する前やあとにする処理を記載することができます
- handler/action_handler.go
package handler
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func HandleAction(dispatcher *Dispatcher) gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-Id")
if requestID == "" {
requestID = "dummy-request-id"
}
// パラメータ統合
if err := c.Request.ParseForm(); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
params := make(map[string]string)
for key, values := range c.Request.Form {
if len(values) > 0 {
params[key] = values[0]
}
}
actionName := params["Action"]
result, err := dispatcher.Execute(actionName, params, c.Request.Method, c.Request)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"requestId": requestID,
"action": actionName,
"result": result,
})
}
}
メインではアクションとそれに紐づく関数の登録を行います
紐づく関数は今回 main.go に記載していますが別の箇所に記載しても OK です
- vim main.go
package main
import (
"fmt"
"net/http"
"strconv"
"gin-sample/handler"
"github.com/gin-gonic/gin"
)
func main() {
// Dispatcher作成
dispatcher := handler.NewDispatcher()
// Action登録
dispatcher.Register("Hello", helloAction)
dispatcher.Register("Sum", sumAction)
// Gin
r := gin.Default()
// ★ここがポイント(引数付きハンドラ)
r.GET("/", handler.HandleAction(dispatcher))
r.POST("/", handler.HandleAction(dispatcher))
r.Run()
}
// --- Action実装 ---
func helloAction(params map[string]string, method string, r *http.Request) (any, error) {
name := params["name"]
if name == "" {
name = "guest"
}
return map[string]string{
"message": "hello " + name,
}, nil
}
func sumAction(params map[string]string, method string, r *http.Request) (any, error) {
aStr := params["a"]
bStr := params["b"]
// 文字列 → 数値変換
a, err := strconv.Atoi(aStr)
if err != nil {
return nil, fmt.Errorf("invalid parameter 'a'")
}
b, err := strconv.Atoi(bStr)
if err != nil {
return nil, fmt.Errorf("invalid parameter 'b'")
}
sum := a + b
return map[string]interface{}{
"a": a,
"b": b,
"sum": sum,
}, nil
}
動作確認は以下です
curl "http://localhost:8080/?Action=Hello&name=Taro"
{"action":"Hello","requestId":"dummy-request-id","result":{"message":"hello Taro"}}
curl -X POST http://localhost:8080 \
-d "Action=Sum&a=10&b=20"
{"action":"Sum","requestId":"dummy-request-id","result":{"a":10,"b":20,"sum":30}}
最後に
gin で dispatcher パターンを試してみました
エラーハンドリングやレスポンスモデルの作成などまだありますがリクエストのハンドリングの部分はだいたい試せたかなと思います
0 件のコメント:
コメントを投稿