2.0.0
This commit is contained in:
parent
5295ea57eb
commit
466c784d03
10 changed files with 75 additions and 71 deletions
|
@ -1,2 +1,3 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
||||
strong-mode:
|
||||
implicit-casts: false
|
|
@ -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()}!');
|
||||
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]) {
|
||||
}, read: (id, [params]) {
|
||||
return {id: '$id at ${new DateTime.now()}'};
|
||||
}
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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>{};
|
||||
|
|
|
@ -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';
|
11
pubspec.yaml
11
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 <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
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue