豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 5e9e096

Browse files
authored
Merge pull request #987 from flippercloud/claude/youthful-hugle
Skip ETag header on cache_bust to prevent stale webhook syncs
2 parents 7f4daa5 + 603b770 commit 5e9e096

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

lib/flipper/adapters/http.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def get_all(cache_bust: false)
6565
path += "&_cb=#{Time.now.to_i}" if cache_bust
6666
etag = @get_all_mutex.synchronize { @last_get_all_etag }
6767

68-
if etag
68+
if etag && !cache_bust
6969
options[:headers] = { if_none_match: etag }
7070
end
7171

spec/flipper/adapters/http_spec.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,95 @@
278278
}.to raise_error(Flipper::Adapters::Http::Error)
279279
end
280280

281+
it "skips If-None-Match header when cache_bust is true" do
282+
features_response = {
283+
"features" => [
284+
{
285+
"key" => "search",
286+
"gates" => [
287+
{"key" => "boolean", "value" => true}
288+
]
289+
}
290+
]
291+
}
292+
293+
# First request - populate the ETag cache
294+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
295+
.to_return(
296+
status: 200,
297+
body: JSON.generate(features_response),
298+
headers: { 'ETag' => '"abc123"' }
299+
)
300+
301+
adapter = described_class.new(url: 'http://app.com/flipper')
302+
adapter.get_all
303+
304+
# Second request with cache_bust - should NOT send If-None-Match
305+
cache_bust_stub = stub_request(:get, %r{/flipper/features\?_cb=\d+&exclude_gate_names=true})
306+
.to_return(
307+
status: 200,
308+
body: JSON.generate(features_response),
309+
headers: { 'ETag' => '"def456"' }
310+
)
311+
312+
adapter.get_all(cache_bust: true)
313+
314+
expect(cache_bust_stub).to have_been_requested.once
315+
expect(
316+
a_request(:get, %r{/flipper/features\?_cb=\d+&exclude_gate_names=true})
317+
.with { |req| req.headers['If-None-Match'].nil? }
318+
).to have_been_made.once
319+
end
320+
321+
it "returns fresh data on cache_bust even when ETag is cached" do
322+
stale_response = {
323+
"features" => [
324+
{
325+
"key" => "search",
326+
"gates" => [
327+
{"key" => "boolean", "value" => nil}
328+
]
329+
}
330+
]
331+
}
332+
333+
fresh_response = {
334+
"features" => [
335+
{
336+
"key" => "search",
337+
"gates" => [
338+
{"key" => "boolean", "value" => true}
339+
]
340+
}
341+
]
342+
}
343+
344+
# First request - populate ETag cache with feature disabled
345+
stub_request(:get, "http://app.com/flipper/features?exclude_gate_names=true")
346+
.to_return(
347+
status: 200,
348+
body: JSON.generate(stale_response),
349+
headers: { 'ETag' => '"abc123"' }
350+
)
351+
352+
adapter = described_class.new(url: 'http://app.com/flipper')
353+
stale_result = adapter.get_all
354+
355+
expect(stale_result["search"][:boolean]).to be_nil
356+
357+
# Cache bust request returns fresh data (feature now enabled)
358+
stub_request(:get, %r{/flipper/features\?_cb=\d+&exclude_gate_names=true})
359+
.to_return(
360+
status: 200,
361+
body: JSON.generate(fresh_response),
362+
headers: { 'ETag' => '"def456"' }
363+
)
364+
365+
fresh_result = adapter.get_all(cache_bust: true)
366+
367+
expect(fresh_result["search"][:boolean]).to eq("true")
368+
end
369+
281370
it "does not send If-None-Match for other endpoints" do
282371
stub_request(:get, "http://app.com/flipper/features/search")
283372
.to_return(status: 404)

0 commit comments

Comments
 (0)