Deprecated ResponseContext.io, added HTTP equivalent

This commit is contained in:
Tobe O 2018-02-07 00:21:14 -05:00
parent cf02582d1b
commit 9b7bf84acf
5 changed files with 272 additions and 197 deletions

View file

@ -2,8 +2,11 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="7b89ff1e-1260-4dcf-9c3d-345de0471ea1" name="Default" comment="">
<change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/lib/src/http/http_response_context.dart" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/lib/src/http/angel_http.dart" afterPath="$PROJECT_DIR$/lib/src/http/angel_http.dart" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/lib/src/http/response_context.dart" afterPath="$PROJECT_DIR$/lib/src/http/response_context.dart" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/lib/src/http/server.dart" afterPath="$PROJECT_DIR$/lib/src/http/server.dart" />
</list>
<ignored path="$PROJECT_DIR$/.tmp/" />
<ignored path="$PROJECT_DIR$/temp/" />
@ -25,16 +28,26 @@
<favorites_list name="framework" />
</component>
<component name="FileEditorManager">
<splitter split-orientation="horizontal" split-proportion="0.53945374">
<splitter split-orientation="horizontal" split-proportion="0.5">
<split-first>
<leaf>
<file leaf-file-name="angel_http.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/src/http/angel_http.dart">
<file leaf-file-name="response_context.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/src/http/response_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="212">
<caret line="278" column="31" lean-forward="false" selection-start-line="278" selection-start-column="31" selection-end-line="278" selection-end-column="31" />
<state relative-caret-position="402">
<caret line="151" column="5" lean-forward="true" selection-start-line="151" selection-start-column="5" selection-end-line="151" selection-end-column="5" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="metric_server.dart" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/lib/src/stats/metric_server.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="135">
<caret line="12" column="43" lean-forward="true" selection-start-line="12" selection-start-column="43" selection-end-line="12" selection-end-column="43" />
<folding>
<element signature="e#0#20#0" expanded="true" />
<marker date="1512571506000" expanded="true" signature="675:868" ph="..." />
</folding>
</state>
</provider>
@ -44,11 +57,11 @@
</split-first>
<split-second>
<leaf>
<file leaf-file-name="http_request_context.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/src/http/http_request_context.dart">
<file leaf-file-name="http_response_context.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/src/http/http_response_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="90">
<caret line="6" column="69" lean-forward="false" selection-start-line="6" selection-start-column="69" selection-end-line="6" selection-end-column="69" />
<state relative-caret-position="437">
<caret line="108" column="0" lean-forward="true" selection-start-line="108" selection-start-column="0" selection-end-line="108" selection-end-column="0" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
@ -70,12 +83,6 @@
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>handleRequest</find>
<find>flatten</find>
<find>pipeline</find>
<find>pat</find>
<find>createR</find>
<find>print</find>
<find>dispos</find>
<find>xhr</find>
<find>accepts</find>
@ -100,6 +107,12 @@
<find>handleRe</find>
<find>instead.</find>
<find>io\b</find>
<find>_isOpen</find>
<find>_isClosed</find>
<find>end</find>
<find>isOpen</find>
<find>_useStream</find>
<find>isClosed</find>
</findStrings>
<replaceStrings>
<replace>_isClosed</replace>
@ -123,6 +136,10 @@
<replace>autoSnakeCaseNames == false ? $0 : '$1ated_at'</replace>
<replace>'content-type'</replace>
<replace>appa</replace>
<replace>isClosed</replace>
<replace>useStream</replace>
<replace>streaming</replace>
<replace>!isOpen</replace>
</replaceStrings>
<dirStrings>
<dir>C:\Users\thosa\Source\Angel\framework\lib</dir>
@ -149,7 +166,6 @@
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/performance/hello/raw.dart" />
<option value="$PROJECT_DIR$/performance/hello/angel.md" />
<option value="$PROJECT_DIR$/performance/hello/raw.md" />
<option value="$PROJECT_DIR$/tool/travis.sh" />
@ -183,7 +199,6 @@
<option value="$PROJECT_DIR$/lib/src/http/service.dart" />
<option value="$PROJECT_DIR$/pubspec.yaml" />
<option value="$PROJECT_DIR$/lib/src/http/map_service.dart" />
<option value="$PROJECT_DIR$/lib/src/http/response_context.dart" />
<option value="$PROJECT_DIR$/CHANGELOG.md" />
<option value="$PROJECT_DIR$/lib/src/http/http.dart" />
<option value="$PROJECT_DIR$/test/extension_test.dart" />
@ -193,13 +208,15 @@
<option value="$PROJECT_DIR$/test/routing_test.dart" />
<option value="$PROJECT_DIR$/test/parameter_meta_test.dart" />
<option value="$PROJECT_DIR$/test/primitives_test.dart" />
<option value="$PROJECT_DIR$/lib/src/http/server.dart" />
<option value="$PROJECT_DIR$/test/server_test.dart" />
<option value="$PROJECT_DIR$/example/json.dart" />
<option value="$PROJECT_DIR$/example/main.dart" />
<option value="$PROJECT_DIR$/lib/src/http/request_context.dart" />
<option value="$PROJECT_DIR$/lib/src/http/http_request_context.dart" />
<option value="$PROJECT_DIR$/lib/src/http/angel_http.dart" />
<option value="$PROJECT_DIR$/lib/src/http/server.dart" />
<option value="$PROJECT_DIR$/lib/src/http/response_context.dart" />
<option value="$PROJECT_DIR$/lib/src/http/http_response_context.dart" />
</list>
</option>
</component>
@ -613,14 +630,7 @@
<workItem from="1513103483207" duration="18000" />
<workItem from="1513103506825" duration="139000" />
<workItem from="1517332581856" duration="858000" />
<workItem from="1517973177718" duration="6361000" />
</task>
<task id="LOCAL-00014" summary="1.0.3">
<created>1497200046584</created>
<option name="number" value="00014" />
<option name="presentableId" value="LOCAL-00014" />
<option name="project" value="LOCAL" />
<updated>1497200046584</updated>
<workItem from="1517973177718" duration="7635000" />
</task>
<task id="LOCAL-00015" summary="+1">
<created>1497200256280</created>
@ -958,7 +968,14 @@
<option name="project" value="LOCAL" />
<updated>1517979545063</updated>
</task>
<option name="localTasksCounter" value="63" />
<task id="LOCAL-00063" summary="Change return type of `AngelHttp.createRequestContext`">
<created>1517979599468</created>
<option name="number" value="00063" />
<option name="presentableId" value="LOCAL-00063" />
<option name="project" value="LOCAL" />
<updated>1517979599468</updated>
</task>
<option name="localTasksCounter" value="64" />
<servers />
</component>
<component name="TestHistory">
@ -994,7 +1011,7 @@
</history-entry>
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="124397000" />
<option name="totallyTimeSpent" value="125671000" />
</component>
<component name="TodoView">
<todo-panel id="selected-file">
@ -1053,7 +1070,6 @@
</component>
<component name="VcsManagerConfiguration">
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<MESSAGE value="+4" />
<MESSAGE value="+6" />
<MESSAGE value="autoIdAndDateFields in MapService" />
<MESSAGE value="+8" />
@ -1078,22 +1094,16 @@
<MESSAGE value="Updated tests to use `AngelHttp` API" />
<MESSAGE value="Added example/main.dart" />
<MESSAGE value="Create HttpRequestContextImpl" />
<option name="LAST_COMMIT_MESSAGE" value="Create HttpRequestContextImpl" />
<MESSAGE value="Change return type of `AngelHttp.createRequestContext`" />
<option name="LAST_COMMIT_MESSAGE" value="Change return type of `AngelHttp.createRequestContext`" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<option name="time" value="4" />
<option name="time" value="5" />
</breakpoint-manager>
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/lib/src/http/angel_base.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="107">
<caret line="10" column="30" lean-forward="false" selection-start-line="10" selection-start-column="30" selection-end-line="10" selection-end-column="30" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/performance/hello/angel.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
@ -1277,14 +1287,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/response_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1005">
<caret line="76" column="26" lean-forward="false" selection-start-line="76" selection-start-column="26" selection-end-line="76" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/angel_route-2.0.3+1/lib/src/router.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="15">
@ -1312,16 +1314,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/stats/metric_server.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="461">
<caret line="65" column="61" lean-forward="false" selection-start-line="65" selection-start-column="61" selection-end-line="65" selection-end-column="61" />
<folding>
<marker date="1512571506000" expanded="true" signature="675:868" ph="..." />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/http.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="263">
@ -1443,16 +1435,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/server.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="6330">
<caret line="422" column="22" lean-forward="true" selection-start-line="422" selection-start-column="22" selection-end-line="422" selection-end-column="22" />
<folding>
<element signature="e#38#58#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/meta-1.1.2/lib/meta.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="224">
@ -1477,16 +1459,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/http_request_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="90">
<caret line="6" column="69" lean-forward="false" selection-start-line="6" selection-start-column="69" selection-end-line="6" selection-end-column="69" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/request_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="227">
@ -1497,16 +1469,64 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/angel_http.dart">
<entry file="file://$PROJECT_DIR$/lib/src/http/http_request_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="212">
<caret line="278" column="31" lean-forward="false" selection-start-line="278" selection-start-column="31" selection-end-line="278" selection-end-column="31" />
<state relative-caret-position="-910">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/angel_http.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="188">
<caret line="292" column="38" lean-forward="false" selection-start-line="292" selection-start-column="38" selection-end-line="292" selection-end-column="38" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/server.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="137">
<caret line="41" column="16" lean-forward="false" selection-start-line="41" selection-start-column="16" selection-end-line="41" selection-end-column="16" />
<folding>
<element signature="e#38#58#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/stats/metric_server.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="135">
<caret line="12" column="43" lean-forward="true" selection-start-line="12" selection-start-column="43" selection-end-line="12" selection-end-column="43" />
<folding>
<marker date="1512571506000" expanded="true" signature="675:868" ph="..." />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/http_response_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="437">
<caret line="108" column="0" lean-forward="true" selection-start-line="108" selection-start-column="0" selection-end-line="108" selection-end-column="0" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/src/http/response_context.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="402">
<caret line="151" column="5" lean-forward="true" selection-start-line="151" selection-start-column="5" selection-end-line="151" selection-end-column="5" />
<folding />
</state>
</provider>
</entry>
</component>
<component name="masterDetails">
<states>

