1919import feign .FeignException ;
2020import feign .RequestTemplate ;
2121import feign .Response ;
22+ import feign .Util ;
2223import feign .interceptor .Invocation ;
2324import feign .interceptor .MethodInterceptor ;
2425import java .time .Instant ;
2526import java .util .Collection ;
2627import java .util .Map ;
2728import java .util .function .Function ;
29+ import java .util .regex .Pattern ;
2830
2931/**
3032 * A {@link MethodInterceptor} that adds conditional revalidation headers ({@code If-None-Match} /
4345@ Experimental
4446public final class HttpCacheInterceptor implements MethodInterceptor {
4547
48+ private static final Pattern NO_STORE = Pattern .compile ("(?i)\\ bno-store\\ b" );
49+
4650 private final HttpCacheStore store ;
4751 private final Function <Invocation , String > keyFn ;
4852 private final Function <RequestTemplate , Boolean > cacheable ;
@@ -78,27 +82,10 @@ public Object intercept(Invocation invocation, Chain chain) throws Throwable {
7882 }
7983 String key = keyFn .apply (invocation );
8084 CachedEntry hit = store .get (key );
81- if (hit != null ) {
82- if (hit .etag () != null ) {
83- template .header ("If-None-Match" , hit .etag ());
84- }
85- if (hit .lastModified () != null ) {
86- template .header ("If-Modified-Since" , hit .lastModified ());
87- }
88- }
85+ addConditionalHeaders (template , hit );
8986 try {
9087 Object result = chain .next (invocation );
91- Response response = invocation .response ();
92- if (response != null
93- && response .status () >= 200
94- && response .status () < 300
95- && !hasNoStore (response )) {
96- String etag = firstHeader (response .headers (), "ETag" );
97- String lastMod = firstHeader (response .headers (), "Last-Modified" );
98- if (etag != null || lastMod != null ) {
99- store .put (key , new CachedEntry (result , etag , lastMod , Instant .now ()));
100- }
101- }
88+ maybeStore (key , result , invocation .response ());
10289 return result ;
10390 } catch (FeignException e ) {
10491 if (e .status () == 304 && hit != null ) {
@@ -108,6 +95,38 @@ public Object intercept(Invocation invocation, Chain chain) throws Throwable {
10895 }
10996 }
11097
98+ private static void addConditionalHeaders (RequestTemplate template , CachedEntry hit ) {
99+ if (hit == null ) {
100+ return ;
101+ }
102+ if (hit .etag () != null ) {
103+ template .header ("If-None-Match" , hit .etag ());
104+ }
105+ if (hit .lastModified () != null ) {
106+ template .header ("If-Modified-Since" , hit .lastModified ());
107+ }
108+ }
109+
110+ private void maybeStore (String key , Object result , Response response ) {
111+ if (response == null ) {
112+ return ;
113+ }
114+ int status = response .status ();
115+ if (status < 200 || status >= 300 ) {
116+ return ;
117+ }
118+ Map <String , Collection <String >> headers = response .headers ();
119+ if (containsNoStore (headers )) {
120+ return ;
121+ }
122+ String etag = firstHeader (headers , "ETag" );
123+ String lastMod = firstHeader (headers , "Last-Modified" );
124+ if (etag == null && lastMod == null ) {
125+ return ;
126+ }
127+ store .put (key , new CachedEntry (result , etag , lastMod , Instant .now ()));
128+ }
129+
111130 private static String defaultKey (Invocation invocation ) {
112131 RequestTemplate template = invocation .requestTemplate ();
113132 return invocation .methodMetadata ().configKey () + "|" + template .method () + " " + template .url ();
@@ -118,13 +137,9 @@ private static Boolean defaultCacheable(RequestTemplate template) {
118137 return "GET" .equalsIgnoreCase (method ) || "HEAD" .equalsIgnoreCase (method );
119138 }
120139
121- private static boolean hasNoStore (Response response ) {
122- Collection <String > values = response .headers ().get ("Cache-Control" );
123- if (values == null ) {
124- return false ;
125- }
126- for (String value : values ) {
127- if (value != null && value .toLowerCase ().contains ("no-store" )) {
140+ private static boolean containsNoStore (Map <String , Collection <String >> headers ) {
141+ for (String value : Util .valuesOrEmpty (headers , "Cache-Control" )) {
142+ if (value != null && NO_STORE .matcher (value ).find ()) {
128143 return true ;
129144 }
130145 }
0 commit comments