2016-09-15 19:53:01 +00:00
|
|
|
library angel_framework.http.response_context;
|
|
|
|
|
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
2016-10-22 20:41:36 +00:00
|
|
|
import 'package:angel_route/angel_route.dart';
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'package:json_god/json_god.dart' as god;
|
|
|
|
import 'package:mime/mime.dart';
|
2017-11-28 18:14:50 +00:00
|
|
|
import 'package:pool/pool.dart';
|
2018-02-07 05:44:21 +00:00
|
|
|
import '../http/http.dart';
|
2017-03-02 04:04:37 +00:00
|
|
|
import 'server.dart' show Angel;
|
2017-08-15 23:01:16 +00:00
|
|
|
import 'request_context.dart';
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-12-21 03:10:03 +00:00
|
|
|
final RegExp _contentType =
|
2017-06-19 01:53:51 +00:00
|
|
|
new RegExp(r'([^/\n]+)\/\s*([^;\n]+)\s*(;\s*charset=([^$;\n]+))?');
|
2016-12-21 03:10:03 +00:00
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
/// Serializes response data into a String.
|
2018-02-07 03:54:24 +00:00
|
|
|
///
|
|
|
|
/// Prefer the String Function(dynamic) syntax.
|
|
|
|
@deprecated
|
2016-12-31 01:46:41 +00:00
|
|
|
typedef String ResponseSerializer(data);
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// A convenience wrapper around an outgoing HTTP request.
|
2018-02-07 05:21:14 +00:00
|
|
|
abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
|
2017-08-03 16:40:21 +00:00
|
|
|
final Map properties = {};
|
2017-08-15 23:01:16 +00:00
|
|
|
final BytesBuilder _buffer = new _LockableBytesBuilder();
|
2018-02-07 05:22:15 +00:00
|
|
|
final Map<String, String> _headers = {'server': 'angel'};
|
2017-08-15 23:01:16 +00:00
|
|
|
|
|
|
|
Completer _done;
|
2017-05-27 12:39:45 +00:00
|
|
|
int _statusCode = 200;
|
2016-10-22 20:41:36 +00:00
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// The [Angel] instance that is sending a response.
|
2017-03-02 04:04:37 +00:00
|
|
|
Angel app;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-12-19 04:32:36 +00:00
|
|
|
/// Is `Transfer-Encoding` chunked?
|
|
|
|
bool chunked;
|
|
|
|
|
2016-12-19 01:38:23 +00:00
|
|
|
/// Any and all cookies to be sent to the user.
|
|
|
|
final List<Cookie> cookies = [];
|
|
|
|
|
2017-08-15 23:01:16 +00:00
|
|
|
/// A set of [Converter] objects that can be used to encode response data.
|
|
|
|
///
|
|
|
|
/// At most one encoder will ever be used to convert data.
|
|
|
|
final Map<String, Converter<List<int>, List<int>>> encoders = {};
|
|
|
|
|
|
|
|
/// Points to the [RequestContext] corresponding to this response.
|
2018-02-07 05:21:14 +00:00
|
|
|
RequestContext get correspondingRequest;
|
2017-08-15 23:01:16 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future get done => (_done ?? new Completer()).future;
|
|
|
|
|
2016-12-19 01:38:23 +00:00
|
|
|
/// Headers that will be sent to the user.
|
2017-03-28 23:29:22 +00:00
|
|
|
Map<String, String> get headers {
|
2017-04-03 19:43:27 +00:00
|
|
|
/// If the response is closed, then this getter will return an immutable `Map`.
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen)
|
2017-03-28 23:29:22 +00:00
|
|
|
return new Map<String, String>.unmodifiable(_headers);
|
2017-04-25 02:32:16 +00:00
|
|
|
else
|
|
|
|
return _headers;
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
2016-12-19 01:38:23 +00:00
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
/// Serializes response data into a String.
|
|
|
|
///
|
2017-03-28 23:29:22 +00:00
|
|
|
/// The default is conversion into JSON via `package:json_god`.
|
|
|
|
///
|
|
|
|
/// If you are 100% sure that your response handlers will only
|
|
|
|
/// be JSON-encodable objects (i.e. primitives, `List`s and `Map`s),
|
|
|
|
/// then consider setting [serializer] to `JSON.encode`.
|
|
|
|
///
|
|
|
|
/// To set it globally for the whole [app], use the following helper:
|
|
|
|
/// ```dart
|
|
|
|
/// app.injectSerializer(JSON.encode);
|
|
|
|
/// ```
|
2018-02-07 03:54:24 +00:00
|
|
|
String Function(dynamic) serializer = god.serialize;
|
2016-12-31 01:46:41 +00:00
|
|
|
|
2016-12-19 01:38:23 +00:00
|
|
|
/// This response's status code.
|
2017-05-27 12:39:45 +00:00
|
|
|
int get statusCode => _statusCode;
|
|
|
|
|
|
|
|
void set statusCode(int value) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen)
|
|
|
|
throw closed();
|
2017-05-27 12:39:45 +00:00
|
|
|
else
|
|
|
|
_statusCode = value ?? 200;
|
|
|
|
}
|
2016-12-19 01:38:23 +00:00
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Can we still write to this response?
|
2018-02-07 05:36:24 +00:00
|
|
|
bool get isOpen;
|
2018-02-07 05:21:14 +00:00
|
|
|
|
|
|
|
/// Returns `true` if a [Stream] is being written directly.
|
|
|
|
bool get streaming;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
/// A set of UTF-8 encoded bytes that will be written to the response.
|
2017-03-28 23:29:22 +00:00
|
|
|
BytesBuilder get buffer => _buffer;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
/// The underlying [HttpResponse] under this instance.
|
2018-02-07 05:21:14 +00:00
|
|
|
HttpResponse get io;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-12-21 03:10:03 +00:00
|
|
|
/// Gets the Content-Type header.
|
|
|
|
ContentType get contentType {
|
2018-02-07 05:22:15 +00:00
|
|
|
if (!headers.containsKey('content-type')) return null;
|
2016-12-21 03:10:03 +00:00
|
|
|
|
2018-02-07 05:22:15 +00:00
|
|
|
var header = headers['content-type'];
|
2016-12-21 03:10:03 +00:00
|
|
|
var match = _contentType.firstMatch(header);
|
|
|
|
|
|
|
|
if (match == null)
|
|
|
|
throw new Exception('Malformed Content-Type response header: "$header".');
|
|
|
|
|
|
|
|
if (match[4]?.isNotEmpty != true)
|
|
|
|
return new ContentType(match[1], match[2]);
|
|
|
|
else
|
|
|
|
return new ContentType(match[1], match[2], charset: match[4]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the Content-Type header.
|
|
|
|
void set contentType(ContentType contentType) {
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = contentType.toString();
|
2016-12-21 03:10:03 +00:00
|
|
|
}
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Set this to true if you will manually close the response.
|
2016-12-31 01:46:41 +00:00
|
|
|
///
|
2016-12-21 18:18:26 +00:00
|
|
|
/// If `true`, all response finalizers will be skipped.
|
2016-04-18 03:27:23 +00:00
|
|
|
bool willCloseItself = false;
|
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
static StateError closed() => new StateError('Cannot modify a closed response.');
|
2017-03-28 23:29:22 +00:00
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Sends a download as a response.
|
2017-09-22 04:48:22 +00:00
|
|
|
Future download(File file, {String filename}) async {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2017-03-28 23:29:22 +00:00
|
|
|
|
2016-12-19 01:38:23 +00:00
|
|
|
headers["Content-Disposition"] =
|
2017-06-19 01:53:51 +00:00
|
|
|
'attachment; filename="${filename ?? file.path}"';
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = lookupMimeType(file.path);
|
|
|
|
headers['content-length'] = file.lengthSync().toString();
|
2017-08-15 23:01:16 +00:00
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
if (streaming) {
|
2017-08-15 23:01:16 +00:00
|
|
|
await file.openRead().pipe(this);
|
|
|
|
} else {
|
|
|
|
buffer.add(await file.readAsBytes());
|
|
|
|
end();
|
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-25 02:32:16 +00:00
|
|
|
/// Prevents more data from being written to the response, and locks it entire from further editing.
|
2018-02-07 05:21:14 +00:00
|
|
|
///
|
|
|
|
/// This method should be overwritten, setting [streaming] to `false`, **after** a `super` call.
|
2017-08-15 23:01:16 +00:00
|
|
|
Future close() {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (streaming) {
|
2017-08-15 23:01:16 +00:00
|
|
|
_buffer?.clear();
|
|
|
|
} else if (_buffer is _LockableBytesBuilder) {
|
|
|
|
(_buffer as _LockableBytesBuilder)._lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_done?.isCompleted == false) _done.complete();
|
2018-02-07 05:21:14 +00:00
|
|
|
return new Future.value();
|
2017-04-25 02:32:16 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 08:50:16 +00:00
|
|
|
/// Disposes of all resources.
|
|
|
|
Future dispose() async {
|
|
|
|
await close();
|
|
|
|
properties.clear();
|
|
|
|
encoders.clear();
|
|
|
|
_buffer.clear();
|
|
|
|
cookies.clear();
|
|
|
|
app = null;
|
|
|
|
_headers.clear();
|
|
|
|
serializer = null;
|
|
|
|
}
|
|
|
|
|
2017-04-25 02:32:16 +00:00
|
|
|
/// Prevents further request handlers from running on the response, except for response finalizers.
|
|
|
|
///
|
|
|
|
/// To disable response finalizers, see [willCloseItself].
|
2018-02-07 05:21:14 +00:00
|
|
|
///
|
|
|
|
/// This method should also set [!isOpen] to true.
|
2017-04-25 02:32:16 +00:00
|
|
|
void end() {
|
2017-10-28 08:50:16 +00:00
|
|
|
if (_done?.isCompleted == false) _done.complete();
|
2016-10-22 20:41:36 +00:00
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
/// Serializes JSON to the response.
|
2016-12-31 01:46:41 +00:00
|
|
|
void json(value) => serialize(value, contentType: ContentType.JSON);
|
2016-04-18 03:27:23 +00:00
|
|
|
|
|
|
|
/// Returns a JSONP response.
|
2017-03-28 23:29:22 +00:00
|
|
|
void jsonp(value, {String callbackName: "callback", contentType}) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2017-03-28 23:29:22 +00:00
|
|
|
write("$callbackName(${serializer(value)})");
|
|
|
|
|
|
|
|
if (contentType != null) {
|
|
|
|
if (contentType is ContentType)
|
|
|
|
this.contentType = contentType;
|
|
|
|
else
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = contentType.toString();
|
2017-03-28 23:29:22 +00:00
|
|
|
} else
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = 'application/javascript';
|
2017-03-28 23:29:22 +00:00
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
end();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Renders a view to the response stream, and closes the response.
|
2016-04-22 02:40:37 +00:00
|
|
|
Future render(String view, [Map data]) async {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2016-04-22 02:40:37 +00:00
|
|
|
write(await app.viewGenerator(view, data));
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = ContentType.HTML.toString();
|
2016-04-18 03:27:23 +00:00
|
|
|
end();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Redirects to user to the given URL.
|
2016-11-28 00:49:27 +00:00
|
|
|
///
|
|
|
|
/// [url] can be a `String`, or a `List`.
|
|
|
|
/// If it is a `List`, a URI will be constructed
|
|
|
|
/// based on the provided params.
|
|
|
|
///
|
|
|
|
/// See [Router]#navigate for more. :)
|
2016-12-23 01:49:30 +00:00
|
|
|
void redirect(url, {bool absolute: true, int code: 302}) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2017-04-15 17:42:21 +00:00
|
|
|
headers
|
2018-02-07 05:22:15 +00:00
|
|
|
..['content-type'] = ContentType.HTML.toString()
|
|
|
|
..['location'] =
|
2017-06-19 01:53:51 +00:00
|
|
|
url is String ? url : app.navigate(url, absolute: absolute);
|
2016-12-23 01:49:30 +00:00
|
|
|
statusCode = code ?? 302;
|
2016-04-18 03:27:23 +00:00
|
|
|
write('''
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>Redirecting...</title>
|
|
|
|
<meta http-equiv="refresh" content="0; url=$url">
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>Currently redirecting you...</h1>
|
|
|
|
<br />
|
2016-07-04 18:06:31 +00:00
|
|
|
Click <a href="$url">here</a> if you are not automatically redirected...
|
2016-04-18 03:27:23 +00:00
|
|
|
<script>
|
|
|
|
window.location = "$url";
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
''');
|
|
|
|
end();
|
|
|
|
}
|
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
/// Redirects to the given named [Route].
|
2016-10-22 20:41:36 +00:00
|
|
|
void redirectTo(String name, [Map params, int code]) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2016-11-28 00:49:27 +00:00
|
|
|
Route _findRoute(Router r) {
|
|
|
|
for (Route route in r.routes) {
|
|
|
|
if (route is SymlinkRoute) {
|
|
|
|
final m = _findRoute(route.router);
|
2016-11-23 09:10:47 +00:00
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
if (m != null) return m;
|
|
|
|
} else if (route.name == name) return route;
|
2016-11-23 09:10:47 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
return null;
|
2016-11-23 09:10:47 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
Route matched = _findRoute(app);
|
2016-11-23 09:10:47 +00:00
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
if (matched != null) {
|
2016-10-22 20:41:36 +00:00
|
|
|
redirect(matched.makeUri(params), code: code);
|
|
|
|
return;
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new ArgumentError.notNull('Route to redirect to ($name)');
|
|
|
|
}
|
|
|
|
|
2016-06-27 00:20:42 +00:00
|
|
|
/// Redirects to the given [Controller] action.
|
2016-10-22 20:41:36 +00:00
|
|
|
void redirectToAction(String action, [Map params, int code]) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2016-06-27 00:20:42 +00:00
|
|
|
// UserController@show
|
|
|
|
List<String> split = action.split("@");
|
|
|
|
|
|
|
|
if (split.length < 2)
|
2016-10-22 20:41:36 +00:00
|
|
|
throw new Exception(
|
|
|
|
"Controller redirects must take the form of 'Controller@action'. You gave: $action");
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
Controller controller =
|
2017-10-04 14:09:12 +00:00
|
|
|
app.controllers[split[0].replaceAll(_straySlashes, '')];
|
2016-06-27 00:20:42 +00:00
|
|
|
|
|
|
|
if (controller == null)
|
|
|
|
throw new Exception("Could not find a controller named '${split[0]}'");
|
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Route matched = controller.routeMappings[split[1]];
|
2016-06-27 00:20:42 +00:00
|
|
|
|
|
|
|
if (matched == null)
|
2016-10-22 20:41:36 +00:00
|
|
|
throw new Exception(
|
|
|
|
"Controller '${split[0]}' does not contain any action named '${split[1]}'");
|
2016-06-27 00:20:42 +00:00
|
|
|
|
2016-11-28 00:49:27 +00:00
|
|
|
final head =
|
2017-06-19 01:53:51 +00:00
|
|
|
controller.findExpose().path.toString().replaceAll(_straySlashes, '');
|
2016-11-28 00:49:27 +00:00
|
|
|
final tail = matched.makeUri(params).replaceAll(_straySlashes, '');
|
|
|
|
|
|
|
|
redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code);
|
2016-06-27 00:20:42 +00:00
|
|
|
}
|
|
|
|
|
2016-12-21 18:18:26 +00:00
|
|
|
/// Copies a file's contents into the response buffer.
|
2017-09-22 04:48:22 +00:00
|
|
|
Future sendFile(File file) async {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = lookupMimeType(file.path);
|
2016-12-19 01:38:23 +00:00
|
|
|
buffer.add(await file.readAsBytes());
|
2016-12-21 18:18:26 +00:00
|
|
|
end();
|
|
|
|
}
|
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
/// Serializes data to the response.
|
|
|
|
///
|
|
|
|
/// [contentType] can be either a [String], or a [ContentType].
|
|
|
|
void serialize(value, {contentType}) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2017-03-28 23:29:22 +00:00
|
|
|
|
2018-02-07 03:16:52 +00:00
|
|
|
var text = serializer(value);
|
|
|
|
|
|
|
|
if (text.isEmpty)
|
|
|
|
return;
|
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
if (contentType is String)
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = contentType;
|
2016-12-31 01:46:41 +00:00
|
|
|
else if (contentType is ContentType) this.contentType = contentType;
|
|
|
|
|
2017-07-23 15:41:12 +00:00
|
|
|
write(text);
|
|
|
|
|
2016-12-31 01:46:41 +00:00
|
|
|
end();
|
|
|
|
}
|
|
|
|
|
2016-12-21 18:18:26 +00:00
|
|
|
/// Streams a file to this response.
|
|
|
|
///
|
|
|
|
/// You can optionally transform the file stream with a [codec].
|
2017-09-22 04:48:22 +00:00
|
|
|
Future streamFile(File file) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen) throw closed();
|
2016-12-21 18:18:26 +00:00
|
|
|
|
2018-02-07 05:22:15 +00:00
|
|
|
headers['content-type'] = lookupMimeType(file.path);
|
2017-09-22 04:48:22 +00:00
|
|
|
return file.openRead().pipe(this);
|
2017-08-15 23:01:16 +00:00
|
|
|
}
|
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
/// Releases critical resources from the [correspondingRequest].
|
|
|
|
void releaseCorrespondingRequest() {
|
|
|
|
if (correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
|
|
|
|
(correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
|
2017-11-28 18:14:50 +00:00
|
|
|
}
|
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
if (correspondingRequest?.injections?.containsKey(PoolResource) ==
|
|
|
|
true) {
|
|
|
|
(correspondingRequest.injections[PoolResource] as PoolResource)
|
|
|
|
.release();
|
|
|
|
}
|
2017-11-28 18:14:50 +00:00
|
|
|
}
|
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
/// Configure the response to write directly to the output stream, instead of buffering.
|
|
|
|
bool useStream();
|
|
|
|
|
|
|
|
/// Adds a stream directly the underlying response.
|
2017-08-15 23:01:16 +00:00
|
|
|
///
|
|
|
|
/// This will also set [willCloseItself] to `true`, thus canceling out response finalizers.
|
|
|
|
///
|
|
|
|
/// If this instance has access to a [correspondingRequest], then it will attempt to transform
|
|
|
|
/// the content using at most one of the response [encoders].
|
|
|
|
@override
|
2018-02-07 05:21:14 +00:00
|
|
|
Future addStream(Stream<List<int>> stream);
|
2017-08-15 23:01:16 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void addError(Object error, [StackTrace stackTrace]) {
|
|
|
|
if (_done?.isCompleted == false) _done.completeError(error, stackTrace);
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Writes data to the response.
|
2017-08-15 23:01:16 +00:00
|
|
|
void write(value, {Encoding encoding}) {
|
|
|
|
encoding ??= UTF8;
|
|
|
|
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen && !streaming)
|
|
|
|
throw closed();
|
|
|
|
else if (streaming) {
|
2017-08-15 23:01:16 +00:00
|
|
|
if (value is List<int>)
|
2018-02-07 05:21:14 +00:00
|
|
|
add(value);
|
2017-08-15 23:01:16 +00:00
|
|
|
else
|
2018-02-07 05:21:14 +00:00
|
|
|
add(encoding.encode(value.toString()));
|
2017-08-15 23:01:16 +00:00
|
|
|
} else {
|
2017-04-25 02:32:16 +00:00
|
|
|
if (value is List<int>)
|
|
|
|
buffer.add(value);
|
|
|
|
else
|
|
|
|
buffer.add(encoding.encode(value.toString()));
|
|
|
|
}
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
2017-06-19 01:53:51 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void writeCharCode(int charCode) {
|
2018-02-07 05:21:14 +00:00
|
|
|
if (!isOpen && !streaming)
|
|
|
|
throw closed();
|
|
|
|
else if (streaming)
|
|
|
|
add([charCode]);
|
2017-06-19 01:53:51 +00:00
|
|
|
else
|
|
|
|
buffer.addByte(charCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void writeln([Object obj = ""]) {
|
|
|
|
write(obj.toString());
|
|
|
|
write('\r\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void writeAll(Iterable objects, [String separator = ""]) {
|
|
|
|
write(objects.join(separator));
|
|
|
|
}
|
2016-10-22 20:41:36 +00:00
|
|
|
}
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
abstract class _LockableBytesBuilder extends BytesBuilder {
|
2017-10-28 08:50:16 +00:00
|
|
|
factory _LockableBytesBuilder() {
|
|
|
|
return new _LockableBytesBuilderImpl();
|
|
|
|
}
|
2017-05-27 12:39:45 +00:00
|
|
|
|
2017-03-28 23:29:22 +00:00
|
|
|
void _lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _LockableBytesBuilderImpl implements _LockableBytesBuilder {
|
2017-10-28 08:50:16 +00:00
|
|
|
final BytesBuilder _buf = new BytesBuilder(copy: false);
|
2017-03-28 23:29:22 +00:00
|
|
|
bool _closed = false;
|
|
|
|
|
|
|
|
StateError _deny() =>
|
|
|
|
new StateError('Cannot modified a closed response\'s buffer.');
|
|
|
|
|
|
|
|
@override
|
|
|
|
void _lock() {
|
2017-04-25 02:32:16 +00:00
|
|
|
_closed = true;
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
2017-08-15 23:01:16 +00:00
|
|
|
|
2017-03-28 23:29:22 +00:00
|
|
|
@override
|
|
|
|
void add(List<int> bytes) {
|
|
|
|
if (_closed)
|
|
|
|
throw _deny();
|
2017-12-06 14:46:35 +00:00
|
|
|
else
|
|
|
|
_buf.add(bytes);
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void addByte(int byte) {
|
|
|
|
if (_closed)
|
|
|
|
throw _deny();
|
2017-12-06 14:46:35 +00:00
|
|
|
else
|
|
|
|
_buf.addByte(byte);
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void clear() {
|
2017-10-28 08:50:16 +00:00
|
|
|
_buf.clear();
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2017-10-28 08:50:16 +00:00
|
|
|
bool get isEmpty => _buf.isEmpty;
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
@override
|
2017-10-28 08:50:16 +00:00
|
|
|
bool get isNotEmpty => _buf.isNotEmpty;
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
@override
|
2017-10-28 08:50:16 +00:00
|
|
|
int get length => _buf.length;
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
List<int> takeBytes() {
|
2017-10-28 08:50:16 +00:00
|
|
|
return _buf.takeBytes();
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
List<int> toBytes() {
|
2017-10-28 08:50:16 +00:00
|
|
|
return _buf.toBytes();
|
2017-03-28 23:29:22 +00:00
|
|
|
}
|
|
|
|
}
|