View file

@ -8,6 +8,7 @@ import 'package:json_god/json_god.dart' as god;
import 'package:pool/pool.dart';
import 'package:tuple/tuple.dart';
import 'http_request_context.dart';
import 'http_response_context.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'server.dart';
@ -289,7 +290,7 @@ class AngelHttp {
Future<ResponseContext> createResponseContext(HttpResponse response,
[RequestContext correspondingRequest]) =>
new Future<ResponseContext>.value(
new ResponseContext(response, app, correspondingRequest)
new HttpResponseContextImpl(response, app, correspondingRequest)
..serializer = (app.serializer ?? god.serialize)
..encoders.addAll(app.encoders ?? {}));

View file

@ -0,0 +1,120 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'http_request_context.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'server.dart';
class HttpResponseContextImpl extends ResponseContext {
/// The underlying [HttpResponse] under this instance.
@override
final HttpResponse io;
Angel app;
final HttpRequestContextImpl _correspondingRequest;
bool _isClosed = false, _useStream = false;
HttpResponseContextImpl(this.io, this.app, [this._correspondingRequest]);
@override
RequestContext get correspondingRequest {
return _correspondingRequest;
}
@override
bool get streaming {
return _useStream;
}
@override
void addError(Object error, [StackTrace stackTrace]) {
io.addError(error, stackTrace);
super.addError(error, stackTrace);
}
@override
bool useStream() {
if (!_useStream) {
// If this is the first stream added to this response,
// then add headers, status code, etc.
io
..statusCode = statusCode
..cookies.addAll(cookies);
headers.forEach(io.headers.set);
willCloseItself = _useStream = _isClosed = true;
releaseCorrespondingRequest();
return true;
}
return false;
}
@override
void end() {
_isClosed = true;
super.end();
}
@override
Future addStream(Stream<List<int>> stream) {
if (_isClosed && !_useStream) throw ResponseContext.closed();
var firstStream = useStream();
Stream<List<int>> output = stream;
if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings =
(correspondingRequest.headers[HttpHeaders.ACCEPT_ENCODING] ?? [])
.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
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) {
if (firstStream) {
io.headers.set(HttpHeaders.CONTENT_ENCODING, key);
}
output = encoders[key].bind(output);
break;
}
}
}
return io.addStream(output);
}
@override
void add(List<int> data) {
if (_isClosed && !_useStream)
throw ResponseContext.closed();
else if (_useStream)
io.add(data);
else
buffer.add(data);
}
@override
Future close() async {
if (_useStream) {
await io.close();
}
_isClosed = true;
await super.close();
_useStream = false;
}
}

