3. Context

Context は各ハンドラに func(*Context) Node として渡され、リクエスト情報と状態へアクセスするための API を提供します。

リクエストローカル state と共有 state

  • そのリクエスト内だけで使う一時値は Context.Local に置きます。
  • アプリ全体で共有する値は Context.SetGlobal / Context.GetGlobal を使います。名前に Global が付く API はアプリの全ユーザーで共有される state を読み書きします。親アプリを持つ context では app mutex 経由で同期されます。
app.Page("/", func(ctx *mb.Context) mf.Node {
    ctx.Local["trace_id"] = ctx.Query("trace_id") // リクエスト内だけの一時値
    ctx.SetGlobal("last_trace_id", ctx.Local["trace_id"]) // 全ユーザー共有のアプリ state
    return mf.Text(ctx.GetGlobal("last_trace_id").(string))
})

Param(name string) string

  • Request.PathValue(name) からパスパラメータを返します。
  • Requestnil の場合は "" を返します。

Query(name string) string

  • Request.URL.Query().Get(name) からクエリパラメータを返します。
  • Requestnil の場合は "" を返します。

FormValue(name string) string

  • Request.FormValue(name) からフォーム値を返します。
  • Requestnil の場合は "" を返します。

Asset(name string) string

  • 親アプリからアセット URL を組み立てます。
  • 画像、スタイルシート、リンクなどを登録済みアセットプレフィックスに追従させたい場合にハンドラ内で使います。

Local map[string]any

  • アプリが作成する context で初期化される、リクエストローカルな一時値用 map です。
  • ここに入れた値は他のリクエストと共有されず、app mutex の保護対象でもありません。

SetGlobal(key string, value any)

  • アプリ共有 state に書き込みます。
  • API 名に Global が付くため、この値はアプリの全ユーザー・全リクエストで共有されます。
  • app が作成した context では app mutex 経由で同期されます。
  • 親アプリがない場合、app-wide state map がないため no-op です。

GetGlobal(key string) any

  • アプリ共有 state を読み取ります。
  • API 名に Global が付くため、この値はアプリの全ユーザー・全リクエストで共有されます。
  • app が作成した context では app mutex 経由で同期されます。
  • 親アプリがない場合、app-wide state map がないため nil を返します。
  • 返ってきた slice、map、pointer は直接変更せず、変更は UpdateGlobal 内で行うか、GetGlobalSnapshot と clone 関数で snapshot を読んでください。

GetGlobalSnapshot(key string, clone func(any) any) any

  • app の read lock を保持したままアプリ共有 state を読み取り、clone(value) を返します。
  • render や後続処理に slice、map などの mutable value を渡す前の snapshot 作成に使います。
  • 親アプリがない場合、app-wide state map がないため clone(nil) を返します。

UpdateGlobal(key string, fn func(old any) any) any

  • アプリ共有 state を atomically に読み取り、変換し、書き戻します。
  • app が作成した context では、app mutex を取得したまま old := state[key]next := fn(old)state[key] = next を実行します。
  • 新しい値が古い値に依存する場合は、個別の GetGlobalSetGlobal ではなくこちらを使ってください。
  • 親アプリがない場合、app-wide state map がないため fn(nil) の結果を保存せずに返します。
app.Action("counter/increment", func(ctx *mb.Context) mf.Node {
    next := ctx.UpdateGlobal("count", func(old any) any {
        oldInt, _ := old.(int)
        return oldInt + 1
    }).(int)
    return mf.Text(strconv.Itoa(next))
})

カウンター、進捗 tick、append 形式の更新では、読み取りと書き込みの間に別リクエストが値を変更する可能性があるため、次の形は避けてください。

count := ctx.GetGlobalInt("count")
ctx.SetGlobal("count", count+1) // UpdateGlobal または IncrementGlobalInt を使う

GetGlobalInt(key string) int

  • GetGlobal + int アサーションです。
  • 値がない、または int でない場合は 0 を返します。

IncrementGlobalInt(key string, delta int) int

  • integer のカウンターや進捗値向けの UpdateGlobal 便利ラッパーです。
  • 値がない、または int でない場合は 0 として扱い、old + delta を保存して新しい int を返します。

Flash APIs

Flashes() []FlashMessage

  • 現在読み込まれている flash のコピーを返します。
  • flash がない場合は nil を返します。

FlashSuccess(message string) / FlashError(message string) / FlashInfo(message string) / FlashWarn(message string)

  • AddFlash(level, message) の便利ラッパーです。
  • level の値は実装定数です:
    • FlashSuccess, FlashError, FlashInfo, FlashWarn

AddFlash(level FlashLevel, message string)

  • メッセージを trim し、空なら no-op です。
  • context の flash リストに追加し、Cookie (marionette_flash) にシリアライズします。
  • Cookie の挙動:
    • Path=/
    • HttpOnly=true
    • SameSite=Lax
    • SecureApp.SetCookieSecure に従います(デフォルト false)。
  • シリアライズ失敗は無視されます(panic / status 変更なし)。

次リクエストでの lifecycle:

  • flash は Cookie から Context.flashes にデコードされます。
  • 既知の level かつ空でないメッセージだけが有効です。
  • flash が存在した場合、レスポンスで Cookie は自動的に削除されます。

Session APIs

SetSession(key, value string)

  • key を trim し、空なら no-op です。
  • context メモリ上の session エントリを保存/更新し、Cookie (marionette_session) に書き込みます。
  • Cookie の挙動:
    • Path=/
    • HttpOnly=true
    • SameSite=Lax
    • SecureApp.SetCookieSecure に従います(デフォルト false)。

Session(key string) string

  • key に対応する session 値を読み取ります。
  • key がない場合は空文字列を返します。

ClearSession()

  • session map を空の map に置き換え、Cookie (marionette_session) に書き込みます。

リクエスト時の lifecycle:

  • session は newContext で Cookie から Context.session にデコードされます。
  • デコード失敗時は空の session map にフォールバックします(panic / status 変更なし)。

Session sample:

app.Page("/session", func(ctx *mb.Context) mf.Node {
    user := ctx.Session("user")
    if user == "" {
        return mf.Form("/session/login", mf.Button("Sign in"))
    }
    return mf.Form("/session/logout", mf.Button("Sign out"))
})
app.Action("session/login", func(ctx *mb.Context) mf.Node {
    ctx.SetSession("user", "Aiko")
    return mf.Paragraph("Signed in")
})
app.Action("session/logout", func(ctx *mb.Context) mf.Node {
    ctx.ClearSession()
    return mf.Paragraph("Signed out")
})

Full example: docs/site-astro/public/examples/go/session.go.