This commit is contained in:
Tobe O 2018-10-21 05:35:04 -04:00
parent 5295ea57eb
commit 466c784d03
10 changed files with 75 additions and 71 deletions

View file

@ -1,2 +1,3 @@
analyzer:
strong-mode: true
strong-mode:
implicit-casts: false

View file

@ -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()}'};
}),
),
);

View file

@ -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);

View file

@ -1,3 +1,3 @@
export 'src/cache.dart';
export 'src/cache_service.dart';
export 'src/serializer.dart';
export 'src/serializer.dart';

View file

@ -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<bool> 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);
}
}
}

View file

@ -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<Id, Data> extends Service<Id, Data> {
/// The underlying [Service] that represents the original data store.
final Service database;
final Service<Id, Data> database;
/// The [Service] used to interface with a caching layer.
///
/// If not provided, this defaults to a [MapService].
final Service cache;
final Service<Id, Data> cache;
final bool ignoreQuery;
final Duration timeout;
final Map<dynamic, _CachedItem> _cache = {};
_CachedItem _indexed;
final Map<Id, _CachedItem<Data>> _cache = {};
_CachedItem<List<Data>> _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<T> _getCached<T>(
Map<String, dynamic> params,
_CachedItem get(),
FutureOr<T> getFresh(),
FutureOr<T> getCached(),
FutureOr<T> 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<List<Data>> index([Map<String, dynamic> 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<Data> read(Id id, [Map<String, dynamic> params]) async {
return _getCached<Data>(
params,
() => _cache[id],
() => database.read(id, params),
@ -91,37 +94,37 @@ class CacheService extends Service {
}
@override
Future create(data, [Map params]) {
Future<Data> create(data, [Map<String, dynamic> params]) {
_indexed = null;
return database.create(data, params);
}
@override
Future modify(id, data, [Map params]) {
Future<Data> modify(Id id, Data data, [Map<String, dynamic> params]) {
_indexed = null;
_cache.remove(id);
return database.modify(id, data, params);
}
@override
Future update(id, data, [Map params]) {
Future<Data> update(Id id, Data data, [Map<String, dynamic> params]) {
_indexed = null;
_cache.remove(id);
return database.modify(id, data, params);
}
@override
Future remove(id, [Map params]) {
Future<Data> remove(Id id, [Map<String, dynamic> params]) {
_indexed = null;
_cache.remove(id);
return database.remove(id, params);
}
}
class _CachedItem {
class _CachedItem<Data> {
final params;
final DateTime timestamp;
final data;
final Data data;
_CachedItem(this.params, this.timestamp, [this.data]);

View file

@ -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<bool> Function(RequestContext, ResponseContext, Object) shouldCache}) {
{Duration timeout,
FutureOr<bool> Function(RequestContext, ResponseContext, Object)
shouldCache}) {
return (RequestContext req, ResponseContext res) async {
var oldSerializer = res.serializer;
var cache = <dynamic, String>{};

View file

@ -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';

View file

@ -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 <thosakwe@gmail.com>
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
test: ^1.0.0

View file

@ -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);