core.ExecutionContext
ExecutionContextは、インターセプターと実行パイプラインでリクエスト情報を読み取り、処理段階の間で値を共有するためのインターフェースです。HTTPリクエスト、イベント、WebSocketメッセージで共通に使用し、トランスポートごとの追加情報は個別のインターフェースで提供します。
import "github.com/NARUBROWN/spine/core"インターフェースの選択
HTTPコントローラーでインターセプターが保存した値を読み取るには、core.ControllerContextを引数として宣言します。インターセプターではcore.ExecutionContextを使用します。HTTPとイベントの入力の解釈には、それぞれHttpRequestContextとConsumerRequestContextを使用します。
| インターフェース | 含むインターフェース | 提供する機能 |
|---|---|---|
ContextCarrier | なし | Go標準のcontext.Context |
EventBusCarrier | なし | リクエストスコープのイベントバス |
ExecutionContext | ContextCarrier, EventBusCarrier | リクエストのメソッドとパス、ヘッダーとパラメータ、値の保存と取得 |
ControllerContext | なし | インターセプターが保存した値の取得 |
HttpRequestContext | ContextCarrier, EventBusCarrier | HTTP入力の取得、本文のバインド、マルチパートフォーム |
ConsumerRequestContext | ContextCarrier, EventBusCarrier | イベント名と生の本文 |
WebSocketContext | ExecutionContext | 接続ID、メッセージ型、生の本文 |
WebSocketRequestContext | ContextCarrier | 接続を要求したHTTPリクエストの情報 |
WebSocketHandshakeContext | WebSocketRequestContext | HTTPアップグレード前の認証と認可に使用するリクエスト情報 |
WebSocketMessageContext | WebSocketContext, WebSocketRequestContext | メッセージと元の接続リクエスト情報 |
ControllerContextはExecutionContextを含まず、Getだけを提供します。HttpRequestContextとConsumerRequestContextもExecutionContextを含みません。ランタイムが提供する実装は、必要なインターフェースを併せて実装します。
インターフェース定義
基底インターフェース
type ContextCarrier interface {
Context() context.Context
}
type EventBusCarrier interface {
EventBus() EventBus
}EventBus()はcore.EventBusを返します。イベントを収集するPublish(events ...publish.DomainEvent)と、収集したイベントを取り出すDrain() []publish.DomainEventを提供します。
ExecutionContext
type ExecutionContext interface {
ContextCarrier
EventBusCarrier
Method() string
Path() string
Params() map[string]string
Header(name string) string
PathKeys() []string
SetRoute(params map[string]string, pathKeys []string)
Queries() map[string][]string
Set(key string, value any)
Get(key string) (any, bool)
}ControllerContext
HTTPコントローラーで値を読み取るための専用インターフェースです。インターセプターがSet()で保存した値をGet()で取得できます。標準のコンシューマーとWebSocketの入力リゾルバーは、この型を注入しません。
type ControllerContext interface {
Get(key string) (any, bool)
}HttpRequestContext
HTTP入力を読み取るインターフェースです。ContextCarrierとEventBusCarrierを含みます。
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)
}HTTPResponseContext
type HTTPResponseContext interface {
ResponseWriter() ResponseWriter
SetResponseWriter(ResponseWriter)
}HTTPResponseContext.ResponseWriter() は HTTP 応答 writer を返します。パイプラインは応答準備中に staging writer に置き換え、送信前に復元します。コントローラーは通常、writer に直接アクセスせず httpx.Response[T]、httpx.Redirect、httpx.Binary を返します。
ConsumerRequestContext
イベントコンシューマーの入力を読み取るインターフェースです。ContextCarrierとEventBusCarrierを含みます。
type ConsumerRequestContext interface {
ContextCarrier
EventBusCarrier
EventName() string
Payload() []byte
}WebSocketContext
WebSocketメッセージを読み取るインターフェースです。ExecutionContextを含みます。
type WebSocketContext interface {
ExecutionContext
ConnID() string
MessageType() int
Payload() []byte
}WebSocketの接続リクエスト情報
v0.6.0はHTTPアップグレードリクエストの情報をコピーして保持します。次のインターフェースで、ヘッダー、クエリ、Cookie、リモートアドレス、ホスト、リクエストURIを取得できます。
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
Context() context.ContextGo標準ライブラリのcontext.Contextを返します。
戻り値
context.Context- リクエストスコープのコンテキスト
例
func (i *TimeoutInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
select {
case <-ctx.Context().Done():
return ctx.Context().Err()
default:
return nil
}
}EventBus
EventBus() core.EventBusリクエストスコープのイベントバスを返します。ドメインイベントの発行に使用します。
戻り値
core.EventBus- イベントバスのインスタンス
イベントを発行するコントローラーでは、context.Contextを引数として受け取り、publish.Event(ctx, event)を呼び出します。ランタイムがイベントバスを標準コンテキストに接続します。イベント発行の例を参照してください。
Method
Method() stringリクエストメソッドを返します。
戻り値
- HTTP:
"GET"、"POST"、"PUT"、"DELETE"など - イベントコンシューマー:
"EVENT" - WebSocket:
"WS"
例
func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
if ctx.Method() == "OPTIONS" {
// CORS 사전 요청 처리
}
return nil
}Path
Path() stringリクエストパスを返します。
戻り値
- HTTP: リクエストパス(例:
"/users/123") - イベントコンシューマー: イベント名(例:
"order.created") - WebSocket: WebSocketのパス(例:
"/ws/chat")
例
log.Printf("[REQ] %s %s", ctx.Method(), ctx.Path())
// HTTP: [REQ] GET /users/123
// Consumer: [REQ] EVENT order.created
// WebSocket: [REQ] WS /ws/chatHeader
Header(name string) string指定した名前のHTTPヘッダー値を返します。
引数
name- ヘッダー名(大文字と小文字を区別しません)
戻り値
string- ヘッダー値。存在しない場合は空文字列- イベントコンシューマー: 空文字列
- WebSocket: HTTPアップグレードリクエストから保持したヘッダー値
例
origin := ctx.Header("Origin")
auth := ctx.Header("Authorization")Params
Params() map[string]stringすべてのパスパラメータをマップで返します。
戻り値
map[string]string- パスパラメータのマップ- イベントコンシューマー:
nil - WebSocket: ルーターが解釈したパスパラメータ。ルーティング前は空のマップ
例
// Route: /users/:userId/posts/:postId
// Request: /users/123/posts/456
params := ctx.Params() // {"userId": "123", "postId": "456"}PathKeys
PathKeys() []stringパスパラメータのキーを宣言順に返します。
戻り値
[]string- キーのスライス- イベントコンシューマー:
nil - WebSocket: ルーターが解釈したキーの順序。ルーティング前は空のスライス
例
// Route: /users/:userId/posts/:postId
ctx.PathKeys() // ["userId", "postId"]SetRoute
SetRoute(params map[string]string, pathKeys []string)ルーターが解決したパラメーターとキーの順序を保存します。実装は入力をコピーし、ユーザーの Set/Get ストアと分離します。ExecutionContext を独自実装する場合、このメソッドも必要です。
Queries
Queries() map[string][]stringすべてのクエリパラメータをマップで返します。
戻り値
map[string][]string- クエリパラメータのマップ- イベントコンシューマー:
nil - WebSocket: HTTPアップグレードリクエストから保持したクエリパラメータ
例
// Request: /users?status=active&tag=go&tag=web
queries := ctx.Queries()
// {"status": ["active"], "tag": ["go", "web"]}Set
Set(key string, value any)内部ストアに値を保存します。
引数
key- 保存するキーvalue- 保存する値
例
ctx.Set("auth.user", authenticatedUser)
ctx.Set("request.startTime", time.Now())Get
Get(key string) (any, bool)内部ストアから値を取得します。
引数
key- 取得するキー
戻り値
any- 保存された値bool- キーの存在有無
例
value, exists := ctx.Get("auth.user")ControllerContext
コントローラーでインターセプターが保存した値を読み取るインターフェースです。
Get
Get(key string) (any, bool)ExecutionContext.Get()に委譲します。Set()メソッドは提供しません。
以下のコードは、CLIプロジェクトのsrc/user/user_controller.goを拡張する例です。User、AuthUser、リポジトリのフィールドはアプリで定義します。生成されたuser.Register(app)とルートのmain.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
Param(name string) string指定したパスパラメータの値を返します。
例
userId := ctx.Param("id") // "123"Query
Query(name string) string指定したクエリパラメータの最初の値を返します。
例
page := ctx.Query("page") // "1"Headers
Headers() map[string][]stringすべてのHTTPヘッダーをマップで返します。
例
headers := ctx.Headers()
// {"Content-Type": ["application/json"], "Accept": ["text/html", "application/json"]}Bind
Bind(out any) errorHTTPリクエストの本文を構造体にバインドします。
例
var req CreateUserRequest
if err := ctx.Bind(&req); err != nil {
return err
}MultipartForm
MultipartForm() (*multipart.Form, error)マルチパートフォームのデータにアクセスします。
例
form, err := ctx.MultipartForm()
if err != nil {
return err
}
for _, file := range form.File["upload"] {
// 파일 처리
}ConsumerRequestContextのメソッド
イベント入力を解釈するときに使用するメソッドです。
EventName
EventName() string受信したイベントの名前を返します。
例
name := ctx.EventName() // "order.created"Payload
Payload() []byteイベントの生のペイロードを返します。
例
payload := ctx.Payload() // []byte (JSON)
var event OrderCreated
json.Unmarshal(payload, &event)WebSocketContextのメソッド
WebSocket入力を解釈するときに使用するメソッドです。
ConnID
ConnID() stringWebSocket接続の一意の識別子を返します。
例
connID := ctx.ConnID() // "a1b2c3d4-..."MessageType
MessageType() intWebSocketメッセージの型を返します。
戻り値
1- TextMessage2- BinaryMessage
例
if ctx.MessageType() == ws.TextMessage {
// 텍스트 메시지 처리
}Payload
Payload() []byteWebSocketメッセージの生のペイロードを返します。
例
payload := ctx.Payload() // []byte
var msg ChatMessage
json.Unmarshal(payload, &msg)ルーティングと応答の契約
ルーティング情報と HTTP 応答 writer は、ユーザーの Set/Get ストアとは独立した型付き契約で管理します。ルーターは SetRoute でパス値を保存し、HTTP パイプラインは HTTPResponseContext で writer を選択します。
| API | 型 | 説明 |
|---|---|---|
HTTPResponseContext.ResponseWriter() | core.ResponseWriter | レスポンス出力インターフェース |
Params() | map[string]string | パスパラメータ |
PathKeys() | []string | パスパラメータのキーの順序 |
インターセプターでの使用
ExecutionContextは、インターセプターのすべてのメソッドに第1引数として渡されます。
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)
}ログ出力の例
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全体の設定には、検証可能な標準インターセプターを使用してください。
type CORSInterceptor struct {
config Config
}
func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
httpCtx, ok := ctx.(core.HTTPResponseContext)
if !ok || httpCtx.ResponseWriter() == nil {
return nil
}
rw := httpCtx.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" {
if err := rw.WriteStatus(204); err != nil {
return err
}
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/123 | order.created | /ws/chat |
Header() | ヘッダー値 | 空文字列 | 接続リクエストのヘッダー値 |
Params() | パスパラメータ | nil | ルーターが解釈したパスパラメータ |
PathKeys() | パスパラメータのキーの順序 | nil | パスパラメータのキーの順序 |
Queries() | クエリパラメータ | nil | 接続リクエストのクエリパラメータ |
EventBus() | EventBus | EventBus | EventBus |
Context() | リクエストコンテキスト | イベント処理コンテキスト | メッセージ処理コンテキスト |
実装
| 実装 | インターフェース | 場所 |
|---|---|---|
echoContext | ExecutionContext + HttpRequestContext | internal/adapter/echo/context_impl.go |
ConsumerRequestContextImpl | ExecutionContext + ConsumerRequestContext | internal/event/consumer/request_context_impl.go |
WSExecutionContext | WebSocketMessageContext | internal/ws/context_impl.go |
controllerCtxView | ControllerContext | internal/runtime/controller_ctx.go |
制約とエラー
- グローバルインターセプターの
PreHandleは、ルーティング前に実行されます。この時点ではHandlerMeta{}が空のため、コントローラーの型やメソッドに直接アクセスしないでください。パスパラメータもルーティング後に読み取る必要があります。 Getは、キーが存在しない場合に(nil, false)を返します。保存された値の型を確認してから使用してください。ControllerContextは、保存されたオブジェクト自体の変更を防ぐものではありません。HeaderとQueryは、値が存在しない場合に空文字列を返します。WebSocketのCookieは、値と存在有無を併せて返します。BindとMultipartFormは、本文の解釈に失敗するとエラーを返します。呼び出し元にエラーを返し、処理を中断してください。spine.で始まる予約済みキーは、ランタイムが使用します。アプリで共有する値には、auth.userのような別の名前を使用してください。
関連情報
- インターセプター: 認証やログ出力などの共通処理
- 実行コンテキストの概念 - 詳細な説明
