本文へスキップ

core.ExecutionContext ​

ExecutionContextは、インターセプターと実行パイプラインでリクエスト情報を読み取り、処理段階の間で値を共有するためのインターフェースです。HTTPリクエスト、イベント、WebSocketメッセージで共通に使用し、トランスポートごとの追加情報は個別のインターフェースで提供します。

go
import "github.com/NARUBROWN/spine/core"

インターフェースの選択 ​

HTTPコントローラーでインターセプターが保存した値を読み取るには、core.ControllerContextを引数として宣言します。インターセプターではcore.ExecutionContextを使用します。HTTPとイベントの入力の解釈には、それぞれHttpRequestContextとConsumerRequestContextを使用します。

インターフェース含むインターフェース提供する機能
ContextCarrierなしGo標準のcontext.Context
EventBusCarrierなしリクエストスコープのイベントバス
ExecutionContextContextCarrier, EventBusCarrierリクエストのメソッドとパス、ヘッダーとパラメータ、値の保存と取得
ControllerContextなしインターセプターが保存した値の取得
HttpRequestContextContextCarrier, EventBusCarrierHTTP入力の取得、本文のバインド、マルチパートフォーム
ConsumerRequestContextContextCarrier, EventBusCarrierイベント名と生の本文
WebSocketContextExecutionContext接続ID、メッセージ型、生の本文
WebSocketRequestContextContextCarrier接続を要求したHTTPリクエストの情報
WebSocketHandshakeContextWebSocketRequestContextHTTPアップグレード前の認証と認可に使用するリクエスト情報
WebSocketMessageContextWebSocketContext, WebSocketRequestContextメッセージと元の接続リクエスト情報

ControllerContextはExecutionContextを含まず、Getだけを提供します。HttpRequestContextとConsumerRequestContextもExecutionContextを含みません。ランタイムが提供する実装は、必要なインターフェースを併せて実装します。

インターフェース定義 ​

基底インターフェース ​

go
type ContextCarrier interface {
    Context() context.Context
}

type EventBusCarrier interface {
    EventBus() EventBus
}

EventBus()はcore.EventBusを返します。イベントを収集するPublish(events ...publish.DomainEvent)と、収集したイベントを取り出すDrain() []publish.DomainEventを提供します。

ExecutionContext ​

go
type ExecutionContext interface {
    ContextCarrier
    EventBusCarrier

    Method() string
    Path() string
    Params() map[string]string
    Header(name string) string
    PathKeys() []string
    Queries() map[string][]string
    Set(key string, value any)
    Get(key string) (any, bool)
}

ControllerContext ​

HTTPコントローラーで値を読み取るための専用インターフェースです。インターセプターがSet()で保存した値をGet()で取得できます。標準のコンシューマーとWebSocketの入力リゾルバーは、この型を注入しません。

go
type ControllerContext interface {
    Get(key string) (any, bool)
}

HttpRequestContext ​

HTTP入力を読み取るインターフェースです。ContextCarrierとEventBusCarrierを含みます。

go
type HttpRequestContext interface {
    ContextCarrier
    EventBusCarrier

    // 개별 접근
    Param(name string) string
    Query(name string) string
    Header(name string) string

    // 전체 뷰 접근
    Params() map[string]string
    Queries() map[string][]string
    Headers() map[string][]string

    // 요청 본문
    Bind(out any) error

    // 멀티파트
    MultipartForm() (*multipart.Form, error)
}

ConsumerRequestContext ​

イベントコンシューマーの入力を読み取るインターフェースです。ContextCarrierとEventBusCarrierを含みます。

go
type ConsumerRequestContext interface {
    ContextCarrier
    EventBusCarrier

    EventName() string
    Payload() []byte
}

WebSocketContext ​

WebSocketメッセージを読み取るインターフェースです。ExecutionContextを含みます。

go
type WebSocketContext interface {
    ExecutionContext

    ConnID() string
    MessageType() int
    Payload() []byte
}

WebSocketの接続リクエスト情報 ​

v0.5.1はHTTPアップグレードリクエストの情報をコピーして保持します。次のインターフェースで、ヘッダー、クエリ、Cookie、リモートアドレス、ホスト、リクエストURIを取得できます。

