refactor(sessiontag): extract WithSessionTag/SessionTagFromContext to modules/sessiontag

This commit is contained in:
Oleks
2026-05-16 00:33:30 +03:00
parent 4676a3af93
commit ca06f6d545
3 changed files with 49 additions and 22 deletions
+38
View File
@@ -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 ""
}
+3 -3
View File
@@ -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)
+8 -19
View File
@@ -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 ------------------------------------------------------