refactor(sessiontag): extract WithSessionTag/SessionTagFromContext to modules/sessiontag
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Package sessiontag carries a per-page-load identifier from the
|
||||||
|
// originating HTTP request down to the service- and model-layer SSE
|
||||||
|
// publishers. The publishers echo the tag back inside event payloads so
|
||||||
|
// the originating browser tab can suppress its own event after it has
|
||||||
|
// already applied the optimistic update locally.
|
||||||
|
//
|
||||||
|
// It is deliberately tiny and dependency-free so any feature that emits
|
||||||
|
// Server-Sent Events (project boards, milestones, ...) can share one
|
||||||
|
// context key without importing one another.
|
||||||
|
package sessiontag
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// sessionTagCtxKey is the context key under which the X-Session-Tag
|
||||||
|
// value from the originating HTTP request is stashed.
|
||||||
|
type sessionTagCtxKey struct{}
|
||||||
|
|
||||||
|
// WithSessionTag returns ctx decorated with the provided session tag.
|
||||||
|
// Web/API middleware reads the X-Session-Tag header and calls this so
|
||||||
|
// service- and model-layer publishers can pull the tag back out.
|
||||||
|
func WithSessionTag(ctx context.Context, tag string) context.Context {
|
||||||
|
if tag == "" {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
return context.WithValue(ctx, sessionTagCtxKey{}, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionTagFromContext returns the session tag previously stored via
|
||||||
|
// WithSessionTag, or "" when none was set.
|
||||||
|
func SessionTagFromContext(ctx context.Context) string {
|
||||||
|
if v, ok := ctx.Value(sessionTagCtxKey{}).(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ package common
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/services/project_events"
|
"code.gitea.io/gitea/modules/sessiontag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SessionTagHeader is the HTTP header browser tabs use to broadcast a
|
// SessionTagHeader is the HTTP header browser tabs use to broadcast a
|
||||||
@@ -17,7 +17,7 @@ const SessionTagHeader = "X-Session-Tag"
|
|||||||
|
|
||||||
// SessionTagMiddleware decorates each incoming request's context with
|
// SessionTagMiddleware decorates each incoming request's context with
|
||||||
// the X-Session-Tag header value when present. Service- and model-
|
// the X-Session-Tag header value when present. Service- and model-
|
||||||
// layer publishers read the value via project_events.SessionTagFromContext.
|
// layer publishers read the value via sessiontag.SessionTagFromContext.
|
||||||
//
|
//
|
||||||
// Empty / missing headers are a no-op.
|
// Empty / missing headers are a no-op.
|
||||||
func SessionTagMiddleware() func(http.Handler) http.Handler {
|
func SessionTagMiddleware() func(http.Handler) http.Handler {
|
||||||
@@ -25,7 +25,7 @@ func SessionTagMiddleware() func(http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
tag := r.Header.Get(SessionTagHeader)
|
tag := r.Header.Get(SessionTagHeader)
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
ctx := project_events.WithSessionTag(r.Context(), tag)
|
ctx := sessiontag.WithSessionTag(r.Context(), tag)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
}
|
}
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -25,31 +25,20 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/sessiontag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sessionTagCtxKey is the context key under which the X-Session-Tag value
|
// WithSessionTag re-exports modules/sessiontag.WithSessionTag so existing
|
||||||
// from the originating HTTP request is stashed. Publishers read it via
|
// callers of project_events keep working after the context-key helper was
|
||||||
// SessionTagFromContext to attach to outgoing events so the originating
|
// extracted into its own dependency-free package (shared with
|
||||||
// browser tab can suppress its own echo.
|
// milestone_events and any future SSE feature).
|
||||||
type sessionTagCtxKey struct{}
|
|
||||||
|
|
||||||
// WithSessionTag returns ctx decorated with the provided session tag.
|
|
||||||
// Web/API middleware reads the X-Session-Tag header and calls this so
|
|
||||||
// service- and model-layer publishers can pull the tag back out.
|
|
||||||
func WithSessionTag(ctx context.Context, tag string) context.Context {
|
func WithSessionTag(ctx context.Context, tag string) context.Context {
|
||||||
if tag == "" {
|
return sessiontag.WithSessionTag(ctx, tag)
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
return context.WithValue(ctx, sessionTagCtxKey{}, tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionTagFromContext returns the session tag previously stored via
|
// SessionTagFromContext re-exports modules/sessiontag.SessionTagFromContext.
|
||||||
// WithSessionTag, or "" when none was set.
|
|
||||||
func SessionTagFromContext(ctx context.Context) string {
|
func SessionTagFromContext(ctx context.Context) string {
|
||||||
if v, ok := ctx.Value(sessionTagCtxKey{}).(string); ok {
|
return sessiontag.SessionTagFromContext(ctx)
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event payload structs ------------------------------------------------------
|
// Event payload structs ------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user