diff --git a/analysis_options.yaml b/analysis_options.yaml index 518eb901..eae1e42a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,2 +1,3 @@ analyzer: - strong-mode: true \ No newline at end of file + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/cache_service.dart b/example/cache_service.dart index 433ffb45..2ac17a12 100644 --- a/example/cache_service.dart +++ b/example/cache_service.dart @@ -2,20 +2,19 @@ import 'package:angel_cache/angel_cache.dart'; import 'package:angel_framework/angel_framework.dart'; main() async { - var app = new Angel()..lazyParseBodies = true; + var app = new Angel(); app.use( '/api/todos', new CacheService( - database: new AnonymousService( - index: ([params]) { - print('Fetched directly from the underlying service at ${new DateTime.now()}!'); - return ['foo', 'bar', 'baz']; - }, - read: (id, [params]) { - return {id: '$id at ${new DateTime.now()}'}; - } - ), + cache: new MapService(), + database: new AnonymousService(index: ([params]) { + print( + 'Fetched directly from the underlying service at ${new DateTime.now()}!'); + return ['foo', 'bar', 'baz']; + }, read: (id, [params]) { + return {id: '$id at ${new DateTime.now()}'}; + }), ), ); diff --git a/example/main.dart b/example/main.dart index bdf8c5a6..9b08105f 100644 --- a/example/main.dart +++ b/example/main.dart @@ -3,7 +3,7 @@ import 'package:angel_framework/angel_framework.dart'; import 'package:glob/glob.dart'; main() async { - var app = new Angel()..lazyParseBodies = true; + var app = new Angel(); // Cache a glob var cache = new ResponseCache() @@ -12,14 +12,14 @@ main() async { ]); // Handle `if-modified-since` header, and also send cached content - app.use(cache.handleRequest); + app.fallback(cache.handleRequest); // A simple handler that returns a different result every time. app.get('/date.txt', - (ResponseContext res) => res.write(new DateTime.now().toIso8601String())); + (req, res) => res.write(new DateTime.now().toIso8601String())); // Support purging the cache. - app.addRoute('PURGE', '*', (RequestContext req) { + app.addRoute('PURGE', '*', (req, res) { if (req.ip != '127.0.0.1') throw new AngelHttpException.forbidden(); cache.purge(req.uri.path); diff --git a/lib/angel_cache.dart b/lib/angel_cache.dart index a0c15fc6..3b5fd187 100644 --- a/lib/angel_cache.dart +++ b/lib/angel_cache.dart @@ -1,3 +1,3 @@ export 'src/cache.dart'; export 'src/cache_service.dart'; -export 'src/serializer.dart'; \ No newline at end of file +export 'src/serializer.dart'; diff --git a/lib/src/cache.dart b/lib/src/cache.dart index 268cf0ea..0fc21c64 100644 --- a/lib/src/cache.dart +++ b/lib/src/cache.dart @@ -1,7 +1,7 @@ import 'dart:async'; +import 'dart:io' show HttpDate; import 'package:angel_framework/angel_framework.dart'; import 'package:pool/pool.dart'; -import 'util.dart'; /// A flexible response cache for Angel. /// @@ -35,9 +35,8 @@ class ResponseCache { Future ifModifiedSince(RequestContext req, ResponseContext res) async { if (req.method != 'GET' && req.method != 'HEAD') return true; - if (req.headers.value('if-modified-since') != null) { - var modifiedSince = fmt - .parse(req.headers.value('if-modified-since').replaceAll('GMT', '')); + if (req.headers.ifModifiedSince != null) { + var modifiedSince = req.headers.ifModifiedSince; // Check if there is a cache entry. for (var pattern in patterns) { @@ -72,7 +71,7 @@ class ResponseCache { // Check if there is a cache entry. // // If `if-modified-since` is present, this check has already been performed. - if (req.headers.value('if-modified-since') == null) { + if (req.headers.ifModifiedSince == null) { for (var pattern in patterns) { if (pattern.allMatches(req.uri.path).isNotEmpty) { var now = new DateTime.now().toUtc(); @@ -88,9 +87,11 @@ class ResponseCache { _setCachedHeaders(response.timestamp, req, res); res ..headers.addAll(response.headers) - ..buffer.add(response.body) - ..end(); + ..add(response.body) + ..close(); return false; + } else { + _setCachedHeaders(now, req, res); } } } @@ -131,7 +132,7 @@ class ResponseCache { new Map.from(res.headers), res.buffer.toBytes(), now); }); - // _setCachedHeaders(now, req, res); + _setCachedHeaders(now, req, res); } } @@ -144,11 +145,11 @@ class ResponseCache { res.headers ..['cache-control'] = '$privacy, max-age=${timeout?.inSeconds ?? 86400}' - ..['last-modified'] = formatDateForHttp(modified); + ..['last-modified'] = HttpDate.format(modified); if (timeout != null) { var expiry = new DateTime.now().add(timeout); - res.headers['expires'] = formatDateForHttp(expiry); + res.headers['expires'] = HttpDate.format(expiry); } } } diff --git a/lib/src/cache_service.dart b/lib/src/cache_service.dart index 1729ae34..c67d8a8e 100644 --- a/lib/src/cache_service.dart +++ b/lib/src/cache_service.dart @@ -7,33 +7,36 @@ import 'package:meta/meta.dart'; /// /// This is useful for applications of scale, where network latency /// can have real implications on application performance. -class CacheService extends Service { +class CacheService extends Service { /// The underlying [Service] that represents the original data store. - final Service database; + final Service database; /// The [Service] used to interface with a caching layer. /// /// If not provided, this defaults to a [MapService]. - final Service cache; + final Service cache; final bool ignoreQuery; final Duration timeout; - final Map _cache = {}; - _CachedItem _indexed; + final Map> _cache = {}; + _CachedItem> _indexed; CacheService( {@required this.database, - Service cache, + @required this.cache, this.ignoreQuery: false, - this.timeout}) - : this.cache = cache ?? new MapService() { + this.timeout}) { assert(database != null); } - Future _getCached(Map params, _CachedItem get(), Future getFresh(), - Future getCached(), Future save(data, DateTime now)) async { + Future _getCached( + Map params, + _CachedItem get(), + FutureOr getFresh(), + FutureOr getCached(), + FutureOr save(T data, DateTime now)) async { var cached = get(); var now = new DateTime.now().toUtc(); @@ -47,8 +50,8 @@ class CacheService extends Service { var queryEqual = ignoreQuery == true || (params != null && cached.params != null && - const MapEquality() - .equals(params['query'], cached.params['query'])); + const MapEquality().equals( + params['query'] as Map, cached.params['query'] as Map)); if (queryEqual) { return await getCached(); } @@ -63,12 +66,12 @@ class CacheService extends Service { } @override - Future index([Map params]) { + Future> index([Map params]) { return _getCached( params, () => _indexed, () => database.index(params), - () => _indexed.data, + () => _indexed?.data ?? [], (data, now) async { _indexed = new _CachedItem(params, now, data); return data; @@ -77,8 +80,8 @@ class CacheService extends Service { } @override - Future read(id, [Map params]) async { - return _getCached( + Future read(Id id, [Map params]) async { + return _getCached( params, () => _cache[id], () => database.read(id, params), @@ -91,37 +94,37 @@ class CacheService extends Service { } @override - Future create(data, [Map params]) { + Future create(data, [Map params]) { _indexed = null; return database.create(data, params); } @override - Future modify(id, data, [Map params]) { + Future modify(Id id, Data data, [Map params]) { _indexed = null; _cache.remove(id); return database.modify(id, data, params); } @override - Future update(id, data, [Map params]) { + Future update(Id id, Data data, [Map params]) { _indexed = null; _cache.remove(id); return database.modify(id, data, params); } @override - Future remove(id, [Map params]) { + Future remove(Id id, [Map params]) { _indexed = null; _cache.remove(id); return database.remove(id, params); } } -class _CachedItem { +class _CachedItem { final params; final DateTime timestamp; - final data; + final Data data; _CachedItem(this.params, this.timestamp, [this.data]); diff --git a/lib/src/serializer.dart b/lib/src/serializer.dart index 925c1f73..3280eddf 100644 --- a/lib/src/serializer.dart +++ b/lib/src/serializer.dart @@ -7,7 +7,9 @@ import 'package:angel_framework/angel_framework.dart'; /// /// You can pass a [shouldCache] callback to determine which values should be cached. RequestHandler cacheSerializationResults( - {Duration timeout, FutureOr Function(RequestContext, ResponseContext, Object) shouldCache}) { + {Duration timeout, + FutureOr Function(RequestContext, ResponseContext, Object) + shouldCache}) { return (RequestContext req, ResponseContext res) async { var oldSerializer = res.serializer; var cache = {}; diff --git a/lib/src/util.dart b/lib/src/util.dart deleted file mode 100644 index 90a91172..00000000 --- a/lib/src/util.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:intl/intl.dart'; - -final DateFormat fmt = new DateFormat('EEE, d MMM yyyy HH:mm:ss'); - -/// Formats a date (converted to UTC), ex: `Sun, 03 May 2015 23:02:37 GMT`. -String formatDateForHttp(DateTime dt) => fmt.format(dt.toUtc()) + ' GMT'; \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 0f89dc07..0b4ec2bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,15 @@ name: angel_cache -version: 1.0.0 +version: 2.0.0 homepage: https://github.com/angel-dart/cache description: Support for server-side caching in Angel. author: Tobe O environment: - sdk: ">=1.19.0 <3.0.0" + sdk: ">=2.0.0-dev <3.0.0" dependencies: - angel_framework: ">=1.0.0-dev <2.0.0" - intl: ^0.15.0 + angel_framework: ^2.0.0-alpha meta: ^1.0.0 pool: ^1.0.0 dev_dependencies: - angel_test: ^1.1.0 + angel_test: ^2.0.0-alpha glob: ^1.0.0 - test: ^0.12.0 \ No newline at end of file + test: ^1.0.0 \ No newline at end of file diff --git a/test/cache_test.dart b/test/cache_test.dart index ed0cfdd0..011981f0 100644 --- a/test/cache_test.dart +++ b/test/cache_test.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'package:angel_cache/src/util.dart'; +import 'dart:io'; import 'package:angel_cache/angel_cache.dart'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_test/angel_test.dart'; @@ -20,14 +20,15 @@ main() async { new Glob('/*.txt'), ]); - app.use(cache.handleRequest); + app.fallback(cache.handleRequest); - app.get( - '/date.txt', - (ResponseContext res) => - res.write(new DateTime.now().toIso8601String())); + app.get('/date.txt', (req, res) { + res + ..useBuffer() + ..write(new DateTime.now().toIso8601String()); + }); - app.addRoute('PURGE', '*', (RequestContext req) { + app.addRoute('PURGE', '*', (req, res) { cache.purge(req.uri.path); print('Purged ${req.uri.path}'); }); @@ -43,7 +44,8 @@ main() async { client = await connectTo(app); response1 = await client.get('/date.txt'); response2 = await client.get('/date.txt'); - lastModified = fmt.parse(response2.headers['last-modified']); + print(response2.headers); + lastModified = HttpDate.parse(response2.headers['last-modified']); print('Response 1 status: ${response1.statusCode}'); print('Response 2 status: ${response2.statusCode}'); print('Response 1 body: ${response1.body}'); @@ -80,7 +82,10 @@ main() async { }); test('sends 304 on if-modified-since', () async { - var headers = {'if-modified-since': formatDateForHttp(lastModified.add(const Duration(days: 1)))}; + var headers = { + 'if-modified-since': + HttpDate.format(lastModified.add(const Duration(days: 1))) + }; var response = await client.get('/date.txt', headers: headers); print('Sending headers: $headers'); print('Response (${response.statusCode}): ${response.headers}'); @@ -90,7 +95,7 @@ main() async { test('last-modified in the past', () async { var response = await client.get('/date.txt', headers: { 'if-modified-since': - formatDateForHttp(lastModified.subtract(const Duration(days: 10))) + HttpDate.format(lastModified.subtract(const Duration(days: 10))) }); print('Response: ${response.body}'); expect(response.statusCode, 200);