View file

@ -23,14 +23,12 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
typedef String ResponseSerializer(data);
/// A convenience wrapper around an outgoing HTTP request.
class ResponseContext implements StreamSink<List<int>>, StringSink {
abstract class ResponseContext implements StreamSink<List<int>>, StringSink {
final Map properties = {};
final BytesBuilder _buffer = new _LockableBytesBuilder();
final Map<String, String> _headers = {HttpHeaders.SERVER: 'angel'};
final RequestContext _correspondingRequest;
Completer _done;
bool _isOpen = true, _isClosed = false, _useStream = false;
int _statusCode = 200;
/// The [Angel] instance that is sending a response.
@ -48,7 +46,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
final Map<String, Converter<List<int>, List<int>>> encoders = {};
/// Points to the [RequestContext] corresponding to this response.
RequestContext get correspondingRequest => _correspondingRequest;
RequestContext get correspondingRequest;
@override
Future get done => (_done ?? new Completer()).future;
@ -56,7 +54,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Headers that will be sent to the user.
Map<String, String> get headers {
/// If the response is closed, then this getter will return an immutable `Map`.
if (_isClosed)
if (!isOpen)
return new Map<String, String>.unmodifiable(_headers);
else
return _headers;
@ -80,20 +78,23 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
int get statusCode => _statusCode;
void set statusCode(int value) {
if (_isClosed)
throw _closed();
if (!isOpen)
throw closed();
else
_statusCode = value ?? 200;
}
/// Can we still write to this response?
bool get isOpen => _isOpen && !_isClosed;
bool get isOpen => !!isOpen;
/// Returns `true` if a [Stream] is being written directly.
bool get streaming;
/// A set of UTF-8 encoded bytes that will be written to the response.
BytesBuilder get buffer => _buffer;
/// The underlying [HttpResponse] under this instance.
final HttpResponse io;
HttpResponse get io;
/// Gets the Content-Type header.
ContentType get contentType {
@ -116,25 +117,23 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
headers[HttpHeaders.CONTENT_TYPE] = contentType.toString();
}
ResponseContext(this.io, this.app, [this._correspondingRequest]);
/// Set this to true if you will manually close the response.
///
/// If `true`, all response finalizers will be skipped.
bool willCloseItself = false;
StateError _closed() => new StateError('Cannot modify a closed response.');
static StateError closed() => new StateError('Cannot modify a closed response.');
/// Sends a download as a response.
Future download(File file, {String filename}) async {
if (!_isOpen) throw _closed();
if (!isOpen) throw closed();
headers["Content-Disposition"] =
'attachment; filename="${filename ?? file.path}"';
headers[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path);
headers[HttpHeaders.CONTENT_LENGTH] = file.lengthSync().toString();
if (_useStream) {
if (streaming) {
await file.openRead().pipe(this);
} else {
buffer.add(await file.readAsBytes());
@ -143,22 +142,17 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
}
/// Prevents more data from being written to the response, and locks it entire from further editing.
///
/// This method should be overwritten, setting [streaming] to `false`, **after** a `super` call.
Future close() {
var f = new Future.value();
if (_useStream) {
_useStream = false;
if (streaming) {
_buffer?.clear();
f = io.close();
} else if (_buffer is _LockableBytesBuilder) {
(_buffer as _LockableBytesBuilder)._lock();
}
_isOpen = _useStream = false;
_isClosed = true;
if (_done?.isCompleted == false) _done.complete();
return f;
return new Future.value();
}
/// Disposes of all resources.
@ -176,8 +170,9 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Prevents further request handlers from running on the response, except for response finalizers.
///
/// To disable response finalizers, see [willCloseItself].
///
/// This method should also set [!isOpen] to true.
void end() {
_isOpen = false;
if (_done?.isCompleted == false) _done.complete();
}
@ -186,7 +181,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Returns a JSONP response.
void jsonp(value, {String callbackName: "callback", contentType}) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
write("$callbackName(${serializer(value)})");
if (contentType != null) {
@ -202,7 +197,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Renders a view to the response stream, and closes the response.
Future render(String view, [Map data]) async {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
write(await app.viewGenerator(view, data));
headers[HttpHeaders.CONTENT_TYPE] = ContentType.HTML.toString();
end();
@ -216,7 +211,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
///
/// See [Router]#navigate for more. :)
void redirect(url, {bool absolute: true, int code: 302}) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
headers
..[HttpHeaders.CONTENT_TYPE] = ContentType.HTML.toString()
..[HttpHeaders.LOCATION] =
@ -244,7 +239,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Redirects to the given named [Route].
void redirectTo(String name, [Map params, int code]) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
Route _findRoute(Router r) {
for (Route route in r.routes) {
if (route is SymlinkRoute) {
@ -269,7 +264,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Redirects to the given [Controller] action.
void redirectToAction(String action, [Map params, int code]) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
// UserController@show
List<String> split = action.split("@");
@ -298,7 +293,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
/// Copies a file's contents into the response buffer.
Future sendFile(File file) async {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
headers[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path);
buffer.add(await file.readAsBytes());
@ -309,7 +304,7 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
///
/// [contentType] can be either a [String], or a [ContentType].
void serialize(value, {contentType}) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
var text = serializer(value);
@ -329,99 +324,39 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
///
/// You can optionally transform the file stream with a [codec].
Future streamFile(File file) {
if (_isClosed) throw _closed();
if (!isOpen) throw closed();
headers[HttpHeaders.CONTENT_TYPE] = lookupMimeType(file.path);
return file.openRead().pipe(this);
}
@override
void add(List<int> data) {
if (_isClosed && !_useStream)
throw _closed();
else if (_useStream)
io.add(data);
else
buffer.add(data);
/// Releases critical resources from the [correspondingRequest].
void releaseCorrespondingRequest() {
if (correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
(correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
}
if (correspondingRequest?.injections?.containsKey(PoolResource) ==
true) {
(correspondingRequest.injections[PoolResource] as PoolResource)
.release();
}
}
/// Configure the response to write directly to the output stream, instead of buffering.
bool useStream() {
if (!_useStream) {
// If this is the first stream added to this response,
// then add headers, status code, etc.
io
..statusCode = statusCode
..cookies.addAll(cookies);
headers.forEach(io.headers.set);
willCloseItself = _useStream = _isClosed = true;
bool useStream();
if (_correspondingRequest?.injections?.containsKey(Stopwatch) == true) {
(_correspondingRequest.injections[Stopwatch] as Stopwatch).stop();
}
if (_correspondingRequest?.injections?.containsKey(PoolResource) ==
true) {
(_correspondingRequest.injections[PoolResource] as PoolResource)
.release();
}
return true;
}
return false;
}
/// Adds a stream directly the underlying dart:[io] response.
/// Adds a stream directly the underlying response.
///
/// 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
Future addStream(Stream<List<int>> stream) {
if (_isClosed && !_useStream) throw _closed();
var firstStream = useStream();
Stream<List<int>> output = stream;
if (encoders.isNotEmpty && correspondingRequest != null) {
var allowedEncodings =
(correspondingRequest.headers[HttpHeaders.ACCEPT_ENCODING] ?? [])
.map((str) {
// Ignore quality specifications in accept-encoding
// ex. gzip;q=0.8
if (!str.contains(';')) return str;
return str.split(';')[0];
});
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) {
if (firstStream) {
io.headers.set(HttpHeaders.CONTENT_ENCODING, key);
}
output = encoders[key].bind(output);
break;
}
}
}
return io.addStream(output);
}
Future addStream(Stream<List<int>> stream);
@override
void addError(Object error, [StackTrace stackTrace]) {
io.addError(error, stackTrace);
if (_done?.isCompleted == false) _done.completeError(error, stackTrace);
}
@ -429,13 +364,13 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
void write(value, {Encoding encoding}) {
encoding ??= UTF8;
if (_isClosed && !_useStream)
throw _closed();
else if (_useStream) {
if (!isOpen && !streaming)
throw closed();
else if (streaming) {
if (value is List<int>)
io.add(value);
add(value);
else
io.add(encoding.encode(value.toString()));
add(encoding.encode(value.toString()));
} else {
if (value is List<int>)
buffer.add(value);
@ -446,10 +381,10 @@ class ResponseContext implements StreamSink<List<int>>, StringSink {
@override
void writeCharCode(int charCode) {
if (_isClosed && !_useStream)
throw _closed();
else if (_useStream)
io.add([charCode]);
if (!isOpen && !streaming)
throw closed();
else if (streaming)
add([charCode]);
else
buffer.addByte(charCode);
}

View file

@ -40,7 +40,6 @@ class Angel extends AngelBase {
AngelHttp _http;
bool _isProduction;
Angel _parent;
Future<HttpServer> Function(dynamic, int) _serverGenerator = HttpServer.bind;
/// A global Map of converters that can transform responses bodies.
final Map<String, Converter<List<int>, List<int>>> encoders = {};