Skip to content

Commit 83f549d

Browse files
committed
fix: 302 & 307 handling.
1 parent c5463a2 commit 83f549d

File tree

6 files changed

+137
-29
lines changed

6 files changed

+137
-29
lines changed

http_cache_core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 1.1.1
22
- fix: Send only one condition on request cache validation.
33
- fix: BaseRequest now has `headers` getter instead of `headerValuesAsList`.
4+
- fix: 302 & 307 codes handling.
45

56
## 1.1.0
67
- feat: Early skip strategy calculation when request has already cache check conditions.

http_cache_core/lib/src/model/cache/cache_strategy.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,13 @@ class CacheStrategyFactory {
143143
return false;
144144
}
145145

146-
if (!allowedStatusCodes.contains(statusCode)) {
147-
if (statusCode == 302 || statusCode == 307) {
148-
// 302 & 307 can only be cached with the right response headers.
149-
// https://datatracker.ietf.org/doc/html/rfc7234#section-3
150-
if (response.headers[expiresHeader] == null &&
151-
respCacheCtrl.maxAge == -1 &&
152-
respCacheCtrl.privacy != null) {
153-
return false;
154-
}
146+
if (statusCode == 302 || statusCode == 307) {
147+
// 302 & 307 can only be cached with the right response headers.
148+
// https://datatracker.ietf.org/doc/html/rfc7234#section-3
149+
if (response.headers[expiresHeader] == null &&
150+
respCacheCtrl.maxAge == -1 &&
151+
respCacheCtrl.privacy == null) {
152+
return false;
155153
}
156154
}
157155

http_cache_core/lib/src/store/cache_store.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ abstract class CacheStore {
3030
Future<void> set(CacheResponse response);
3131

3232
/// Removes the given key from store.
33+
///
3334
/// [staleOnly] flag will remove it only if the key is expired
3435
/// (from maxStale).
3536
Future<void> delete(String key, {bool staleOnly = false});
@@ -52,7 +53,9 @@ abstract class CacheStore {
5253
});
5354

5455
/// Removes all keys from store.
56+
///
5557
/// [priorityOrBelow] flag will remove keys only for the priority or below.
58+
///
5659
/// [staleOnly] flag will remove keys only if expired
5760
/// (from maxStale).
5861
///

http_cache_core/test/cache_strategy_test.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,79 @@ void main() {
274274
expect(strategy.request!.headers[ifModifiedSinceHeader], isNotNull);
275275
});
276276

277+
test('compute returns cached response when cache is valid on 302 or 307',
278+
() async {
279+
Future<CacheStrategy> computeStrategy(
280+
int statusCode,
281+
Map<String, List<String>>? headers,
282+
) {
283+
final request = MockRequest(
284+
url: Uri.parse('https://ok.org'),
285+
headers: {etagHeader: '1234'},
286+
);
287+
final response = MockResponse(statusCode: statusCode, headers: headers);
288+
289+
final factory = CacheStrategyFactory(
290+
request: request,
291+
cacheOptions: cacheOptions,
292+
response: response,
293+
);
294+
295+
return factory.compute(
296+
cacheResponseBuilder: () async =>
297+
cacheResponsefrom(cacheOptions, request, response),
298+
);
299+
}
300+
301+
Future<void> testStatusCode(int statusCode) async {
302+
// public
303+
var strategy = await computeStrategy(statusCode, {
304+
'cache-control': ['public'],
305+
etagHeader: ['1234'],
306+
});
307+
expect(strategy.cacheResponse, isNotNull);
308+
309+
// max-age
310+
strategy = await computeStrategy(statusCode, {
311+
'cache-control': ['max-age=3600'],
312+
etagHeader: ['1234'],
313+
});
314+
expect(strategy.cacheResponse, isNotNull);
315+
316+
// expires
317+
strategy = await computeStrategy(statusCode, {
318+
expiresHeader: [HttpDate.format(DateTime.now())],
319+
etagHeader: ['1234'],
320+
});
321+
expect(strategy.cacheResponse, isNotNull);
322+
323+
// public & max-age
324+
strategy = await computeStrategy(statusCode, {
325+
'cache-control': ['public, max-age=3600'],
326+
etagHeader: ['1234'],
327+
});
328+
expect(strategy.cacheResponse, isNotNull);
329+
330+
// public & max-age & expires
331+
strategy = await computeStrategy(statusCode, {
332+
'cache-control': ['public, max-age=3600'],
333+
expiresHeader: [HttpDate.format(DateTime.now())],
334+
etagHeader: ['1234'],
335+
});
336+
expect(strategy.cacheResponse, isNotNull);
337+
338+
// no headers (default values)
339+
strategy = await computeStrategy(statusCode, {
340+
etagHeader: ['1234']
341+
});
342+
expect(strategy.request, isNotNull);
343+
expect(strategy.cacheResponse, isNull);
344+
}
345+
346+
await testStatusCode(302);
347+
await testStatusCode(307);
348+
});
349+
277350
Future<BaseRequest> testWithPreconditionRequest(
278351
Map<String, String> headers, {
279352
String? eTag,

http_cache_file_store/test/file_store_test.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import 'dart:io';
22

3+
import 'package:http_cache_core/http_cache_core.dart';
34
import 'package:http_cache_file_store/http_cache_file_store.dart';
45
import 'package:http_cache_store_tester/common_store_testing.dart';
6+
import 'package:path/path.dart' as path;
57
import 'package:test/test.dart';
68

79
void main() {
810
late FileCacheStore store;
11+
final dirPath = '${Directory.current.path}/test/data/file_store';
912

1013
setUpAll(() {
11-
store = FileCacheStore('${Directory.current.path}/test/data/file_store');
14+
store = FileCacheStore(dirPath);
1215
});
1316

1417
setUp(() async {
@@ -29,6 +32,27 @@ void main() {
2932
test('pathExists', () => pathExists(store));
3033
test('deleteFromPath', () => deleteFromPath(store));
3134
test('getFromPath', () => getFromPath(store));
35+
36+
test(
37+
'Corrupted file',
38+
() async {
39+
await addFooResponse(store, key: 'corrupt');
40+
expect(await store.get('corrupt'), isNotNull);
41+
42+
// corrupt file
43+
final file =
44+
File(path.join(dirPath, CachePriority.normal.name, 'corrupt'));
45+
if (!file.existsSync()) {
46+
throw Exception('Unexpected missing file.');
47+
}
48+
49+
final bytes = file.readAsBytesSync();
50+
await file.writeAsBytes(bytes.sublist(0, bytes.length ~/ 2));
51+
52+
expect(await store.get('corrupt'), isNull);
53+
},
54+
);
55+
3256
test(
3357
'Concurrent access',
3458
() async => await concurrentAccess(store),

http_cache_store_tester/lib/common_store_testing.dart

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'dart:convert';
44
import 'package:http_cache_core/http_cache_core.dart';
55
import 'package:test/test.dart';
66

7-
Future<void> _addFooResponse(
7+
Future<void> addFooResponse(
88
CacheStore store, {
99
String key = 'foo',
1010
CacheControl? cacheControl,
@@ -41,8 +41,17 @@ Future<void> emptyByDefault(CacheStore store) async {
4141
}
4242

4343
Future<void> addItem(CacheStore store) async {
44-
await _addFooResponse(store);
45-
expect(await store.exists('foo'), isTrue);
44+
await addFooResponse(store, priority: CachePriority.low);
45+
var resp = await store.get('foo');
46+
expect(resp?.priority, CachePriority.low);
47+
48+
await addFooResponse(store, priority: CachePriority.normal);
49+
resp = await store.get('foo');
50+
expect(resp?.priority, CachePriority.normal);
51+
52+
await addFooResponse(store, priority: CachePriority.high);
53+
resp = await store.get('foo');
54+
expect(resp?.priority, CachePriority.high);
4655
}
4756

4857
Future<void> getItem(CacheStore store) async {
@@ -53,7 +62,7 @@ Future<void> getItem(CacheStore store) async {
5362
final expires = DateTime.now();
5463
final lastModified = HttpDate.format(DateTime.now());
5564

56-
await _addFooResponse(
65+
await addFooResponse(
5766
store,
5867
maxStale: DateTime.now().add(const Duration(days: 1)),
5968
headers: headers,
@@ -80,14 +89,14 @@ Future<void> getItem(CacheStore store) async {
8089
}
8190

8291
Future<void> deleteItem(CacheStore store) async {
83-
await _addFooResponse(store);
92+
await addFooResponse(store);
8493
expect(await store.exists('foo'), isTrue);
8594

8695
await store.delete('foo');
8796
expect(await store.exists('foo'), isFalse);
8897
await store.delete('foo'); // check for non exception
8998

90-
await _addFooResponse(
99+
await addFooResponse(
91100
store,
92101
maxStale: DateTime.now().add(const Duration(days: 1)),
93102
);
@@ -98,12 +107,12 @@ Future<void> deleteItem(CacheStore store) async {
98107
}
99108

100109
Future<void> clean(CacheStore store) async {
101-
await _addFooResponse(
110+
await addFooResponse(
102111
store,
103112
key: 'not-stale',
104113
maxStale: DateTime.now().add(const Duration(days: 1)),
105114
);
106-
await _addFooResponse(
115+
await addFooResponse(
107116
store,
108117
key: 'stale',
109118
maxStale: DateTime.now().subtract(const Duration(days: 1)),
@@ -127,7 +136,7 @@ Future<void> clean(CacheStore store) async {
127136

128137
Future<void> expires(CacheStore store) async {
129138
final now = DateTime.now();
130-
await _addFooResponse(store, expires: DateTime.now());
139+
await addFooResponse(store, expires: DateTime.now());
131140
final resp = await store.get('foo');
132141
expect(
133142
resp!.expires!.subtract(
@@ -146,7 +155,7 @@ Future<void> expires(CacheStore store) async {
146155
Future<void> lastModified(CacheStore store) async {
147156
final lastModified = 'Wed, 21 Oct 2015 07:28:00 GMT';
148157

149-
await _addFooResponse(store, lastModified: lastModified);
158+
await addFooResponse(store, lastModified: lastModified);
150159
final resp = await store.get('foo');
151160
expect(resp!.lastModified, equals(lastModified));
152161
}
@@ -159,7 +168,7 @@ Future<void> concurrentAccess(CacheStore store) async {
159168

160169
for (var i = 1; i <= max; i++) {
161170
final key = i % 3 == 0 ? 'bar' : 'foo';
162-
_addFooResponse(store, key: key, lastModified: lastModified).then(
171+
addFooResponse(store, key: key, lastModified: lastModified).then(
163172
(value) {
164173
store.get(key).then(
165174
(resp) {
@@ -265,28 +274,28 @@ void pathExists(CacheStore store) {
265274
}
266275

267276
Future<void> deleteFromPath(CacheStore store) async {
268-
await _addFooResponse(store);
277+
await addFooResponse(store);
269278
expect(await store.exists('foo'), isTrue);
270279
await store.deleteFromPath(RegExp('https://foo.com'));
271280
expect(await store.exists('foo'), isFalse);
272281

273-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
282+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
274283
expect(await store.exists('foo'), isTrue);
275284
await store.deleteFromPath(
276285
RegExp('https://foo.com'),
277286
queryParams: {'bar': null},
278287
);
279288
expect(await store.exists('foo'), isFalse);
280289

281-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
290+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
282291
expect(await store.exists('foo'), isTrue);
283292
await store.deleteFromPath(
284293
RegExp('https://foo.com'),
285294
queryParams: {'bar': 'bar'},
286295
);
287296
expect(await store.exists('foo'), isFalse);
288297

289-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
298+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
290299
expect(await store.exists('foo'), isTrue);
291300
await store.deleteFromPath(
292301
RegExp('https://foo.com'),
@@ -296,25 +305,25 @@ Future<void> deleteFromPath(CacheStore store) async {
296305
}
297306

298307
Future<void> getFromPath(CacheStore store) async {
299-
await _addFooResponse(store);
308+
await addFooResponse(store);
300309
var list = await store.getFromPath(RegExp('https://foo.com'));
301310
expect(list.length, 1);
302311

303-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
312+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
304313
list = await store.getFromPath(
305314
RegExp('https://foo.com'),
306315
queryParams: {'bar': null},
307316
);
308317
expect(list.length, 1);
309318

310-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
319+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
311320
list = await store.getFromPath(
312321
RegExp('https://foo.com'),
313322
queryParams: {'bar': 'bar'},
314323
);
315324
expect(list.length, 1);
316325

317-
await _addFooResponse(store, url: 'https://foo.com?bar=bar');
326+
await addFooResponse(store, url: 'https://foo.com?bar=bar');
318327
list = await store.getFromPath(
319328
RegExp('https://foo.com'),
320329
queryParams: {'bar': 'foobar'},

0 commit comments

Comments
 (0)