platform/lib/src/wings_response.dart

214 lines
5.6 KiB
Dart
Raw Normal View History

import 'dart:async';
2019-04-29 08:22:36 +00:00
import 'dart:convert';
import 'dart:io';
2019-04-29 08:22:36 +00:00
import 'dart:typed_data';
import 'package:angel_framework/angel_framework.dart';
2019-04-29 08:22:36 +00:00
import 'package:charcode/ascii.dart';
import 'wings_request.dart';
import 'wings_socket.dart';
class WingsResponseContext extends ResponseContext<int> {
@override
final WingsRequestContext correspondingRequest;
LockableBytesBuilder _buffer;
2018-07-03 23:16:29 +00:00
@override
2019-04-29 08:22:36 +00:00
final int rawResponse;
bool _isDetached = false, _isClosed = false, _streamInitialized = false;
WingsResponseContext(this.rawResponse, [this.correspondingRequest]);
Iterable<String> __allowedEncodings;
Iterable<String> 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 = <String, String>{};
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<int>, List<int>> 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;
2018-07-03 23:16:29 +00:00
}
@override
Future addStream(Stream<List<int>> stream) {
2019-04-29 08:22:36 +00:00
if (_isClosed && isBuffered) throw ResponseContext.closed();
_openStream();
Stream<List<int>> output = stream;
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> 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));
});
2018-07-03 23:16:29 +00:00
}
@override
2019-04-29 08:22:36 +00:00
void add(List<int> data) {
if (_isClosed && isBuffered)
throw ResponseContext.closed();
else if (!isBuffered) {
if (!_isClosed) {
_openStream();
2018-07-03 23:16:29 +00:00
2019-04-29 08:22:36 +00:00
if (encoders.isNotEmpty && correspondingRequest != null) {
if (_allowedEncodings != null) {
for (var encodingName in _allowedEncodings) {
Converter<List<int>, List<int>> 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);
}
2018-07-03 23:16:29 +00:00
@override
2019-04-29 08:22:36 +00:00
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();
2018-07-03 23:16:29 +00:00
}
@override
2019-04-29 08:22:36 +00:00
BytesBuilder get buffer => _buffer;
2018-07-03 23:16:29 +00:00
@override
2019-04-29 08:22:36 +00:00
int detach() {
_isDetached = true;
return rawResponse;
}
@override
bool get isBuffered => _buffer != null;
2018-07-03 23:16:29 +00:00
@override
2019-04-29 08:22:36 +00:00
bool get isOpen => !_isClosed && !_isDetached;
2018-07-03 23:16:29 +00:00
@override
void useBuffer() {
2019-04-29 08:22:36 +00:00
_buffer = LockableBytesBuilder();
2018-07-03 23:16:29 +00:00
}
2019-04-29 08:22:36 +00:00
}