Deprecated ResponseContext.io
, added HTTP equivalent
This commit is contained in:
parent
cf02582d1b
commit
9b7bf84acf
5 changed files with 272 additions and 197 deletions
|
@ -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>
|
||||
|
|
|
@ -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 ?? {}));
|
||||
|
||||
|
|
120
lib/src/http/http_response_context.dart
Normal file
120
lib/src/http/http_response_context.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
Loading…
Reference in a new issue