Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
36dbbf0f
authored
Sep 03, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: keep ETag valid across different JSON packages
parent
9df450fe
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
81 additions
and
84 deletions
+81
-84
common/etag.go
+50
-0
controller/misc.go
+5
-25
controller/revalidated_response.go
+26
-59
No files found.
common/etag.go
0 → 100644
View file @
36dbbf0f
package
common
import
(
"crypto/sha256"
"encoding/hex"
"reflect"
"strings"
)
type
digestAnchor
struct
{}
func
modulePath
()
string
{
return
reflect
.
TypeOf
(
digestAnchor
{})
.
PkgPath
()
}
var
digestSeed
=
func
()
(
s
[
sha256
.
Size
]
byte
)
{
return
sha256
.
Sum256
([]
byte
(
modulePath
()))
}()
// ETagFor returns a weak ETag derived from the namespace and content.
func
ETagFor
(
namespace
,
content
string
)
string
{
buf
:=
make
([]
byte
,
0
,
sha256
.
Size
+
1
+
len
(
namespace
)
+
1
+
len
(
content
))
buf
=
append
(
buf
,
digestSeed
[
:
]
...
)
buf
=
append
(
buf
,
0
)
buf
=
append
(
buf
,
namespace
...
)
buf
=
append
(
buf
,
0
)
buf
=
append
(
buf
,
content
...
)
digest
:=
sha256
.
Sum256
(
buf
)
return
`W/"`
+
hex
.
EncodeToString
(
digest
[
:
])
+
`"`
}
// ETagMatches reports whether an If-None-Match header matches etag under weak
// comparison (RFC 9110 §13.1.2): the W/ prefix is ignored on both sides, and
// "*" matches everything.
func
ETagMatches
(
ifNoneMatch
,
etag
string
)
bool
{
ifNoneMatch
=
strings
.
TrimSpace
(
ifNoneMatch
)
if
ifNoneMatch
==
""
{
return
false
}
if
ifNoneMatch
==
"*"
{
return
true
}
etag
=
strings
.
TrimPrefix
(
etag
,
"W/"
)
for
candidate
:=
range
strings
.
SplitSeq
(
ifNoneMatch
,
","
)
{
if
strings
.
TrimPrefix
(
strings
.
TrimSpace
(
candidate
),
"W/"
)
==
etag
{
return
true
}
}
return
false
}
controller/misc.go
View file @
36dbbf0f
...
...
@@ -178,38 +178,22 @@ func GetNotice(c *gin.Context) {
common
.
OptionMapRWMutex
.
RLock
()
notice
:=
common
.
OptionMap
[
"Notice"
]
common
.
OptionMapRWMutex
.
RUnlock
()
serveRevalidatedJSON
(
c
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
notice
,
})
serveRevalidatedJSON
(
c
,
notice
)
}
func
GetAbout
(
c
*
gin
.
Context
)
{
common
.
OptionMapRWMutex
.
RLock
()
about
:=
common
.
OptionMap
[
"About"
]
common
.
OptionMapRWMutex
.
RUnlock
()
serveRevalidatedJSON
(
c
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
about
,
})
serveRevalidatedJSON
(
c
,
about
)
}
func
GetUserAgreement
(
c
*
gin
.
Context
)
{
serveRevalidatedJSON
(
c
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
system_setting
.
GetLegalSettings
()
.
UserAgreement
,
})
serveRevalidatedJSON
(
c
,
system_setting
.
GetLegalSettings
()
.
UserAgreement
)
}
func
GetPrivacyPolicy
(
c
*
gin
.
Context
)
{
serveRevalidatedJSON
(
c
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
system_setting
.
GetLegalSettings
()
.
PrivacyPolicy
,
})
serveRevalidatedJSON
(
c
,
system_setting
.
GetLegalSettings
()
.
PrivacyPolicy
)
}
func
GetMidjourney
(
c
*
gin
.
Context
)
{
...
...
@@ -227,11 +211,7 @@ func GetHomePageContent(c *gin.Context) {
common
.
OptionMapRWMutex
.
RLock
()
homePageContent
:=
common
.
OptionMap
[
"HomePageContent"
]
common
.
OptionMapRWMutex
.
RUnlock
()
serveRevalidatedJSON
(
c
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
homePageContent
,
})
serveRevalidatedJSON
(
c
,
homePageContent
)
}
func
SendEmailVerification
(
c
*
gin
.
Context
)
{
...
...
controller/revalidated_response.go
View file @
36dbbf0f
package
controller
import
(
"crypto/sha256"
"encoding/hex"
"net/http"
"strings"
"github.com/QuantumNous/new-api/common"
"github.com/gin-gonic/gin"
)
// serveRevalidatedJSON writes payload as JSON with a weak content-derived ETag
// and answers conditional requests with 304 Not Modified.
//
// Intended for small, public, admin-editable payloads (notice, home page
// content) that every anonymous visitor fetches on page load. The goal is to
// make those fetches cheap without ever serving stale content:
//
// - The ETag is a hash of the response body, so it is identical across
// replicas. Deriving it from a timestamp would not be, and the Option table
// has no updated_at column to derive one from anyway.
// - The validator is weak (W/ prefixed) because /api is gzip-compressed by
// middleware that runs after this handler returns. The hash is computed over
// the uncompressed body, so the compressed and identity forms of one payload
// share a validator, and a strong ETag asserts byte-for-byte equality that
// does not hold across encodings (RFC 9110 §8.8.1). Weakening it costs
// nothing here: conditional GET compares weakly anyway, and these payloads
// are a few hundred bytes of JSON that no client Range-requests.
// - Cache-Control is "no-cache", which means "may be stored, but must be
// revalidated before reuse" (RFC 9111 §5.2.2.4). Browsers and CDNs both
// revalidate on every request, so an admin edit takes effect immediately.
// max-age/s-maxage are deliberately not set: upstream cannot assume how
// long any given deployment tolerates a stale notice.
// - Vary: Accept-Encoding is still required. Weakening the validator makes
// revalidation correct, but it does not separate the two encodings in a
// shared cache. Without Vary, a cache holding the gzip copy would hand those
// bytes to a client that never sent Accept-Encoding: gzip.
func
serveRevalidatedJSON
(
c
*
gin
.
Context
,
payload
any
)
{
body
,
err
:=
common
.
Marshal
(
payload
)
// etagVersionPublicContent namespaces the public-content ETag; bump it when
// the JSON envelope served by serveRevalidatedJSON changes shape.
const
etagVersionPublicContent
=
"public-content:v1"
type
publicContentResponse
struct
{
Success
bool
`json:"success"`
Message
string
`json:"message"`
Data
string
`json:"data"`
}
// serveRevalidatedJSON writes public content as JSON with a weak
// content-derived ETag and answers conditional requests with 304 Not
// Modified. The ETag is a weak validator derived from the content, so it is
// stable across replicas and JSON encodings, and a new one is issued when
// the content changes. Cache-Control: no-cache forces revalidation before
// reuse, so an admin edit takes effect on the next request; Vary:
// Accept-Encoding keeps the gzip and identity encodings apart in shared
// caches.
func
serveRevalidatedJSON
(
c
*
gin
.
Context
,
content
string
)
{
body
,
err
:=
common
.
Marshal
(
publicContentResponse
{
Success
:
true
,
Message
:
""
,
Data
:
content
,
})
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"success"
:
false
,
...
...
@@ -46,42 +39,16 @@ func serveRevalidatedJSON(c *gin.Context, payload any) {
return
}
digest
:=
sha256
.
Sum256
(
body
)
etag
:=
`W/"`
+
hex
.
EncodeToString
(
digest
[
:
])
+
`"`
etag
:=
common
.
ETagFor
(
etagVersionPublicContent
,
content
)
c
.
Header
(
"ETag"
,
etag
)
c
.
Header
(
"Cache-Control"
,
"no-cache"
)
c
.
Header
(
"Vary"
,
"Accept-Encoding"
)
if
et
agMatches
(
c
.
GetHeader
(
"If-None-Match"
),
etag
)
{
if
common
.
ET
agMatches
(
c
.
GetHeader
(
"If-None-Match"
),
etag
)
{
c
.
Status
(
http
.
StatusNotModified
)
return
}
c
.
Data
(
http
.
StatusOK
,
"application/json; charset=utf-8"
,
body
)
}
// etagMatches reports whether an If-None-Match header field matches etag,
// using the weak comparison required for conditional GET (RFC 9110 §13.1.2).
// The field is a comma-separated list of entity-tags or the wildcard "*".
//
// Weak comparison ignores the W/ prefix on both operands, so it must be
// stripped from the served etag as well as from each candidate. Stripping only
// the candidate would make a weak served validator match nothing, silently
// disabling 304 responses.
func
etagMatches
(
ifNoneMatch
string
,
etag
string
)
bool
{
ifNoneMatch
=
strings
.
TrimSpace
(
ifNoneMatch
)
if
ifNoneMatch
==
""
{
return
false
}
if
ifNoneMatch
==
"*"
{
return
true
}
etag
=
strings
.
TrimPrefix
(
etag
,
"W/"
)
for
_
,
candidate
:=
range
strings
.
Split
(
ifNoneMatch
,
","
)
{
if
strings
.
TrimPrefix
(
strings
.
TrimSpace
(
candidate
),
"W/"
)
==
etag
{
return
true
}
}
return
false
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment