Six defects on byte-serving paths, all of which made the PR's monitoring and
kill-switch claims narrower than documented.
#2/GAP-10 -- the ABS in-flight kill switch was a production no-op. accessLog
wraps every ABS route, and its statusRecorder implemented Write, WriteHeader,
Hijack and Flush but not Unwrap, so http.NewResponseController dead-ended and
SetWriteDeadline returned ErrNotSupported. A multi-GB audiobook pour survived a
RevokeUser. The existing test passed throughout because it called handlers
directly and never saw the middleware; the new test drives the mounted router
over a real socket, and both new assertions fail if Unwrap is removed again.
GAP-11 -- ebook, comic and PDF serving was invisible and un-killable: no meter,
no transfer record, no Refuse, no watcher, on a route that serves cbz/cbr/pdf
files routinely 100 MB-1 GB+. It now follows the ABS file-handler idiom. Note
guardRevocationCut is deliberately *not* reused here: it keys on a session_id
URL param and an ?st= token this route does not carry, so it would have
compiled and silently guarded nothing. Cap-exempt per decision A4 -- admission
is untouched and neither route consumes a stream slot.
#10 -- the no-proxy remote transcode hop forwarded segments through a bare
RollingDeadlineWriter, so bytes on the API hop went unaccounted for a supported
topology. Metering is scoped to media bodies; manifests are excluded so a
rewritten playlist is not counted as media, and a mid-copy failure is no longer
silently discarded.
#16 -- native, proxy and compat subtitle pours were entry-gated only. They now
carry a transport span, a meter and an in-flight watcher. Proxy subtitle bytes
are attributed only when a tracker record already exists: taking Track/Remove
lifecycle ownership per subtitle request would walk straight into the
overlapping-request defect (#1) that Batch 2 addresses. Compat subtitle
extraction is buffered and rejects bitmap formats, so a cut stops delivery but
not extraction already in progress.
M2 -- the proxy deferred tracker Remove with the request context, which is
already canceled on client disconnect, so the Redis DEL never happened and the
key lingered until TTL -- a false over-cap window that could get a legitimate
stream killed. Cleanup now uses a short bounded context.
GAP-13 -- mergeStreams took the freshest record wholesale and never merged
BytesServed, so a stream that poured 8 GiB at an edge could report 0. Merged as
a max, not a sum: the records are two observers of one pour. Fixed in
DedupeSessionInfos too, which had the same hole and feeds the admin view.
#11 needed no behaviour change -- that route was already metered, registered and
watched, and commit c24d8396 plus this Unwrap fix are what make its cut work.
Its comment claimed download-class exemption while the comment above it said the
?token= form is for iOS streaming; both facts and decision A4 are now stated.
Part of #305.
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/config"
|
|
"github.com/Silo-Server/silo-server/internal/httpstream"
|
|
"github.com/Silo-Server/silo-server/internal/nodeconfig"
|
|
"github.com/Silo-Server/silo-server/internal/nodesessions"
|
|
"github.com/Silo-Server/silo-server/internal/streamtoken"
|
|
)
|
|
|
|
type latchCapturingRevocationStore struct {
|
|
latch *httpstream.CutLatch
|
|
}
|
|
|
|
func (s *latchCapturingRevocationStore) IsRevoked(string, int, time.Time) bool {
|
|
return false
|
|
}
|
|
|
|
func (s *latchCapturingRevocationStore) Refuse(http.ResponseWriter, string, int, time.Time) bool {
|
|
return false
|
|
}
|
|
|
|
func (s *latchCapturingRevocationStore) WatchAndCutContext(ctx context.Context, _ http.ResponseWriter, _ string, _ int, _ time.Time) func() {
|
|
s.latch = httpstream.CutLatchFrom(ctx)
|
|
return func() {}
|
|
}
|
|
|
|
func TestHandleRemuxCarriesCutLatchToRevocationWatcher(t *testing.T) {
|
|
const secret = "proxy-revocation-test-secret"
|
|
watcher := nodeconfig.NewWatcher(nil, nil, nil, nodeconfig.BootstrapOverrides{})
|
|
cfg := &config.Config{}
|
|
cfg.Auth.JWTSecret = secret
|
|
watcher.SetConfigForTest(cfg)
|
|
|
|
server := NewServer(watcher, nodesessions.NewTracker(nil, "http://proxy", "proxy", "proxy"))
|
|
revocation := &latchCapturingRevocationStore{}
|
|
server.SetRevocationStore(revocation)
|
|
|
|
token, err := streamtoken.Sign(streamtoken.Claims{
|
|
SessionID: "remux-latch",
|
|
MediaPath: t.TempDir() + "/missing.mkv",
|
|
}, secret, time.Minute)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/stream/remux/"+token, nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
server.Handler().ServeHTTP(rr, req)
|
|
|
|
if revocation.latch == nil {
|
|
t.Fatal("remux revocation watcher request context has no cut latch")
|
|
}
|
|
}
|