core.ExecutionContext
ExecutionContext is an interface for reading request information and sharing values between processing stages in interceptors and the execution pipeline. It is used for HTTP requests, events, and WebSocket messages, with separate interfaces providing additional information for each transport.
import "github.com/NARUBROWN/spine/core"Choosing an interface
To read values stored by an interceptor in an HTTP controller, declare a core.ControllerContext parameter. Use core.ExecutionContext in interceptors. Use HttpRequestContext and ConsumerRequestContext to resolve HTTP and event inputs, respectively.
| Interface | Embedded interfaces | Provides |
|---|---|---|
ContextCarrier | None | Go standard context.Context |
EventBusCarrier | None | Request-scoped event bus |
ExecutionContext | ContextCarrier, EventBusCarrier | Request method and path, headers and parameters, value storage and retrieval |
ControllerContext | None | Retrieval of values stored by interceptors |
HttpRequestContext | ContextCarrier, EventBusCarrier | HTTP input access, body binding, multipart forms |
ConsumerRequestContext | ContextCarrier, EventBusCarrier | Event name and raw body |
WebSocketContext | ExecutionContext | Connection ID, message type, and raw body |
WebSocketRequestContext | ContextCarrier | Information from the HTTP request that initiated the connection |
WebSocketHandshakeContext | WebSocketRequestContext | Request information for authentication and authorization before the HTTP upgrade |
WebSocketMessageContext | WebSocketContext, WebSocketRequestContext | Message and original connection request information |
ControllerContext does not embed ExecutionContext and provides only Get. HttpRequestContext and ConsumerRequestContext do not embed ExecutionContext either. The implementations provided by the runtime implement the required interfaces together.
Interface definitions
Base interfaces
type ContextCarrier interface {
Context() context.Context
}
type EventBusCarrier interface {
EventBus() EventBus
}EventBus() returns core.EventBus. It provides Publish(events ...publish.DomainEvent) to collect events and Drain() []publish.DomainEvent to retrieve the collected events.
ExecutionContext
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
An interface dedicated to reading values in HTTP controllers. Use Get() to retrieve values stored by interceptors with Set(). The default consumer and WebSocket input resolvers do not inject this type.
type ControllerContext interface {
Get(key string) (any, bool)
}HttpRequestContext
An interface for reading HTTP inputs. It embeds ContextCarrier and 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)
}ConsumerRequestContext
An interface for reading event consumer inputs. It embeds ContextCarrier and EventBusCarrier.
type ConsumerRequestContext interface {
ContextCarrier
EventBusCarrier
EventName() string
Payload() []byte
}WebSocketContext
An interface for reading WebSocket messages. It embeds ExecutionContext.
type WebSocketContext interface {
ExecutionContext
ConnID() string
MessageType() int
Payload() []byte
}WebSocket connection request information
v0.5.1 copies and preserves HTTP upgrade request information. The following interfaces provide access to headers, queries, cookies, the remote address, the host, and the request 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
}Use WebSocketHandshakeContext in PreHandshake. During message processing, assert ExecutionContext to WebSocketMessageContext to read the original connection request information. Headers(), Queries(), and Cookies() return copies, so modifying the returned maps does not change the preserved request information.
ExecutionContext methods
Context
Context() context.ContextReturns a context.Context from the Go standard library.
Returns
context.Context- Request-scoped context
Example
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.EventBusReturns the request-scoped event bus. Used to publish domain events.
Returns
core.EventBus- Event bus instance
In a controller that publishes events, accept context.Context as a parameter and call publish.Event(ctx, event). The runtime attaches the event bus to the standard context. See the event publishing example.
Method
Method() stringReturns the request method.
Returns
- HTTP:
"GET","POST","PUT","DELETE", etc. - Event consumer:
"EVENT" - WebSocket:
"WS"
Example
func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
if ctx.Method() == "OPTIONS" {
// CORS 사전 요청 처리
}
return nil
}Path
Path() stringReturns the request path.
Returns
- HTTP: Request path (for example,
"/users/123") - Event consumer: Event name (for example,
"order.created") - WebSocket: WebSocket path (for example,
"/ws/chat")
Example
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) stringReturns the value of the HTTP header with the specified name.
Parameters
name- Header name (case-insensitive)
Returns
string- Header value, or an empty string if absent- Event consumer: Empty string
- WebSocket: Header value preserved from the HTTP upgrade request
Example
origin := ctx.Header("Origin")
auth := ctx.Header("Authorization")Params
Params() map[string]stringReturns all path parameters as a map.
Returns
map[string]string- Map of path parameters- Event consumer:
nil - WebSocket: Path parameters resolved by the router. An empty map before routing
Example
// Route: /users/:userId/posts/:postId
// Request: /users/123/posts/456
params := ctx.Params() // {"userId": "123", "postId": "456"}PathKeys
PathKeys() []stringReturns path parameter keys in declaration order.
Returns
[]string- Slice of keys- Event consumer:
nil - WebSocket: Key order resolved by the router. An empty slice before routing
Example
// Route: /users/:userId/posts/:postId
ctx.PathKeys() // ["userId", "postId"]Queries
Queries() map[string][]stringReturns all query parameters as a map.
Returns
map[string][]string- Map of query parameters- Event consumer:
nil - WebSocket: Query parameters preserved from the HTTP upgrade request
Example
// Request: /users?status=active&tag=go&tag=web
queries := ctx.Queries()
// {"status": ["active"], "tag": ["go", "web"]}Set
Set(key string, value any)Stores a value in the internal store.
Parameters
key- Key to storevalue- Value to store
Example
ctx.Set("auth.user", authenticatedUser)
ctx.Set("request.startTime", time.Now())Get
Get(key string) (any, bool)Retrieves a value from the internal store.
Parameters
key- Key to retrieve
Returns
any- Stored valuebool- Whether the key exists
Example
if rw, ok := ctx.Get("spine.response_writer"); ok {
responseWriter := rw.(core.ResponseWriter)
}ControllerContext
An interface for reading values stored by interceptors in controllers.
Get
Get(key string) (any, bool)Delegates to ExecutionContext.Get(). It does not provide a Set() method.
The following code extends src/user/user_controller.go in a CLI project. Define User, AuthUser, and the repository field in your application. Keep the generated user.Register(app) and root 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 methods
Additional methods used to resolve HTTP inputs.
Param
Param(name string) stringReturns the value of a specific path parameter.
Example
userId := ctx.Param("id") // "123"Query
Query(name string) stringReturns the first value of a specific query parameter.
Example
page := ctx.Query("page") // "1"Headers
Headers() map[string][]stringReturns all HTTP headers as a map.
Example
headers := ctx.Headers()
// {"Content-Type": ["application/json"], "Accept": ["text/html", "application/json"]}Bind
Bind(out any) errorBinds the HTTP request body to a struct.
Example
var req CreateUserRequest
if err := ctx.Bind(&req); err != nil {
return err
}MultipartForm
MultipartForm() (*multipart.Form, error)Provides access to multipart form data.
Example
form, err := ctx.MultipartForm()
if err != nil {
return err
}
for _, file := range form.File["upload"] {
// 파일 처리
}ConsumerRequestContext methods
Methods used to resolve event inputs.
EventName
EventName() stringReturns the name of the received event.
Example
name := ctx.EventName() // "order.created"Payload
Payload() []byteReturns the event's raw payload.
Example
payload := ctx.Payload() // []byte (JSON)
var event OrderCreated
json.Unmarshal(payload, &event)WebSocketContext methods
Methods used to resolve WebSocket inputs.
ConnID
ConnID() stringReturns the unique identifier of the WebSocket connection.
Example
connID := ctx.ConnID() // "a1b2c3d4-..."MessageType
MessageType() intReturns the WebSocket message type.
Returns
1- TextMessage2- BinaryMessage
Example
if ctx.MessageType() == ws.TextMessage {
// 텍스트 메시지 처리
}Payload
Payload() []byteReturns the WebSocket message's raw payload.
Example
payload := ctx.Payload() // []byte
var msg ChatMessage
json.Unmarshal(payload, &msg)Reserved keys
| Key | Type | Description |
|---|---|---|
spine.response_writer | core.ResponseWriter | Response output interface |
spine.params | map[string]string | Path parameters |
spine.pathKeys | []string | Path parameter key order |
Using ExecutionContext in interceptors
ExecutionContext is passed as the first argument to every interceptor method.
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)
}Logging example
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 example
The following example accesses the response output interface. Define Config and isAllowedOrigin in your application. For a complete CORS configuration, use the validatable built-in interceptor.
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
}Using ExecutionContext in input resolution
Input resolution components receive core.ExecutionContext and assert it to the interface for the relevant transport. For example, reading an HTTP path parameter requires core.HttpRequestContext, reading an event name requires core.ConsumerRequestContext, and reading a WebSocket connection ID requires core.WebSocketContext. The type assertion fails if the context does not implement the required interface.
For an HTTP controller's context.Context parameter, the runtime passes the standard context with a request-scoped event bus. For its core.ControllerContext parameter, it passes a view that provides only Get(). The default consumer and WebSocket input resolvers do not inject ControllerContext. The runtime's input resolution code is available in the following sources.
- HTTP path parameter resolution
- Standard context injection
- Controller read context injection
- Event input resolution
- WebSocket input resolution
Behavior by protocol
| Method | HTTP | Event consumer | WebSocket |
|---|---|---|---|
Method() | "GET", "POST", etc. | "EVENT" | "WS" |
Path() | /users/123 | order.created | /ws/chat |
Header() | Header value | Empty string | Header value from the connection request |
Params() | Path parameters | nil | Path parameters resolved by the router |
PathKeys() | Path parameter key order | nil | Path parameter key order |
Queries() | Query parameters | nil | Query parameters from the connection request |
EventBus() | EventBus | EventBus | EventBus |
Context() | Request context | Event processing context | Message processing context |
Implementations
| Implementation | Interfaces | Location |
|---|---|---|
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 |
Constraints and errors
- Global interceptors'
PreHandleruns before routing.HandlerMeta{}is empty at this point, so do not access the controller type or method directly. Read path parameters only after routing as well. Getreturns(nil, false)when the key is absent. Check the type of the stored value before using it.ControllerContextdoes not prevent changes to the stored objects themselves.HeaderandQueryreturn an empty string when the value is absent. WebSocket'sCookiereturns both the value and whether it exists.BindandMultipartFormreturn errors if body parsing fails. Return the error to the caller and stop processing.- The runtime uses reserved keys that start with
spine.. Use separate names such asauth.userfor values shared by your application.
See also
- Interceptors: Shared processing such as authentication and logging
- Execution context concepts - Detailed explanation
