import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:angel_framework/angel_framework.dart'; import 'package:charcode/ascii.dart'; import 'wings_request.dart'; import 'wings_socket.dart'; class WingsResponseContext extends ResponseContext { @override final WingsRequestContext correspondingRequest; LockableBytesBuilder _buffer; @override final int rawResponse; bool _isDetached = false, _isClosed = false, _streamInitialized = false; WingsResponseContext(this.rawResponse, [this.correspondingRequest]); 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]; }); } bool _openStream() { if (!_streamInitialized) { // If this is the first stream added to this response, // then add headers, status code, etc. var outHeaders = {}; var statusLine = utf8.encode('HTTP/1.1 $statusCode').followedBy([$cr, $lf]); writeToNativeSocket(rawResponse, Uint8List.fromList(statusLine.toList())); headers.forEach((k, v) => outHeaders[k] = v); if (headers.containsKey('content-length')) { var l = int.tryParse(headers['content-length']); if (l != null) { outHeaders['content-length'] = l.toString(); } } if (contentType != null) outHeaders['content-type'] = contentType.toString(); 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) { outHeaders['content-encoding'] = key; break; } } } } void _wh(String k, String v) { var headerLine = utf8.encode('$k: ${Uri.encodeComponent(v)}').followedBy([$cr, $lf]); writeToNativeSocket( rawResponse, Uint8List.fromList(headerLine.toList())); } outHeaders.forEach(_wh); for (var c in cookies) { _wh('set-cookie', c.toString()); } writeToNativeSocket(rawResponse, Uint8List.fromList([$cr, $lf])); //_isClosed = true; return _streamInitialized = true; } return false; } @override Future addStream(Stream> stream) { if (_isClosed && isBuffered) throw ResponseContext.closed(); _openStream(); 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 output.forEach((buf) { writeToNativeSocket( rawResponse, buf is Uint8List ? buf : Uint8List.fromList(buf)); }); } @override void add(List data) { if (_isClosed && isBuffered) throw ResponseContext.closed(); else if (!isBuffered) { if (!_isClosed) { _openStream(); 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; } } } } writeToNativeSocket( rawResponse, data is Uint8List ? data : Uint8List.fromList(data)); } } else buffer.add(data); } @override Future close() { if (!_isDetached) { if (!_isClosed) { if (!isBuffered) { try { _openStream(); closeNativeSocketDescriptor(rawResponse); } catch (_) { // This only seems to occur on `MockHttpRequest`, but // this try/catch prevents a crash. } } else { _buffer.lock(); } _isClosed = true; } super.close(); } return new Future.value(); } @override BytesBuilder get buffer => _buffer; @override int detach() { _isDetached = true; return rawResponse; } @override bool get isBuffered => _buffer != null; @override bool get isOpen => !_isClosed && !_isDetached; @override void useBuffer() { _buffer = LockableBytesBuilder(); } }