Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ const (
// Name of capability to enable the "v3" API for the signaling endpoint.
FeatureSignalingV3Api = "signaling-v3"

// Cache capabilities for one minute if response does not contain a
// "Cache-Control" header.
defaultCapabilitiesCacheDuration = time.Minute
// minCapabilitiesCacheDuration specifies the minimum duration to cache
// capabilities.
// This could overwrite the "max-age" from a "Cache-Control" header.
minCapabilitiesCacheDuration = time.Minute

// Don't invalidate more than once per minute.
maxInvalidateInterval = time.Minute
Expand Down Expand Up @@ -112,9 +113,12 @@ func (e *capabilitiesEntry) update(response *http.Response, now time.Time) error
if nc, _ := cc.NoCache(); !nc {
maxAge = cc.MaxAge()
}
if maxAge < minCapabilitiesCacheDuration {
maxAge = minCapabilitiesCacheDuration
}
e.mustRevalidate = cc.MustRevalidate()
} else {
maxAge = defaultCapabilitiesCacheDuration
maxAge = minCapabilitiesCacheDuration
}
e.nextUpdate = now.Add(maxAge)

Expand Down
84 changes: 80 additions & 4 deletions capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ func NewCapabilitiesForTestWithCallback(t *testing.T, callback func(*Capabilitie
}
var cc []string
if !strings.Contains(t.Name(), "NoCache") {
cc = append(cc, "max-age=60")
if strings.Contains(t.Name(), "ShortCache") {
cc = append(cc, "max-age=1")
} else {
cc = append(cc, "max-age=60")
}
}
if strings.Contains(t.Name(), "MustRevalidate") && !strings.Contains(t.Name(), "NoMustRevalidate") {
cc = append(cc, "must-revalidate")
Expand Down Expand Up @@ -348,7 +352,75 @@ func TestCapabilitiesNoCache(t *testing.T) {
}

SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(defaultCapabilitiesCacheDuration)
return time.Now().Add(minCapabilitiesCacheDuration)
})

if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
t.Error("could not find value for \"foo\"")
} else if value != expectedString {
t.Errorf("expected value %s, got %s", expectedString, value)
} else if cached {
t.Errorf("expected direct response")
}

if value := called.Load(); value != 2 {
t.Errorf("expected called %d, got %d", 2, value)
}
}

func TestCapabilitiesShortCache(t *testing.T) {
t.Parallel()
CatchLogForTest(t)
var called atomic.Uint32
url, capabilities := NewCapabilitiesForTestWithCallback(t, func(cr *CapabilitiesResponse, w http.ResponseWriter) error {
called.Add(1)
return nil
})

ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()

expectedString := "bar"
if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
t.Error("could not find value for \"foo\"")
} else if value != expectedString {
t.Errorf("expected value %s, got %s", expectedString, value)
} else if cached {
t.Errorf("expected direct response")
}

if value := called.Load(); value != 1 {
t.Errorf("expected called %d, got %d", 1, value)
}

// Capabilities are cached for some time if no "Cache-Control" header is set.
if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
t.Error("could not find value for \"foo\"")
} else if value != expectedString {
t.Errorf("expected value %s, got %s", expectedString, value)
} else if !cached {
t.Errorf("expected cached response")
}

if value := called.Load(); value != 1 {
t.Errorf("expected called %d, got %d", 1, value)
}

// The capabilities are cached for a minumum duration.
SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(minCapabilitiesCacheDuration / 2)
})

if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
t.Error("could not find value for \"foo\"")
} else if value != expectedString {
t.Errorf("expected value %s, got %s", expectedString, value)
} else if !cached {
t.Errorf("expected cached response")
}

SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(minCapabilitiesCacheDuration)
})

if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
Expand Down Expand Up @@ -400,7 +472,7 @@ func TestCapabilitiesNoCacheETag(t *testing.T) {
}

SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(defaultCapabilitiesCacheDuration)
return time.Now().Add(minCapabilitiesCacheDuration)
})

if value, cached, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); !found {
Expand Down Expand Up @@ -492,7 +564,7 @@ func TestCapabilitiesNoCacheNoMustRevalidate(t *testing.T) {
}

SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(defaultCapabilitiesCacheDuration)
return time.Now().Add(minCapabilitiesCacheDuration)
})

// Expired capabilities can still be used even in case of update errors if
Expand Down Expand Up @@ -538,6 +610,10 @@ func TestCapabilitiesNoCacheMustRevalidate(t *testing.T) {
t.Errorf("expected called %d, got %d", 1, value)
}

SetCapabilitiesGetNow(t, capabilities, func() time.Time {
return time.Now().Add(minCapabilitiesCacheDuration)
})

// Capabilities will be cleared if "must-revalidate" is set and an error
// occurs while fetching the updated data.
if value, _, found := capabilities.GetStringConfig(ctx, url, "signaling", "foo"); found {
Expand Down