perf: preemptively sign Blossom reads for known auth-gated hosts

The retry-on-401 interceptor re-probed anonymously on every blob, so each
image from an auth-gated host (e.g. a Buzz community feed, where nearly all
media is on one host) paid a wasted 401 round trip before the signed retry.

Remember hosts that answered 401 and attach the cached token up front on
their subsequent blobs — one round trip instead of two in steady state. The
first blob per host still costs the probe; the learned set falls back to an
anonymous request when no signer is available so a logged-out user never
loops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ao9w26c2gAm4gjJdhgvLyp
This commit is contained in:
Claude
2026-07-28 23:54:31 +00:00
parent 493aed8609
commit 7d1c45607b
2 changed files with 76 additions and 8 deletions
@@ -22,7 +22,9 @@ package com.vitorpamplona.amethyst.service.okhttp
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import java.util.concurrent.ConcurrentHashMap
/**
* Retries a Blossom blob download with a BUD-01 `t=get` authorization when the
@@ -51,10 +53,24 @@ import okhttp3.Response
* signer is available or signing times out, and is only consulted on a real
* `401`, so an unauthenticated user simply keeps seeing the broken image
* rather than paying any signing cost.
*
* The first blob from an auth-gated host costs an extra round trip (anonymous
* `GET` → `401` → signed retry), but that host is then remembered in
* [knownAuthHosts] so every later blob from it is signed **up front** — one
* round trip, not two. This matters on a Buzz community feed where nearly every
* image comes from the same gated host: without it each image would keep paying
* the wasted 401 probe. The learned host also short-circuits to anonymous when
* no signer is available, so a logged-out user never re-probes needlessly.
*/
class BlossomReadAuthInterceptor(
private val authHeaderProvider: (host: String, sha256: HexKey) -> String?,
) : Interceptor {
// Hosts observed to answer 401 to an anonymous Blossom GET. Small (a user
// follows a handful of auth-gated servers at most) and shared across all
// clients derived from the same factory. newKeySet() is thread-safe for the
// concurrent reads/writes of parallel feed downloads.
private val knownAuthHosts: MutableSet<String> = ConcurrentHashMap.newKeySet()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
@@ -65,24 +81,36 @@ class BlossomReadAuthInterceptor(
}
val sha256 = blossomHashOrNull(request.url.encodedPath) ?: return chain.proceed(request)
val host = request.url.host
// Known-gated host: skip the anonymous probe and sign the first attempt.
// Falls through to anonymous only when we can't produce a token (no
// signer / timeout) — the server would 401 either way.
if (host in knownAuthHosts) {
authHeaderProvider(host, sha256)?.let { header ->
return chain.proceed(request.withAuth(header))
}
}
val response = chain.proceed(request)
if (response.code != 401) return response
val header = authHeaderProvider(request.url.host, sha256) ?: return response
// Learn the host so its next blob is signed up front.
knownAuthHosts.add(host)
val header = authHeaderProvider(host, sha256) ?: return response
// Close the 401 body before replaying so the connection can be reused.
response.close()
val authed =
request
.newBuilder()
.header("Authorization", header)
.build()
return chain.proceed(authed)
return chain.proceed(request.withAuth(header))
}
private fun Request.withAuth(header: String) =
newBuilder()
.header("Authorization", header)
.build()
companion object {
private val SHA256_HEX = Regex("^[0-9a-f]{64}$")
@@ -163,6 +163,46 @@ class BlossomReadAuthInterceptorTest {
response.close()
}
@Test
fun learnsHostThenSignsSubsequentBlobsUpFront() {
val provider = RecordingProvider(header = "Nostr token")
val interceptor = BlossomReadAuthInterceptor(provider::header)
val otherSha = "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553"
// First blob learns the host via the 401 probe + signed retry.
val first = fakeChain("https://$host/media/$sha.png", codes = listOf(401, 200))
interceptor.intercept(first.asChain()).close()
assertEquals(2, first.requests.size)
// Second, different blob on the same host is signed on the first attempt —
// no anonymous probe, so a single request.
val second = fakeChain("https://$host/media/$otherSha.png", codes = listOf(200))
val response = interceptor.intercept(second.asChain())
assertEquals(200, response.code)
assertEquals("no anonymous probe on a known-gated host", 1, second.requests.size)
assertEquals("Nostr token", second.requests.single().header("Authorization"))
response.close()
}
@Test
fun learnedHostWithoutSignerDoesNotLoop() {
val provider = RecordingProvider(header = null)
val interceptor = BlossomReadAuthInterceptor(provider::header)
val first = fakeChain("https://$host/media/$sha.png", codes = listOf(401))
interceptor.intercept(first.asChain()).close()
// Host is now known, but with no signer the preemptive path must fall
// back to a single anonymous request rather than retrying endlessly.
val second = fakeChain("https://$host/media/$sha.png", codes = listOf(401))
val response = interceptor.intercept(second.asChain())
assertEquals(401, response.code)
assertEquals(1, second.requests.size)
response.close()
}
private class RecordingProvider(
private val header: String?,
) {