go
type WebSocketRequestContext interface {
    ContextCarrier
    Path() string
    Header(name string) string
    Headers() map[string][]string
    Query(name string) string
    Queries() map[string][]string
    Cookie(name string) (string, bool)
    Cookies() map[string]string
    RemoteAddr() string
    Host() string
    RequestURI() string
}

type WebSocketHandshakeContext interface { WebSocketRequestContext }

type WebSocketMessageContext interface {
    WebSocketContext
    WebSocketRequestContext
}

WebSocketHandshakeContextはPreHandshakeで使用します。メッセージの処理中は、ExecutionContextをWebSocketMessageContextに型アサーションして、元の接続リクエスト情報を読み取ります。Headers()、Queries()、Cookies()はコピーを返すため、返されたマップを変更しても、保持されているリクエスト情報は変わりません。

ExecutionContextのメソッド ​

Context ​

go
Context() context.Context

Go標準ライブラリのcontext.Contextを返します。

戻り値

  • context.Context - リクエストスコープのコンテキスト

例

go
func (i *TimeoutInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    select {
    case <-ctx.Context().Done():
        return ctx.Context().Err()
    default:
        return nil
    }
}

EventBus ​

go
EventBus() core.EventBus

リクエストスコープのイベントバスを返します。ドメインイベントの発行に使用します。

戻り値

  • core.EventBus - イベントバスのインスタンス

イベントを発行するコントローラーでは、context.Contextを引数として受け取り、publish.Event(ctx, event)を呼び出します。ランタイムがイベントバスを標準コンテキストに接続します。イベント発行の例を参照してください。

Method ​

go
Method() string

リクエストメソッドを返します。

戻り値

  • HTTP: "GET"、"POST"、"PUT"、"DELETE"など
  • イベントコンシューマー: "EVENT"
  • WebSocket: "WS"

例

go
func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    if ctx.Method() == "OPTIONS" {
        // CORS 사전 요청 처리
    }
    return nil
}

Path ​

go
Path() string

リクエストパスを返します。

戻り値

  • HTTP: リクエストパス(例: "/users/123")
  • イベントコンシューマー: イベント名(例: "order.created")
  • WebSocket: WebSocketのパス(例: "/ws/chat")

例

go
log.Printf("[REQ] %s %s", ctx.Method(), ctx.Path())
// HTTP:      [REQ] GET /users/123
// Consumer:  [REQ] EVENT order.created
// WebSocket: [REQ] WS /ws/chat
go
Header(name string) string

指定した名前のHTTPヘッダー値を返します。

引数

  • name - ヘッダー名(大文字と小文字を区別しません)

戻り値

  • string - ヘッダー値。存在しない場合は空文字列
  • イベントコンシューマー: 空文字列
  • WebSocket: HTTPアップグレードリクエストから保持したヘッダー値

例

go
origin := ctx.Header("Origin")
auth := ctx.Header("Authorization")

Params ​

go
Params() map[string]string

すべてのパスパラメータをマップで返します。

戻り値

  • map[string]string - パスパラメータのマップ
  • イベントコンシューマー: nil
  • WebSocket: ルーターが解釈したパスパラメータ。ルーティング前は空のマップ

例

go
// Route: /users/:userId/posts/:postId
// Request: /users/123/posts/456

params := ctx.Params()  // {"userId": "123", "postId": "456"}

PathKeys ​

go
PathKeys() []string

パスパラメータのキーを宣言順に返します。

戻り値

  • []string - キーのスライス
  • イベントコンシューマー: nil
  • WebSocket: ルーターが解釈したキーの順序。ルーティング前は空のスライス

例

go
// Route: /users/:userId/posts/:postId

ctx.PathKeys()  // ["userId", "postId"]

Queries ​

go
Queries() map[string][]string

すべてのクエリパラメータをマップで返します。

戻り値

  • map[string][]string - クエリパラメータのマップ
  • イベントコンシューマー: nil
  • WebSocket: HTTPアップグレードリクエストから保持したクエリパラメータ

例

go
// Request: /users?status=active&tag=go&tag=web

queries := ctx.Queries()
// {"status": ["active"], "tag": ["go", "web"]}

Set ​

go
Set(key string, value any)

内部ストアに値を保存します。

引数

  • key - 保存するキー
  • value - 保存する値

例

go
ctx.Set("auth.user", authenticatedUser)
ctx.Set("request.startTime", time.Now())

Get ​

go
Get(key string) (any, bool)

内部ストアから値を取得します。

引数

  • key - 取得するキー

戻り値

  • any - 保存された値
  • bool - キーの存在有無

例

go
if rw, ok := ctx.Get("spine.response_writer"); ok {
    responseWriter := rw.(core.ResponseWriter)
}

ControllerContext ​

コントローラーでインターセプターが保存した値を読み取るインターフェースです。

Get ​

go
Get(key string) (any, bool)

ExecutionContext.Get()に委譲します。Set()メソッドは提供しません。

以下のコードは、CLIプロジェクトのsrc/user/user_controller.goを拡張する例です。User、AuthUser、リポジトリのフィールドはアプリで定義します。生成されたuser.Register(app)とルートのmain.goは維持します。

go
func (c *UserController) GetUser(ctx core.ControllerContext, userId path.Int) (httpx.Response[User], error) {
    // 인터셉터가 Set("auth.user", ...)으로 저장한 값 조회
    if authUser, ok := ctx.Get("auth.user"); ok {
        _ = authUser.(*AuthUser)
        // ...
    }
    user, err := c.repo.FindByID(userId.Value)
    if err != nil {
        return httpx.Response[User]{}, err
    }
    return httpx.Response[User]{Body: user}, nil
}

HttpRequestContextのメソッド ​

HTTP入力を解釈するときに使用する追加メソッドです。

Param ​

go
Param(name string) string

指定したパスパラメータの値を返します。

例

go
userId := ctx.Param("id")  // "123"

Query ​

go
Query(name string) string

指定したクエリパラメータの最初の値を返します。

例

go
page := ctx.Query("page")  // "1"

Headers ​

go
Headers() map[string][]string

すべてのHTTPヘッダーをマップで返します。

例

go
headers := ctx.Headers()
// {"Content-Type": ["application/json"], "Accept": ["text/html", "application/json"]}

Bind ​

go
Bind(out any) error

HTTPリクエストの本文を構造体にバインドします。

例

go
var req CreateUserRequest
if err := ctx.Bind(&req); err != nil {
    return err
}

MultipartForm ​

go
MultipartForm() (*multipart.Form, error)

マルチパートフォームのデータにアクセスします。

例

go
form, err := ctx.MultipartForm()
if err != nil {
    return err
}
for _, file := range form.File["upload"] {
    // 파일 처리
}

ConsumerRequestContextのメソッド ​

イベント入力を解釈するときに使用するメソッドです。

EventName ​

go
EventName() string

受信したイベントの名前を返します。

例

go
name := ctx.EventName()  // "order.created"

Payload ​

go
Payload() []byte

イベントの生のペイロードを返します。

例

go
payload := ctx.Payload()  // []byte (JSON)
var event OrderCreated
json.Unmarshal(payload, &event)

WebSocketContextのメソッド ​

WebSocket入力を解釈するときに使用するメソッドです。

ConnID ​

go
ConnID() string

WebSocket接続の一意の識別子を返します。

例

go
connID := ctx.ConnID()  // "a1b2c3d4-..."

MessageType ​

go
MessageType() int

WebSocketメッセージの型を返します。

戻り値

  • 1 - TextMessage
  • 2 - BinaryMessage

例

go
if ctx.MessageType() == ws.TextMessage {
    // 텍스트 메시지 처리
}

Payload ​

go
Payload() []byte

WebSocketメッセージの生のペイロードを返します。

例

go
payload := ctx.Payload()  // []byte
var msg ChatMessage
json.Unmarshal(payload, &msg)

予約済みキー ​

キー型説明
spine.response_writercore.ResponseWriterレスポンス出力インターフェース
spine.paramsmap[string]stringパスパラメータ
spine.pathKeys[]stringパスパラメータのキーの順序

インターセプターでの使用 ​

ExecutionContextは、インターセプターのすべてのメソッドに第1引数として渡されます。

go
type Interceptor interface {
    PreHandle(ctx ExecutionContext, meta HandlerMeta) error
    PostHandle(ctx ExecutionContext, meta HandlerMeta)
    BeforeResponse(ctx ExecutionContext, meta HandlerMeta, executionErr error) error
    AfterCompletion(ctx ExecutionContext, meta HandlerMeta, err error)
}

ログ出力の例 ​

go
type LoggingInterceptor struct{}

