コンテンツにスキップ

Gin@フレームワーク

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


Context

Contextとは

受信したリクエストの全てのコンテキスト (例:処理中のコンテキスト) を持つ。

type Context struct {
    Request *http.Request

    Writer  ResponseWriter

    Params Params

    Keys map[string]any

    Errors errorMsgs

    Accepted []string
}


Request

処理中のコンテキスト (例:デッドライン、キャンセルなど) を持つ。

処理中のコンテキストは、gin.Context.Request.Context関数で取得できる。

type Context struct {
    Request *http.Request
}

Ginの計装で必要なコンテキストもgin.Context.Request.Contextである。

func Middleware(service string, opts ...Option) gin.HandlerFunc {

    ...


    return func(c *gin.Context) {

        ...

        savedCtx := c.Request.Context()

        ...

        ctx := cfg.Propagators.Extract(
            savedCtx,
            // Carrierとして使用するHTTPヘッダーを設定し、トレースコンテキストを抽出する
            propagation.HeaderCarrier(c.Request.Header)
        )

        ...

        tracer := otel.Tracer("<計装パッケージ名>")
        ctx, span := tracer.Start(ctx, spanName, opts...)

        ...
    }
}


Bind

▼ 処理

リクエストからデータを取得し、構造体に紐付ける。

Content-TypeヘッダーのMIMEタイプに応じて、バインド関数をコールし分ける。


BindJSON

▼ 処理

Content-TypeヘッダーのMIMEタイプがapplication/jsonであることが前提である。

リクエストからJSON型データを取得し、構造体に紐付ける。

type User struct {
    Id   int    `json:"id"   binding:"required"`
    Name string `json:"name" binding:"required"`
}


BindQuery

▼ 処理

クエリパラメーターからデータを取得し、構造体に紐付ける。


Get

▼ 処理

同じリクエストにてSet関数でセットされたmap型データから、インターフェース型で値を取得する。

値が存在しない場合は、第二返却値でfalseを返却する。


ShouldBindQuery (= ShouldBindWith)

▼ 処理

クエリパラメーターからデータを取得し、指定したバインディングツールを使用して、構造体に紐付ける。


JSON

▼ 処理

JSON型データとして、レスポンスを返信する。

第二引数の引数型がインターフェースになっているため、様々なデータ型を渡せる。

*実装例*

map型データを渡す。

package server

import (
    "github.com/gin-gonic/gin"
)

func fooHandler(ginCtx *gin.Context) {

    ...

    ginCtx.JSON(
        200,
        gin.H{id: 1,"name": "hiroki hasegawa"},
    )

    ...
}

構造体型データを渡す。

package server

import (
    "github.com/gin-gonic/gin"
)

type Foo struct {
    id int json:"id"
    name string json:"name"
}

func fooHandler(ginCtx *gin.Context) {

    ...

    ginCtx.JSON(
        200,
        &Foo{id: 1, name: "hiroki hasegawa"},
    )

    ...
}


MustGet

▼ 処理

同じリクエストにてSet関数でセットされたmap型データから、インターフェース型で値を取得する。

値が存在しない場合は、ランタイムエラーとなる。


Request

▼ Requestとは

受信したリクエストを情報を持つ。

▼ Context

受信したリクエストのコンテキストを取得する。

*実装例*

package server

import (
    "context"

    "github.com/gin-gonic/gin"
)

func getRequestContext(ginCtx *gin.Context) context.Context {

    // コンテキストを取得する
    ctx := ginCtx.Request.Context()

    return ctx
}

受信したリクエストのHTTPヘッダーを操作する。

*実装例*

package server

import (
    "log"

    "github.com/gin-gonic/gin"
)

func getRequestHeader(ginCtx *gin.Context) string {

    // HTTPヘッダーの特定の値を取得する
    val := ginCtx.Request.Header.Get("<ヘッダーのキー名>")

    log.Print(val)

    return val
}

*実装例*

package server

import (
    "log"

    "github.com/gin-gonic/gin"
)

