ミドルウェア


ミドルウェア は、エンドポイントまたはトランスポート固有のハンドラを受け付けて返す関数で構成されます。

ミドルウェアのエンドポイント

エンドポイント・ミドルウェアはエンドポイントレベルで動作し、トランスポートに依存しません。 これらは、Goa エンドポイントを受け付けて返す関数で構成されています。 ここで、エラーをログに記録するエンドポイント・ミドルウェアの例を次に示します。

// ErrorLogger は、指定されたロガーを使用してエラーを記録するエンドポイントミドルウェアです。
// すべてのログエントリは、指定されたプレフィックスで始まります。
func ErrorLogger(l *log.Logger, prefix string) func (goa.Endpoint) goa.Endpoint {
    return func(e goa.Endpoint) goa.Endpoint {
        // Goa エンドポイント自体が関数です。
        return goa.Endpoint(func (ctx context.Context, req interface{}) (interface{}, error) {
            // オリジナルのエンドポイント関数を呼び出します。
            res, err := e(ctx, req)
            // 何かエラーがあれば記録する。
            if err != nil {
                l.Printf("%s: %s", prefix, err.Error())
            }
            // エンドポイントの結果を返します。
            return res, err
        })
    }
}

Goa によって生成されたサービスコードは、ミドルウェアをサービスによって定義されたすべてのエンドポイントに適用する Use メソッドを定義します。 あるいは、対応するエンドポイント構造体フィールドでラップすることにより、ミドルウェアを特定のエンドポイントに適用できます。

import (
    calcsvc "goa.design/examples/basic/gen/calc"
    calc "goa.design/examples/basic"
)

func main() {
    // ...
    var svc calcsvc.Service
    {
        svc = calc.NewCalc(logger)
    }

    var eps *calcsvc.Endpoints
    {
        eps = calcsvc.NewEndpoints(svc)

        // ErrorLoggerをすべてのエンドポイントに適用します。
        calcsvc.Use(ErrorLogger(logger, "calc"))

        // または、特定のエンドポイントにErrorLoggerを適用します。
        // eps.Add = ErrorLogger(logger, "add")(eps.Add)
    }
    // ...
}

トランスポート ミドルウェア

トランスポート・ミドルウェアはトランスポート層で動作します。 HTTP ハンドラまたは gRPC メソッドに適用されます。

HTTP ミドルウェア

HTTP ミドルウェアは、HTTP ハンドラを取って、HTTP ハンドラを返す関数です。

ここで、X-Request-Id リクエストヘッダーの値を読み取り、該当リクエストヘッダーが存在する場合はリクエストコンテキストに書き込む HTTP ミドルウェアの例を次に示します。

// InitRequestID は、X-Request-Id ヘッダーの値を読み取り、存在する場合は
// それをリクエストコンテキストに書き込む HTTP サーバーミドルウェアです。
func InitRequestID() func(http.Handler) http.Handler {
    return func(h http.Handler) http.Handler {
        // A HTTP handler is a function.
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            req := r
            //  X-Request-Idヘッダーを取得して、リクエストコンテキストを初期化します。
            if id := r.Header.Get("X-Request-Id"); id != "" {
                ctx = context.WithValue(r.Context(), RequestIDKey, id)
                req = r.WithContext(ctx)
            }

            // 最初のハンドラを呼び出す。
            h.ServeHTTP(w, req)
        })
    }
}

HTTP ミドルウェアは、HTTPサーバー上に生成された Use メソッドを使用して適用できます。 このメソッドは、ミドルウェアをすべてのサーバーハンドラに適用します。 エンドポイント・ミドルウェアを特定のエンドポイントに適用する方法と同様に、 ミドルウェアを特定のサーバーハンドラに適用することもできます。 HTTP ミドルウェアを goa Muxer に直接マウントして、 ハンドラとは無関係にすべてのリクエストでミドルウェアを実行することもできます。

func main() {
    // ...
    var mux goahttp.Muxer
    {
        mux = goahttp.NewMuxer()
    }

    var calcServer *calcsvcsvr.Server
    {
        calcServer = calcsvcsvr.New(calcEndpoints, mux, dec, enc, errorHandler(logger))
        // InitRequestID ミドルウェアをすべてのサーバーハンドラに適用します。
        calcServer.Use(InitRequestID())
    }

    // あるいは、ミドルウェアをすべてのリクエストに適用します。
    // var handler http.Handler = mux
    // handler = InitRequestID()(handler)
    // ... ここで、mux の代わりにハンドラを使用して HTTP サーバーを起動します。
}

Goaには、次のような HTTP ミドルウェアの実装があります。

gRPC ミドルウェア

gRPC ミドルウェアは gRPC トランスポート固有であり、サーバーとクライアントの gRPC インターセプターで構成されます。

Goa は次の gRPC ミドルウェアを実装しています:

gRPC ミドルウェアを gRPC エンドポイントに適用する方法については、トレースの例を参照してください。