2.0.0-alpha.11
This commit is contained in:
parent
2d168dd3aa
commit
bf0f01a5b9
23 changed files with 418 additions and 233 deletions
29
dev.key
Normal file
29
dev.key
Normal file
|
@ -0,0 +1,29 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
|
||||
xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
|
||||
ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
|
||||
Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
|
||||
qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
|
||||
gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
|
||||
0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
|
||||
gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
|
||||
oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
|
||||
oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
|
||||
kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
|
||||
zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
|
||||
J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
|
||||
d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
|
||||
TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
|
||||
ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
|
||||
HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
|
||||
goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
|
||||
EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
|
||||
ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
|
||||
YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
|
||||
q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
|
||||
Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
|
||||
Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
|
||||
QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
|
||||
xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
|
||||
AUukhVtTNn4=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
57
dev.pem
Normal file
57
dev.pem
Normal file
|
@ -0,0 +1,57 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
|
||||
BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
|
||||
MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
|
||||
Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
|
||||
EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
|
||||
we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
|
||||
N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
|
||||
7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
|
||||
hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
|
||||
BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
|
||||
YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
|
||||
AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
|
||||
CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
|
||||
4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
|
||||
MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
|
||||
V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
|
||||
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
|
||||
WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
|
||||
EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
|
||||
DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
|
||||
YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
|
||||
MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
|
||||
B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
|
||||
IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
|
||||
oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
|
||||
cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
|
||||
x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
|
||||
e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
|
||||
NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
|
||||
0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
|
||||
FKvRDxsW
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
|
||||
BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
|
||||
WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
|
||||
dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
|
||||
siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
|
||||
kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
|
||||
hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
|
||||
DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||
ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
|
||||
26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
|
||||
lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
|
||||
J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
|
||||
uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
|
||||
4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
|
||||
t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
|
||||
r6AL284qtw==
|
||||
-----END CERTIFICATE-----
|
|
@ -13,7 +13,7 @@ main() async {
|
|||
});
|
||||
app.logger = new Logger('angel')..onRecord.listen(prettyLog);
|
||||
|
||||
app.get('/', (_, __) => 'Hello HTTP/2!!!');
|
||||
app.get('/', (req, res) => 'Hello HTTP/2!!!');
|
||||
|
||||
app.fallback((req, res) => throw new AngelHttpException.notFound(
|
||||
message: 'No file exists at ${req.uri.path}'));
|
||||
|
@ -38,6 +38,6 @@ main() async {
|
|||
// HTTP/1.x requests will fallback to `AngelHttp`
|
||||
http2.onHttp1.listen(http1.handleRequest);
|
||||
|
||||
var server = await http2.startServer('127.0.0.1', 3000);
|
||||
print('Listening at https://${server.address.address}:${server.port}');
|
||||
await http2.startServer('127.0.0.1', 3000);
|
||||
print('Listening at ${http2.uri}');
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ main() async {
|
|||
});
|
||||
|
||||
// Index route. Returns JSON.
|
||||
app.get('/', (req, res) => res.write('Welcome to Angel!'));
|
||||
app.get('/', (req, res) => 'Welcome to Angel!');
|
||||
|
||||
// Accepts a URL like /greet/foo or /greet/bob.
|
||||
app.get(
|
||||
|
|
|
@ -34,15 +34,22 @@ abstract class Driver<
|
|||
/// The native server running this instance.
|
||||
Server get server => _server;
|
||||
|
||||
Future<Server> generateServer(address, int port) =>
|
||||
serverGenerator(address, port);
|
||||
|
||||
/// Starts, and returns the server.
|
||||
Future<Server> startServer([address, int port]) {
|
||||
var host = address ?? '127.0.0.1';
|
||||
return serverGenerator(host, port ?? 0).then((server) {
|
||||
return generateServer(host, port ?? 0).then((server) {
|
||||
_server = server;
|
||||
return Future.wait(app.startupHooks.map(app.configure)).then((_) {
|
||||
app.optimizeForProduction();
|
||||
_sub = server.listen((request) =>
|
||||
handleRawRequest(request, createResponseFromRawRequest(request)));
|
||||
_sub = server.listen((request) {
|
||||
var stream = createResponseStreamFromRawRequest(request);
|
||||
stream.listen((response) {
|
||||
return handleRawRequest(request, response);
|
||||
});
|
||||
});
|
||||
return _server;
|
||||
});
|
||||
});
|
||||
|
@ -78,11 +85,9 @@ abstract class Driver<
|
|||
|
||||
void writeToResponse(Response response, List<int> data);
|
||||
|
||||
Uri getUriFromRequest(Request request);
|
||||
|
||||
Future closeResponse(Response response);
|
||||
|
||||
Response createResponseFromRawRequest(Request request);
|
||||
Stream<Response> createResponseStreamFromRawRequest(Request request);
|
||||
|
||||
/// Handles a single request.
|
||||
Future handleRawRequest(Request request, Response response) {
|
||||
|
@ -123,26 +128,17 @@ abstract class Driver<
|
|||
}
|
||||
|
||||
var pipeline = tuple.item1;
|
||||
var it = pipeline.iterator;
|
||||
|
||||
Future Function() runPipeline;
|
||||
|
||||
for (var handler in pipeline) {
|
||||
if (handler == null) break;
|
||||
|
||||
if (runPipeline == null)
|
||||
runPipeline = () =>
|
||||
Future.sync(() => app.executeHandler(handler, req, res));
|
||||
else {
|
||||
var current = runPipeline;
|
||||
runPipeline = () => current().then((result) => !res.isOpen
|
||||
? new Future.value(result)
|
||||
: app.executeHandler(handler, req, res));
|
||||
}
|
||||
}
|
||||
var runPipeline = pipeline.isEmpty
|
||||
? null
|
||||
: Future.doWhile(() => !it.moveNext()
|
||||
? new Future.value(false)
|
||||
: app.executeHandler(it.current, req, res));
|
||||
|
||||
return runPipeline == null
|
||||
? sendResponse(request, response, req, res)
|
||||
: runPipeline()
|
||||
: runPipeline
|
||||
.then((_) => sendResponse(request, response, req, res));
|
||||
}
|
||||
|
||||
|
@ -211,7 +207,6 @@ abstract class Driver<
|
|||
e, trace, req, res, request, response);
|
||||
}).catchError((e, StackTrace st) {
|
||||
var trace = new Trace.from(st ?? StackTrace.current).terse;
|
||||
var uri = getUriFromRequest(request);
|
||||
closeResponse(response);
|
||||
// Ideally, we won't be in a position where an absolutely fatal error occurs,
|
||||
// but if so, we'll need to log it.
|
||||
|
@ -221,7 +216,7 @@ abstract class Driver<
|
|||
} else {
|
||||
stderr
|
||||
..writeln('Fatal error occurred when processing '
|
||||
'$uri:')
|
||||
'${req.uri}:')
|
||||
..writeln(e)
|
||||
..writeln(trace);
|
||||
}
|
||||
|
@ -299,11 +294,10 @@ abstract class Driver<
|
|||
|
||||
Future finalizers = ignoreFinalizers == true
|
||||
? new Future.value()
|
||||
: app.responseFinalizers.fold<Future>(
|
||||
new Future.value(), (out, f) => out.then((_) => f(req, res)));
|
||||
: Future.forEach(app.responseFinalizers, (f) => f(req, res));
|
||||
|
||||
return finalizers.then((_) {
|
||||
if (res.isOpen) res.close();
|
||||
//if (res.isOpen) res.close();
|
||||
|
||||
for (var key in res.headers.keys) {
|
||||
setHeader(response, key, res.headers[key]);
|
||||
|
|
|
@ -59,7 +59,8 @@ abstract class RequestContext<RawRequest> {
|
|||
String get originalMethod;
|
||||
|
||||
/// The content type of an incoming request.
|
||||
MediaType get contentType;
|
||||
MediaType get contentType =>
|
||||
new MediaType.parse(headers.contentType.toString());
|
||||
|
||||
/// The URL parameters extracted from the request URI.
|
||||
Map<String, dynamic> params = <String, dynamic>{};
|
||||
|
@ -67,6 +68,12 @@ abstract class RequestContext<RawRequest> {
|
|||
/// The requested path.
|
||||
String get path;
|
||||
|
||||
/// Is this an **XMLHttpRequest**?
|
||||
bool get isXhr {
|
||||
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
|
||||
'xmlhttprequest';
|
||||
}
|
||||
|
||||
/// The remote address requesting this resource.
|
||||
InternetAddress get remoteAddress;
|
||||
|
||||
|
@ -76,9 +83,6 @@ abstract class RequestContext<RawRequest> {
|
|||
/// The [Uri] instance representing the path this request is responding to.
|
||||
Uri get uri;
|
||||
|
||||
/// Is this an **XMLHttpRequest**?
|
||||
bool get xhr;
|
||||
|
||||
/// Returns the file extension of the requested path, if any.
|
||||
///
|
||||
/// Includes the leading `.`, if there is one.
|
||||
|
|
|
@ -20,7 +20,10 @@ final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
|||
abstract class ResponseContext<RawResponse>
|
||||
implements StreamSink<List<int>>, StringSink {
|
||||
final Map properties = {};
|
||||
final Map<String, String> _headers = {'server': 'angel'};
|
||||
final Map<String, String> _headers = new CaseInsensitiveMap.from({
|
||||
'content-type': 'text/plain',
|
||||
'server': 'angel',
|
||||
});
|
||||
|
||||
Completer _done;
|
||||
int _statusCode = 200;
|
||||
|
@ -101,7 +104,18 @@ abstract class ResponseContext<RawResponse>
|
|||
FutureOr<RawResponse> detach();
|
||||
|
||||
/// Gets or sets the content type to send back to a client.
|
||||
MediaType contentType = new MediaType('text', 'plain');
|
||||
MediaType get contentType {
|
||||
try {
|
||||
return new MediaType.parse(headers['content-type']);
|
||||
} catch (_) {
|
||||
return new MediaType('text', 'plain');
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets or sets the content type to send back to a client.
|
||||
void set contentType(MediaType value) {
|
||||
headers['content-type'] = value.toString();
|
||||
}
|
||||
|
||||
static StateError closed() =>
|
||||
new StateError('Cannot modify a closed response.');
|
||||
|
@ -124,8 +138,6 @@ abstract class ResponseContext<RawResponse>
|
|||
}
|
||||
|
||||
/// 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() {
|
||||
if (buffer is LockableBytesBuilder) {
|
||||
(buffer as LockableBytesBuilder).lock();
|
||||
|
@ -145,21 +157,21 @@ abstract class ResponseContext<RawResponse>
|
|||
/// You can override the [contentType] sent; by default it is `application/javascript`.
|
||||
void jsonp(value, {String callbackName: "callback", MediaType contentType}) {
|
||||
if (!isOpen) throw closed();
|
||||
write("$callbackName(${serializer(value)})");
|
||||
this.contentType =
|
||||
contentType ?? new MediaType('application', 'javascript');
|
||||
write("$callbackName(${serializer(value)})");
|
||||
close();
|
||||
}
|
||||
|
||||
/// Renders a view to the response stream, and closes the response.
|
||||
Future render(String view, [Map<String, dynamic> data]) {
|
||||
if (!isOpen) throw closed();
|
||||
contentType = new MediaType('text', 'html');
|
||||
return Future<String>.sync(() => app.viewGenerator(
|
||||
view,
|
||||
new Map<String, dynamic>.from(renderParams)
|
||||
..addAll(data ?? <String, dynamic>{}))).then((content) {
|
||||
write(content);
|
||||
contentType = new MediaType('text', 'html');
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
@ -296,8 +308,6 @@ abstract class ResponseContext<RawResponse>
|
|||
|
||||
/// 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
|
||||
|
@ -305,7 +315,9 @@ abstract class ResponseContext<RawResponse>
|
|||
|
||||
@override
|
||||
void addError(Object error, [StackTrace stackTrace]) {
|
||||
if (_done?.isCompleted == false) _done.completeError(error, stackTrace);
|
||||
if (_done?.isCompleted == false)
|
||||
_done.completeError(error, stackTrace);
|
||||
else if (_done == null) Zone.current.handleUncaughtError(error, stackTrace);
|
||||
}
|
||||
|
||||
/// Writes data to the response.
|
||||
|
|
|
@ -73,6 +73,12 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
|||
void addCookies(HttpResponse response, Iterable<Cookie> cookies) =>
|
||||
response.cookies.addAll(cookies);
|
||||
|
||||
@override
|
||||
Future<HttpServer> close() async {
|
||||
await server?.close();
|
||||
return await super.close();
|
||||
}
|
||||
|
||||
@override
|
||||
Future closeResponse(HttpResponse response) => response.close();
|
||||
|
||||
|
@ -95,11 +101,9 @@ class AngelHttp extends Driver<HttpRequest, HttpResponse, HttpServer,
|
|||
}
|
||||
|
||||
@override
|
||||
HttpResponse createResponseFromRawRequest(HttpRequest request) =>
|
||||
request.response;
|
||||
|
||||
@override
|
||||
Uri getUriFromRequest(HttpRequest request) => request.uri;
|
||||
Stream<HttpResponse> createResponseStreamFromRawRequest(
|
||||
HttpRequest request) =>
|
||||
new Stream.fromIterable([request.response]);
|
||||
|
||||
@override
|
||||
void setChunkedEncoding(HttpResponse response, bool value) =>
|
||||
|
|
|
@ -70,15 +70,6 @@ class HttpRequestContext extends RequestContext<HttpRequest> {
|
|||
return rawRequest.uri;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get xhr {
|
||||
return rawRequest.headers
|
||||
.value("X-Requested-With")
|
||||
?.trim()
|
||||
?.toLowerCase() ==
|
||||
'xmlhttprequest';
|
||||
}
|
||||
|
||||
/// Magically transforms an [HttpRequest] into a [RequestContext].
|
||||
static Future<HttpRequestContext> from(
|
||||
HttpRequest request, Angel app, String path) {
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
import '../core/core.dart';
|
||||
import 'http_request_context.dart';
|
||||
|
||||
|
@ -68,6 +70,14 @@ class HttpResponseContext extends ResponseContext<HttpResponse> {
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void set contentType(MediaType value) {
|
||||
super.contentType = value;
|
||||
if (!_streamInitialized)
|
||||
rawResponse.headers.contentType =
|
||||
ContentType(value.type, value.subtype, parameters: value.parameters);
|
||||
}
|
||||
|
||||
bool _openStream() {
|
||||
if (!_streamInitialized) {
|
||||
// If this is the first stream added to this response,
|
||||
|
|
|
@ -2,34 +2,32 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart' hide Header;
|
||||
import 'package:combinator/combinator.dart';
|
||||
import 'package:http2/src/artificial_server_socket.dart';
|
||||
import 'package:http2/transport.dart';
|
||||
import 'package:mock_request/mock_request.dart';
|
||||
import 'http2_request_context.dart';
|
||||
import 'http2_response_context.dart';
|
||||
import 'package:pool/pool.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
||||
ArtificialServerSocket, Http2RequestContext, Http2ResponseContext> {
|
||||
SecureServerSocket, Http2RequestContext, Http2ResponseContext> {
|
||||
final ServerSettings settings;
|
||||
final StreamController<HttpRequest> _onHttp1 = new StreamController();
|
||||
final Map<String, MockHttpSession> _sessions = {};
|
||||
final Uuid _uuid = new Uuid();
|
||||
ArtificialServerSocket _artificial;
|
||||
HttpServer _httpServer;
|
||||
StreamController<SecureSocket> _http1;
|
||||
SecureServerSocket _socket;
|
||||
StreamSubscription _sub;
|
||||
_AngelHttp2ServerSocket _artificial;
|
||||
|
||||
SecureServerSocket get socket => _artificial;
|
||||
|
||||
AngelHttp2._(
|
||||
Angel app,
|
||||
Future<ArtificialServerSocket> Function(dynamic, int) serverGenerator,
|
||||
Future<SecureServerSocket> Function(dynamic, int) serverGenerator,
|
||||
bool useZone,
|
||||
this.settings)
|
||||
: super(app, serverGenerator, useZone: useZone);
|
||||
: super(
|
||||
app,
|
||||
serverGenerator,
|
||||
useZone: useZone,
|
||||
);
|
||||
|
||||
factory AngelHttp2(Angel app, SecurityContext securityContext,
|
||||
{bool useZone: true, ServerSettings settings}) {
|
||||
|
@ -48,14 +46,25 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
var addr = address is InternetAddress
|
||||
? address
|
||||
: new InternetAddress(address.toString());
|
||||
return SecureServerSocket.bind(addr, port, ctx)
|
||||
.then((s) => ArtificialServerSocket(addr, port, s));
|
||||
return SecureServerSocket.bind(addr, port, ctx);
|
||||
}, useZone, settings);
|
||||
}
|
||||
|
||||
/// Fires when an HTTP/1.x request is received.
|
||||
Stream<HttpRequest> get onHttp1 => _onHttp1.stream;
|
||||
|
||||
@override
|
||||
Future<SecureServerSocket> generateServer([address, int port]) async {
|
||||
var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
|
||||
return _artificial = new _AngelHttp2ServerSocket(s, this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SecureServerSocket> close() async {
|
||||
await _artificial.close();
|
||||
return await super.close();
|
||||
}
|
||||
|
||||
@override
|
||||
void addCookies(ServerTransportStream response, Iterable<Cookie> cookies) {
|
||||
var headers = cookies
|
||||
|
@ -84,14 +93,11 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
}
|
||||
|
||||
@override
|
||||
ServerTransportStream createResponseFromRawRequest(Socket request) {
|
||||
Stream<ServerTransportStream> createResponseStreamFromRawRequest(
|
||||
Socket request) {
|
||||
var connection =
|
||||
new ServerTransportConnection.viaSocket(request, settings: settings);
|
||||
}
|
||||
|
||||
@override
|
||||
Uri getUriFromRequest(Socket request) {
|
||||
// TODO: implement getUriFromRequest
|
||||
return connection.incomingStreams;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -130,3 +136,85 @@ class AngelHttp2 extends Driver<Socket, ServerTransportStream,
|
|||
response.sendData(data);
|
||||
}
|
||||
}
|
||||
|
||||
class _FakeServerSocket extends Stream<Socket> implements ServerSocket {
|
||||
final _AngelHttp2ServerSocket angel;
|
||||
final _ctrl = new StreamController<Socket>();
|
||||
|
||||
_FakeServerSocket(this.angel);
|
||||
|
||||
@override
|
||||
InternetAddress get address => angel.address;
|
||||
|
||||
@override
|
||||
Future<ServerSocket> close() async {
|
||||
_ctrl.close();
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
int get port => angel.port;
|
||||
|
||||
@override
|
||||
StreamSubscription<Socket> listen(void Function(Socket event) onData,
|
||||
{Function onError, void Function() onDone, bool cancelOnError}) {
|
||||
return _ctrl.stream.listen(onData,
|
||||
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
|
||||
}
|
||||
}
|
||||
|
||||
class _AngelHttp2ServerSocket extends Stream<SecureSocket>
|
||||
implements SecureServerSocket {
|
||||
final SecureServerSocket socket;
|
||||
final AngelHttp2 driver;
|
||||
final _ctrl = new StreamController<SecureSocket>();
|
||||
_FakeServerSocket _fake;
|
||||
StreamSubscription _sub;
|
||||
|
||||
_AngelHttp2ServerSocket(this.socket, this.driver) {
|
||||
_fake = new _FakeServerSocket(this);
|
||||
new HttpServer.listenOn(_fake).pipe(driver._onHttp1);
|
||||
_sub = socket.listen(
|
||||
(socket) {
|
||||
if (socket.selectedProtocol == null ||
|
||||
socket.selectedProtocol == 'http/1.0' ||
|
||||
socket.selectedProtocol == 'http/1.1') {
|
||||
_fake._ctrl.add(socket);
|
||||
} else if (socket.selectedProtocol == 'h2' ||
|
||||
socket.selectedProtocol == 'h2-14') {
|
||||
_ctrl.add(socket);
|
||||
} else {
|
||||
socket.destroy();
|
||||
throw new Exception(
|
||||
'AngelHttp2 does not support ${socket.selectedProtocol} as an ALPN protocol.');
|
||||
}
|
||||
},
|
||||
onDone: _ctrl.close,
|
||||
onError: (e, st) {
|
||||
driver.app.logger.warning(
|
||||
'HTTP/2 incoming connection failure: ', e, st as StackTrace);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
InternetAddress get address => socket.address;
|
||||
|
||||
int get port => socket.port;
|
||||
|
||||
Future<SecureServerSocket> close() async {
|
||||
_sub?.cancel();
|
||||
_fake.close();
|
||||
_ctrl.close();
|
||||
return await socket.close();
|
||||
}
|
||||
|
||||
@override
|
||||
StreamSubscription<SecureSocket> listen(
|
||||
void Function(SecureSocket event) onData,
|
||||
{Function onError,
|
||||
void Function() onDone,
|
||||
bool cancelOnError}) {
|
||||
return _ctrl.stream.listen(onData,
|
||||
cancelOnError: cancelOnError, onError: onError, onDone: onDone);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_container/src/container.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:body_parser/body_parser.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
@ -11,9 +12,9 @@ import 'package:uuid/uuid.dart';
|
|||
final RegExp _comma = new RegExp(r',\s*');
|
||||
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||
|
||||
class Http2RequestContext extends RequestContext {
|
||||
class Http2RequestContext extends RequestContext<ServerTransportStream> {
|
||||
final Container container;
|
||||
BytesBuilder _buf;
|
||||
ContentType _contentType;
|
||||
List<Cookie> _cookies;
|
||||
HttpHeaders _headers;
|
||||
String _method, _override, _path;
|
||||
|
@ -22,13 +23,15 @@ class Http2RequestContext extends RequestContext {
|
|||
ServerTransportStream _stream;
|
||||
Uri _uri;
|
||||
|
||||
Http2RequestContext._(this.container);
|
||||
|
||||
static Future<Http2RequestContext> from(
|
||||
ServerTransportStream stream,
|
||||
Socket socket,
|
||||
Angel app,
|
||||
Map<String, MockHttpSession> sessions,
|
||||
Uuid uuid) async {
|
||||
var req = new Http2RequestContext()
|
||||
var req = new Http2RequestContext._(app.container.createChild())
|
||||
..app = app
|
||||
.._socket = socket
|
||||
.._stream = stream;
|
||||
|
@ -93,7 +96,7 @@ class Http2RequestContext extends RequestContext {
|
|||
cookies.firstWhere((c) => c.name == 'DARTSESSID', orElse: () => null);
|
||||
|
||||
if (dartSessId == null) {
|
||||
dartSessId = new Cookie('DARTSESSID', uuid.v4());
|
||||
dartSessId = new Cookie('DARTSESSID', uuid.v4() as String);
|
||||
}
|
||||
|
||||
req._session = sessions.putIfAbsent(
|
||||
|
@ -110,12 +113,6 @@ class Http2RequestContext extends RequestContext {
|
|||
/// The underlying HTTP/2 [ServerTransportStream].
|
||||
ServerTransportStream get stream => _stream;
|
||||
|
||||
@override
|
||||
bool get xhr {
|
||||
return headers.value("X-Requested-With")?.trim()?.toLowerCase() ==
|
||||
'xmlhttprequest';
|
||||
}
|
||||
|
||||
@override
|
||||
Uri get uri => _uri;
|
||||
|
||||
|
@ -132,12 +129,6 @@ class Http2RequestContext extends RequestContext {
|
|||
return _path;
|
||||
}
|
||||
|
||||
@override
|
||||
ContentType get contentType =>
|
||||
_contentType ??= (headers['content-type'] == null
|
||||
? null
|
||||
: ContentType.parse(headers.value('content-type')));
|
||||
|
||||
@override
|
||||
String get originalMethod {
|
||||
return _method;
|
||||
|
@ -148,9 +139,6 @@ class Http2RequestContext extends RequestContext {
|
|||
return _override ?? _method;
|
||||
}
|
||||
|
||||
@override
|
||||
HttpRequest get io => null;
|
||||
|
||||
@override
|
||||
String get hostname => _headers.value('host');
|
||||
|
||||
|
@ -168,7 +156,10 @@ class Http2RequestContext extends RequestContext {
|
|||
new Stream.fromIterable([_buf.takeBytes()]),
|
||||
contentType == null ? null : new MediaType.parse(contentType.toString()),
|
||||
uri,
|
||||
storeOriginalBuffer: app.storeOriginalBuffer,
|
||||
storeOriginalBuffer: app.keepRawRequestBuffers == true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ServerTransportStream get rawRequest => _stream;
|
||||
}
|
||||
|
|
|
@ -5,11 +5,21 @@ import 'package:angel_framework/angel_framework.dart' hide Header;
|
|||
import 'package:http2/transport.dart';
|
||||
import 'http2_request_context.dart';
|
||||
|
||||
class Http2ResponseContext extends ResponseContext {
|
||||
class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
|
||||
final Angel app;
|
||||
final ServerTransportStream stream;
|
||||
|
||||
ServerTransportStream get rawResponse => stream;
|
||||
|
||||
LockableBytesBuilder _buffer;
|
||||
|
||||
final Http2RequestContext _req;
|
||||
bool _useStream = false, _isClosed = false, _isPush = false;
|
||||
|
||||
bool _isDetached = false,
|
||||
_isClosed = false,
|
||||
_streamInitialized = false,
|
||||
_isPush = false;
|
||||
|
||||
Uri _targetUri;
|
||||
|
||||
Http2ResponseContext(this.app, this.stream, this._req) {
|
||||
|
@ -26,46 +36,65 @@ class Http2ResponseContext extends ResponseContext {
|
|||
/// Returns a [List] of all resources that have [push]ed to the client.
|
||||
List<Http2ResponseContext> get pushes => new List.unmodifiable(_pushes);
|
||||
|
||||
@override
|
||||
ServerTransportStream detach() {
|
||||
_isDetached = true;
|
||||
return stream;
|
||||
}
|
||||
|
||||
@override
|
||||
RequestContext get correspondingRequest => _req;
|
||||
|
||||
Uri get targetUri => _targetUri;
|
||||
|
||||
@override
|
||||
HttpResponse get io => null;
|
||||
bool get isOpen {
|
||||
return !_isClosed && !_isDetached;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get streaming => _useStream;
|
||||
bool get isBuffered => _buffer != null;
|
||||
|
||||
@override
|
||||
bool get isOpen => !_isClosed;
|
||||
BytesBuilder get buffer => _buffer;
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace stackTrace]) {
|
||||
super.addError(error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void useBuffer() {
|
||||
_buffer = new LockableBytesBuilder();
|
||||
}
|
||||
|
||||
/// Write headers, status, etc. to the underlying [stream].
|
||||
void finalize() {
|
||||
if (_isPush) return;
|
||||
bool _openStream() {
|
||||
if (_isPush || _streamInitialized) return false;
|
||||
|
||||
var headers = <Header>[
|
||||
new Header.ascii(':status', statusCode.toString()),
|
||||
];
|
||||
|
||||
if (encoders.isNotEmpty && correspondingRequest != null) {
|
||||
var allowedEncodings =
|
||||
(correspondingRequest.headers['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) {
|
||||
if (_allowedEncodings != null) {
|
||||
for (var encodingName in _allowedEncodings) {
|
||||
Converter<List<int>, List<int>> encoder;
|
||||
String key = encodingName;
|
||||
|
||||
if (encoders.containsKey(encodingName)) {
|
||||
if (encoders.containsKey(encodingName))
|
||||
encoder = encoders[encodingName];
|
||||
else if (encodingName == '*') {
|
||||
encoder = encoders[key = encoders.keys.first];
|
||||
}
|
||||
|
||||
if (encoder != null) {
|
||||
this.headers['content-encoding'] = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all normal headers
|
||||
for (var key in this.headers.keys) {
|
||||
|
@ -81,54 +110,35 @@ class Http2ResponseContext extends ResponseContext {
|
|||
}
|
||||
|
||||
stream.sendHeaders(headers);
|
||||
return _streamInitialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace stackTrace]) {
|
||||
Zone.current.handleUncaughtError(error, stackTrace);
|
||||
super.addError(error, stackTrace);
|
||||
}
|
||||
Iterable<String> __allowedEncodings;
|
||||
|
||||
@override
|
||||
bool useStream() {
|
||||
if (!_useStream) {
|
||||
// If this is the first stream added to this response,
|
||||
// then add headers, status code, etc.
|
||||
finalize();
|
||||
|
||||
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 ((firstStream || !headers.containsKey('content-encoding')) &&
|
||||
encoders.isNotEmpty &&
|
||||
correspondingRequest != null) {
|
||||
var allowedEncodings =
|
||||
(correspondingRequest.headers['accept-encoding'] ?? []).map((str) {
|
||||
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];
|
||||
});
|
||||
}
|
||||
|
||||
for (var encodingName in allowedEncodings) {
|
||||
@override
|
||||
Future addStream(Stream<List<int>> stream) {
|
||||
if (!isOpen && 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;
|
||||
|
||||
|
@ -139,58 +149,62 @@ class Http2ResponseContext extends ResponseContext {
|
|||
}
|
||||
|
||||
if (encoder != null) {
|
||||
/*
|
||||
if (firstStream) {
|
||||
this.stream.sendHeaders([
|
||||
new Header.ascii(
|
||||
'content-encoding', headers['content-encoding'] = key)
|
||||
]);
|
||||
}
|
||||
*/
|
||||
|
||||
output = encoders[key].bind(output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output.forEach(this.stream.sendData);
|
||||
}
|
||||
|
||||
@override
|
||||
void add(List<int> data) {
|
||||
if (_isClosed && !_useStream)
|
||||
if (!isOpen && isBuffered)
|
||||
throw ResponseContext.closed();
|
||||
else if (_useStream)
|
||||
//stream.sendData(data);
|
||||
addStream(new Stream.fromIterable([data]));
|
||||
else
|
||||
else if (!isBuffered) {
|
||||
_openStream();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.sendData(data);
|
||||
} else
|
||||
buffer.add(data);
|
||||
}
|
||||
|
||||
@override
|
||||
Future close() async {
|
||||
if (_useStream) {
|
||||
try {
|
||||
if (!_isDetached && !_isClosed && !isBuffered) {
|
||||
_openStream();
|
||||
await stream.outgoingMessages.close();
|
||||
} catch (_) {
|
||||
// This only seems to occur on `MockHttpRequest`, but
|
||||
// this try/catch prevents a crash.
|
||||
}
|
||||
}
|
||||
|
||||
_isClosed = true;
|
||||
await super.close();
|
||||
_useStream = false;
|
||||
}
|
||||
|
||||
/// Pushes a resource to the client.
|
||||
Http2ResponseContext push(String path,
|
||||
{Map<String, String> headers: const {}, String method: 'GET'}) {
|
||||
if (isOpen)
|
||||
throw new StateError(
|
||||
'You can only push resources after the main response context is closed. You will need to use streaming methods, i.e. `addStream`.');
|
||||
|
||||
var targetUri = _req.uri.replace(path: path);
|
||||
|
||||
var h = <Header>[
|
||||
|
@ -211,8 +225,4 @@ class Http2ResponseContext extends ResponseContext {
|
|||
_pushes.add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
void internalReopen() {
|
||||
_isClosed = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
name: angel_framework
|
||||
version: 2.0.0-alpha.10
|
||||
description: >
|
||||
A high-powered HTTP server with DI, routing and more.
|
||||
When combined with the other packages in the Angel ecosystem, this
|
||||
package can be used to make robust application servers for API's,
|
||||
traditional server-side applications, and much more, along with
|
||||
a rich client API.
|
||||
version: 2.0.0-alpha.11
|
||||
description: A high-powered HTTP server with dependency injection, routing and much more.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_framework
|
||||
environment:
|
||||
|
|
|
@ -64,5 +64,5 @@ Future<RequestContext> acceptContentTypes(
|
|||
rq.close();
|
||||
var app = new Angel(reflector: MirrorsReflector());
|
||||
var http = new AngelHttp(app);
|
||||
return http.createRequestContext(rq);
|
||||
return http.createRequestContext(rq, rq.response);
|
||||
}
|
||||
|
|
|
@ -38,12 +38,14 @@ class NamedController extends Controller {
|
|||
optional() => 2;
|
||||
}
|
||||
|
||||
void foo(RequestContext req, ResponseContext res) {
|
||||
bool foo(RequestContext req, ResponseContext res) {
|
||||
res.write("Hello, ");
|
||||
return true;
|
||||
}
|
||||
|
||||
void bar(RequestContext req, ResponseContext res) {
|
||||
bool bar(RequestContext req, ResponseContext res) {
|
||||
res.write("world!");
|
||||
return true;
|
||||
}
|
||||
|
||||
main() {
|
||||
|
|
|
@ -67,7 +67,7 @@ void encodingTests(Angel getApp()) {
|
|||
await http.handleRequest(rq);
|
||||
|
||||
var body = await getBody(rs);
|
||||
print(rs.headers);
|
||||
//print(rs.headers);
|
||||
expect(rs.headers.value('content-encoding'), 'deflate');
|
||||
expect(body, zlib.encode(utf8.encode('Hello, world!')));
|
||||
});
|
||||
|
|
|
@ -28,5 +28,5 @@ Future<RequestContext> makeRequest(String path) {
|
|||
var rq = new MockHttpRequest('GET', ENDPOINT.replace(path: path))..close();
|
||||
var app = new Angel(reflector: MirrorsReflector());
|
||||
var http = new AngelHttp(app);
|
||||
return http.createRequestContext(rq);
|
||||
return http.createRequestContext(rq, rq.response);
|
||||
}
|
||||
|
|
|
@ -29,10 +29,9 @@ void main() {
|
|||
..keepRawRequestBuffers = true
|
||||
..encoders['gzip'] = gzip.encoder;
|
||||
|
||||
app.get('/', (req, res) {
|
||||
res
|
||||
..write('Hello world')
|
||||
..close();
|
||||
app.get('/', (req, res) async {
|
||||
res.write('Hello world');
|
||||
await res.close();
|
||||
});
|
||||
|
||||
app.all('/method', (req, res) => req.method);
|
||||
|
@ -41,16 +40,14 @@ void main() {
|
|||
|
||||
app.get('/stream', (req, res) => jfkStream().pipe(res));
|
||||
|
||||
app.get('/headers', (req, res) {
|
||||
res
|
||||
..headers.addAll({'foo': 'bar', 'x-angel': 'http2'})
|
||||
..close();
|
||||
app.get('/headers', (req, res) async {
|
||||
res.headers.addAll({'foo': 'bar', 'x-angel': 'http2'});
|
||||
await res.close();
|
||||
});
|
||||
|
||||
app.get('/status', (req, res) {
|
||||
res
|
||||
..statusCode = 1337
|
||||
..close();
|
||||
app.get('/status', (req, res) async {
|
||||
res.statusCode = 1337;
|
||||
await res.close();
|
||||
});
|
||||
|
||||
app.post('/body', (req, res) => req.parseBody());
|
||||
|
@ -63,9 +60,7 @@ void main() {
|
|||
});
|
||||
|
||||
app.get('/push', (req, res) async {
|
||||
res
|
||||
..write('ok')
|
||||
..close();
|
||||
res.write('ok');
|
||||
|
||||
if (res is Http2ResponseContext && res.canPush) {
|
||||
res.push('a')
|
||||
|
@ -76,6 +71,8 @@ void main() {
|
|||
..write('b')
|
||||
..close();
|
||||
}
|
||||
|
||||
await res.close();
|
||||
});
|
||||
|
||||
var ctx = new SecurityContext()
|
||||
|
@ -211,7 +208,6 @@ void main() {
|
|||
expect(getPath(pushA), '/a');
|
||||
expect(getPath(pushB), '/b');
|
||||
|
||||
// TODO: Dart http/2 client seems to not be able to get body
|
||||
// However, Chrome, Firefox, Edge all can
|
||||
//expect(await getBody(pushA.stream), 'a');
|
||||
//expect(await getBody(pushB.stream), 'b');
|
||||
|
|
|
@ -62,8 +62,8 @@ main() {
|
|||
try {
|
||||
var rq = new MockHttpRequest('GET', Uri.parse('/num/unparsed/32'))
|
||||
..close();
|
||||
var req = await http.createRequestContext(rq);
|
||||
var res = await http.createResponseContext(rq.response, req);
|
||||
var req = await http.createRequestContext(rq, rq.response);
|
||||
var res = await http.createResponseContext(rq, rq.response, req);
|
||||
await app.runContained((num unparsed) => unparsed, req, res);
|
||||
throw new StateError(
|
||||
'ArgumentError should be thrown if a parameter cannot be resolved.');
|
||||
|
|
|
@ -29,8 +29,9 @@ void interceptor(RequestContext req, ResponseContext res) {
|
|||
..close();
|
||||
}
|
||||
|
||||
void interceptService(RequestContext req, ResponseContext res) {
|
||||
bool interceptService(RequestContext req, ResponseContext res) {
|
||||
res.write("Service with ");
|
||||
return true;
|
||||
}
|
||||
|
||||
main() {
|
||||
|
@ -96,6 +97,7 @@ main() {
|
|||
RequestHandler write(String message) {
|
||||
return (req, res) {
|
||||
res.write(message);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -104,7 +106,7 @@ main() {
|
|||
|
||||
app.fallback((req, res) => 'MJ');
|
||||
|
||||
app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
|
||||
//app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true);
|
||||
|
||||
client = new http.Client();
|
||||
var server = await new AngelHttp(app).startServer('127.0.0.1', 0);
|
||||
|
|
|
@ -44,12 +44,12 @@ main() {
|
|||
var rq = new MockHttpRequest('GET', $foo);
|
||||
rq.close();
|
||||
var rs = rq.response;
|
||||
var req = await http.createRequestContext(rq);
|
||||
var res = await http.createResponseContext(rs);
|
||||
var req = await http.createRequestContext(rq, rs);
|
||||
var res = await http.createResponseContext(rq, rs);
|
||||
var e = new AngelHttpException(null,
|
||||
statusCode: 321, message: 'Hello', errors: ['foo', 'bar']);
|
||||
await app.errorHandler(e, req, res);
|
||||
await http.sendResponse(rq, req, res);
|
||||
await http.sendResponse(rq, rs, req, res);
|
||||
expect(
|
||||
ContentType.parse(rs.headers.value('content-type')).mimeType,
|
||||
'text/html',
|
||||
|
@ -120,8 +120,8 @@ main() {
|
|||
|
||||
setUp(() async {
|
||||
var rq = new MockHttpRequest('GET', $foo)..close();
|
||||
req = await http.createRequestContext(rq);
|
||||
res = await http.createResponseContext(rq.response);
|
||||
req = await http.createRequestContext(rq, rq.response);
|
||||
res = await http.createResponseContext(rq, rq.response);
|
||||
});
|
||||
|
||||
group('getHandlerResult', () {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
pub run test
|
||||
ANGEL_ENV=production pub run test
|
||||
pub run test --timeout 5s
|
||||
ANGEL_ENV=production pub run test --timeout 5s
|
Loading…
Reference in a new issue