func printRequestHeaderList(ginCtx *gin.Context) {

    // HTTPヘッダーのリストを取得する
    for k, vals := range ginCtx.Request.Header {
        log.Printf("%v", k)
        for _, v := range vals {
            log.Printf("%v", v)
        }
    }
}


Param

▼ 処理

クエリパラメーターからデータを取得する。

この後、構造体に紐付ける場合は、BindQuery関数を使用した方が良い。


Set

▼ 処理

当該のリクエストで利用できるmap型データに、値を保管する。

▼ 注意点

データ型を変換した値をSet関数で保管しないようにすることによりある。

Set関数後にGet関数で取得される値は、元のデータ型に関係なくインターフェース型に変換されてしまう。

そのため、例えば、タイプID型として値を保管したとしても、Get関数で得られたインターフェース型データを改めて変換しないといけなくなってしまう。

*実装例*

package middlewares

import (
    "strconv"

    "github.com/gin-gonic/gin"
)

// ConvertId パスパラメーターのidのデータ型を変換します。
func ConvertId() gin.HandlerFunc {

    return func(ginCtx *gin.Context) {

        id, err := strconv.Atoi(ginCtx.Param("id"))

        if err != nil {
            _ = ginCtx.Error(err)
            return
        }

        ginCtx.Set("id", id)

        ginCtx.Next()
    }
}
package controller

type UserController struct {
    *interfaces.Controller
    userInteractor *interactor.UserInteractor
}

func (uc *UserController) GetUser(ginCtx *gin.Context) {

    // インターフェース型になってしまう。
    userId, ok := ginCtx.Get("id")

    if !ok {
        uc.SendErrorJson(
            ginCtx,
            400,
            []string{"Parameters are not found."},
        )

        return
    }


Engine

Engine

▼ Engineとは

ルーティングを定義する。

router := gin.New()


Use

▼ Useとは

ユーザー定義のミドルウェアを使用する。

gin.HandlerFunc関数というGin固有のデータ型が必要である。

ミドルウェアは、Useした順番で実行する。

package main

func main() {

    ...

    router := gin.New()
    router.Use(FooMiddleware)

    ...
}

func FooMiddleware() gin.HandlerFunc {
    return func(ginCtx *gin.Context) {
        // ミドルウェアとして実行したい処理
    }
}

http.Handlerhttp.HandlerFuncからgin.HandlerFuncへの変換

gin.HandlerFunc関数を引数にとるパッケージにhttp.Handler関数やhttp.HandlerFunc関数を渡すことができる。

WrapH関数やWrapF関数を使用すると、http.Handlerhttp.HandlerFuncからgin.HandlerFuncに変換できる。

package main

func main() {

    ...

    router.Use(gin.WrapH(BarMiddleware(router)))
    router.Use(gin.WrapF(BazMiddleware(router)))

    ...
}

func BarMiddleware(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        // ミドルウェアとして実行したい処理
    }

    return http.HandlerFunc(fn)
}

func BazMiddleware(next http.Handler) http.HandlerFunc {
    fn := func(w http.ResponseWriter, r *http.Request) {
        // ミドルウェアとして実行したい処理
    }

    return http.HandlerFunc(fn)
}

gin.HandlerFuncからhttp.Handlerへの変換

http.Handler関数を引数にとるパッケージにgin.HandlerFunc関数を渡すことは諦めた方がいい。

gin.HandlerFunc関数はhttp.Handlerのラッパーである。

そのため、http.Handler関数からgin.HandlerFunc関数への変換は簡単にできるが、その逆はgin.HandlerFunc関数から値を取り出さないといけず、難しい。


Util

H

map型の変数のエイリアスとして働く。

type H map[string]interface{}
ginCtx.JSON(
  200,
  gin.H{"id": 1,"name": "hiroki hasegawa"},
)
ginCtx.JSON(
  400,
  gin.H{"errors": []string{"Fooエラーメッセージ", "Barエラーメッセージ"}},
)


Validator

tag

▼ binding

バリデーションのルールを定義する。

標準のルールの一覧は、以下のリンクを参考にせよ。