diff --git a/lib/src/shelf_response.dart b/lib/src/shelf_response.dart index e69de29b..e671c236 100644 --- a/lib/src/shelf_response.dart +++ b/lib/src/shelf_response.dart @@ -0,0 +1,120 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'shelf_request.dart'; + +class ShelfResponseContext extends ResponseContext { + final Angel app; + final ShelfRequestContext correspondingRequest; + final StreamController> _ctrl = StreamController(); + bool _isOpen = true, _isDetached = false; + + ShelfResponseContext(this.app, this.correspondingRequest); + + shelf.Response get shelfResponse { + return shelf.Response(statusCode, body: _ctrl.stream, headers: headers); + } + + @override + Future close() { + _isOpen = false; + _ctrl.close(); + return super.close(); + } + + Iterable __allowedEncodings; + + Iterable get _allowedEncodings { + return __allowedEncodings ??= correspondingRequest.headers + .value('accept-encoding') + ?.split(',') + ?.map((s) => s.trim()) + ?.where((s) => s.isNotEmpty) + ?.map((str) { + // Ignore quality specifications in accept-encoding + // ex. gzip;q=0.8 + if (!str.contains(';')) return str; + return str.split(';')[0]; + }); + } + + @override + Future addStream(Stream> stream) { + if (!isOpen && isBuffered) throw ResponseContext.closed(); + Stream> output = stream; + + if (encoders.isNotEmpty && correspondingRequest != null) { + if (_allowedEncodings != null) { + for (var encodingName in _allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (encoders.containsKey(encodingName)) { + encoder = encoders[encodingName]; + } else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + output = encoders[key].bind(output); + break; + } + } + } + } + + return _ctrl.addStream(output); + } + + @override + void add(List data) { + if (!isOpen && isBuffered) { + throw ResponseContext.closed(); + } else if (_isOpen) { + if (encoders.isNotEmpty && correspondingRequest != null) { + if (_allowedEncodings != null) { + for (var encodingName in _allowedEncodings) { + Converter, List> encoder; + String key = encodingName; + + if (encoders.containsKey(encodingName)) { + encoder = encoders[encodingName]; + } else if (encodingName == '*') { + encoder = encoders[key = encoders.keys.first]; + } + + if (encoder != null) { + data = encoders[key].convert(data); + break; + } + } + } + } + + _ctrl.add(data); + } + } + + @override + BytesBuilder get buffer => null; + + @override + FutureOr detach() { + _isDetached = true; + return this; + } + + @override + bool get isBuffered => false; + + @override + bool get isOpen => _isOpen && !_isDetached; + + @override + void useBuffer() {} + + @override + ShelfResponseContext get rawResponse => this; +}