본문으로 건너뛰기

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 업그레이드 요청 정보를 복사해 보존합니다. 다음 인터페이스로 헤더, 쿼리, 쿠키, 원격 주소, 호스트와 요청 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는 인터셉터의 모든 메서드에서 첫 번째 인자로 전달됩니다.

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처럼 별도 이름을 사용하십시오.

참고 ​