intercept method
Future<HttpResponse>
intercept(
- HttpRequest request,
- CancellationToken token,
- HttpRequestHandler next
override
Implementation
@override
Future<HttpResponse> intercept(HttpRequest request, CancellationToken token,
HttpRequestHandler next) async {
final key = request.uri.replace(fragment: '').toString();
if (request.method != HttpMethod.get) {
final response = await next(request, token);
if (response.isSuccessful &&
!{HttpMethod.head, HttpMethod.options}.contains(request.method)) {
await store.remove(key);
}
return response;
}
final policy = _directives(request.headers['cache-control']);
if (request.headers.contains('authorization') ||
request.headers.contains('cookie') ||
request.headers.contains('range') ||
policy.containsKey('no-store')) {
return next(request, token);
}
var cached = await store.read(key);
if (cached != null &&
cached.vary.entries.any((entry) =>
entry.value.join('\u0000') !=
request.headers[entry.key].join('\u0000'))) {
cached = null;
}
if (cached != null &&
_clock().isBefore(cached.expiresAt) &&
!policy.containsKey('no-cache') &&
policy['max-age'] != '0') {
return cached.response.toResponse();
}
var outgoing = request;
if (cached != null) {
final etag = cached.response.headers['etag'];
final modified = cached.response.headers['last-modified'];
if (etag.isNotEmpty) {
outgoing = request.copyWith(
headers: request.headers.withValue('if-none-match', etag.first));
} else if (modified.isNotEmpty) {
outgoing = request.copyWith(
headers:
request.headers.withValue('if-modified-since', modified.first));
}
}
var response = await next(outgoing, token);
if (response.statusCode == 304 && cached != null) {
await response.dispose();
response = HttpResponse.bytes(
statusCode: cached.response.statusCode,
bytes: cached.response.bytes,
headers: cached.response.headers.merge(response.headers),
uri: response.uri);
}
final control = _directives(response.headers['cache-control']);
final vary = response.headers['vary']
.join(',')
.split(',')
.map((name) => name.trim().toLowerCase())
.where((name) => name.isNotEmpty)
.toList();
if (response.statusCode != 200 ||
control.containsKey('no-store') ||
control.containsKey('private') ||
response.headers.contains('set-cookie') ||
vary.contains('*')) {
await store.remove(key);
return response;
}
final lengths = response.headers['content-length'];
final length = lengths.isEmpty ? null : int.tryParse(lengths.first);
// Unknown or large bodies remain streaming; caching must not force buffering.
if (length == null || length > maxEntryBytes || length < 0) return response;
final bytes =
await ResponseBodyReader(maxBytes: maxEntryBytes).readBytes(response);
final maxAge = int.tryParse(control['max-age'] ?? '') ?? 0;
final ages = response.headers['age'];
var age = ages.isEmpty ? 0 : int.tryParse(ages.first) ?? 0;
final dates = response.headers['date'];
final date =
dates.isEmpty ? null : const HttpDateCodec().parse(dates.first);
if (date != null && _clock().difference(date).inSeconds > age) {
age = _clock().difference(date).inSeconds;
}
final ttl = control.containsKey('no-cache')
? 0
: (maxAge - age).clamp(0, maxAge < 0 ? 0 : maxAge);
final buffered = BufferedHttpResponse(
statusCode: response.statusCode,
bytes: bytes,
headers: response.headers,
uri: response.uri);
await store.write(
key,
CacheEntry(
response: buffered,
expiresAt: _clock().add(Duration(seconds: ttl)),
vary: {for (final name in vary) name: request.headers[name]}));
return buffered.toResponse();
}