func (i *LoggingInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    log.Printf("[REQ] %s %s", ctx.Method(), ctx.Path())
    return nil
}

func (i *LoggingInterceptor) PostHandle(ctx core.ExecutionContext, meta core.HandlerMeta) {
    log.Printf("[RES] %s %s OK", ctx.Method(), ctx.Path())
}

func (i *LoggingInterceptor) BeforeResponse(ctx core.ExecutionContext, meta core.HandlerMeta, executionErr error) error {
    return nil
}

func (i *LoggingInterceptor) AfterCompletion(ctx core.ExecutionContext, meta core.HandlerMeta, err error) {
    if err != nil {
        log.Printf("[ERR] %s %s : %v", ctx.Method(), ctx.Path(), err)
    }
}

CORSの例 ​

次は、レスポンス出力インターフェースにアクセスする例です。ConfigとisAllowedOriginはアプリで定義します。CORS全体の設定には、検証可能な標準インターセプターを使用してください。

go
type CORSInterceptor struct {
    config Config
}

func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    rwAny, ok := ctx.Get("spine.response_writer")
    if !ok {
        return nil
    }
    rw := rwAny.(core.ResponseWriter)

    origin := ctx.Header("Origin")
    if origin != "" && i.isAllowedOrigin(origin) {
        rw.SetHeader("Access-Control-Allow-Origin", origin)
        rw.SetHeader("Vary", "Origin")
    }

    if ctx.Method() == "OPTIONS" {
        rw.WriteStatus(204)
        return core.ErrAbortPipeline
    }

    return nil
}

入力の解釈での使用 ​

入力を解釈するコンポーネントは、core.ExecutionContextを受け取り、トランスポートに対応するインターフェースに型アサーションします。たとえば、HTTPのパスパラメータを読み取るにはcore.HttpRequestContext、イベント名を読み取るにはcore.ConsumerRequestContext、WebSocketの接続IDを読み取るにはcore.WebSocketContextが必要です。対応するインターフェースを実装していないコンテキストでは、型アサーションが失敗します。

HTTPコントローラーのcontext.Context引数には標準コンテキストとリクエストスコープのイベントバスを渡し、core.ControllerContext引数にはGet()だけを提供するビューを渡します。コンシューマーとWebSocketの標準入力リゾルバーには、ControllerContextを注入する機能がありません。ランタイムの入力解釈コードは、次のソースで確認できます。

プロトコルごとの動作の違い ​

メソッドHTTPイベントコンシューマーWebSocket
Method()"GET"、"POST"など"EVENT""WS"
Path()/users/123order.created/ws/chat
Header()ヘッダー値空文字列接続リクエストのヘッダー値
Params()パスパラメータnilルーターが解釈したパスパラメータ
PathKeys()パスパラメータのキーの順序nilパスパラメータのキーの順序
Queries()クエリパラメータnil接続リクエストのクエリパラメータ
EventBus()EventBusEventBusEventBus
Context()リクエストコンテキストイベント処理コンテキストメッセージ処理コンテキスト

実装 ​

実装インターフェース場所
echoContextExecutionContext + HttpRequestContextinternal/adapter/echo/context_impl.go
ConsumerRequestContextImplExecutionContext + ConsumerRequestContextinternal/event/consumer/request_context_impl.go
WSExecutionContextWebSocketMessageContextinternal/ws/context_impl.go
controllerCtxViewControllerContextinternal/runtime/controller_ctx.go

制約とエラー ​

  • グローバルインターセプターのPreHandleは、ルーティング前に実行されます。この時点ではHandlerMeta{}が空のため、コントローラーの型やメソッドに直接アクセスしないでください。パスパラメータもルーティング後に読み取る必要があります。
  • Getは、キーが存在しない場合に(nil, false)を返します。保存された値の型を確認してから使用してください。ControllerContextは、保存されたオブジェクト自体の変更を防ぐものではありません。
  • HeaderとQueryは、値が存在しない場合に空文字列を返します。WebSocketのCookieは、値と存在有無を併せて返します。
  • BindとMultipartFormは、本文の解釈に失敗するとエラーを返します。呼び出し元にエラーを返し、処理を中断してください。
  • spine.で始まる予約済みキーは、ランタイムが使用します。アプリで共有する値には、auth.userのような別の名前を使用してください。

関連情報 ​