2017-06-12 23:53:08 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
2018-11-11 17:35:37 +00:00
|
|
|
import 'package:angel_framework/http.dart';
|
|
|
|
import 'package:angel_framework/http2.dart';
|
|
|
|
import 'package:path/path.dart' as p;
|
2017-06-12 23:53:08 +00:00
|
|
|
import 'package:shelf/shelf.dart' as shelf;
|
2018-11-11 17:35:37 +00:00
|
|
|
import 'package:stream_channel/stream_channel.dart';
|
2017-06-12 23:53:08 +00:00
|
|
|
|
2018-11-11 17:35:37 +00:00
|
|
|
/// Creates a [shelf.Request] analogous to the input [req].
|
2017-06-12 23:53:08 +00:00
|
|
|
///
|
2018-11-11 17:35:37 +00:00
|
|
|
/// The new request's `context` will contain [req.container] as `angel_shelf.container`, as well as
|
2017-06-12 23:53:08 +00:00
|
|
|
/// the provided [context], if any.
|
|
|
|
///
|
|
|
|
/// The context will also have the original request available as `angel_shelf.request`.
|
|
|
|
///
|
2018-11-11 17:35:37 +00:00
|
|
|
/// If you want to read the request body, you *must* set `keepRawRequestBuffers` to `true`
|
2017-06-12 23:53:08 +00:00
|
|
|
/// on your application instance.
|
2018-11-11 17:35:37 +00:00
|
|
|
Future<shelf.Request> convertRequest(RequestContext req, ResponseContext res,
|
2017-06-12 23:53:08 +00:00
|
|
|
{String handlerPath, Map<String, Object> context}) async {
|
2018-11-11 17:35:37 +00:00
|
|
|
var app = req.app;
|
2017-06-12 23:53:08 +00:00
|
|
|
var headers = <String, String>{};
|
2018-11-11 17:35:37 +00:00
|
|
|
req.headers.forEach((k, v) {
|
2017-06-12 23:53:08 +00:00
|
|
|
headers[k] = v.join(',');
|
|
|
|
});
|
|
|
|
|
2018-11-11 17:35:37 +00:00
|
|
|
headers.remove(HttpHeaders.transferEncodingHeader);
|
2017-06-12 23:53:08 +00:00
|
|
|
|
2018-11-11 17:35:37 +00:00
|
|
|
void Function(void Function(StreamChannel<List<int>>)) onHijack;
|
|
|
|
String protocolVersion;
|
|
|
|
Uri requestedUri;
|
|
|
|
|
|
|
|
if (req is HttpRequestContext && res is HttpResponseContext) {
|
|
|
|
protocolVersion = req.rawRequest.protocolVersion;
|
|
|
|
requestedUri = req.rawRequest.requestedUri;
|
|
|
|
|
|
|
|
onHijack = (void hijack(StreamChannel<List<int>> channel)) {
|
|
|
|
new Future(() async {
|
|
|
|
var rs = res.detach();
|
|
|
|
var socket = await rs.detachSocket(writeHeaders: false);
|
|
|
|
var ctrl = new StreamChannelController<List<int>>();
|
|
|
|
var body = await req.parseRawRequestBuffer() ?? [];
|
|
|
|
ctrl.local.sink.add(body ?? []);
|
|
|
|
socket.listen(ctrl.local.sink.add,
|
|
|
|
onError: ctrl.local.sink.addError, onDone: ctrl.local.sink.close);
|
|
|
|
ctrl.local.stream.pipe(socket);
|
|
|
|
hijack(ctrl.foreign);
|
|
|
|
}).catchError((e, st) {
|
|
|
|
app.logger?.severe('An error occurred while hijacking a shelf request',
|
|
|
|
e, st as StackTrace);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
} else if (req is Http2RequestContext && res is Http2ResponseContext) {
|
|
|
|
protocolVersion = '2.0';
|
|
|
|
requestedUri = req.uri;
|
|
|
|
|
|
|
|
onHijack = (void hijack(StreamChannel<List<int>> channel)) {
|
|
|
|
new Future(() async {
|
|
|
|
var rs = await res.detach();
|
|
|
|
var ctrl = new StreamChannelController<List<int>>();
|
|
|
|
var body = await req.parseRawRequestBuffer() ?? [];
|
|
|
|
ctrl.local.sink.add(body ?? []);
|
|
|
|
ctrl.local.stream.listen(rs.sendData, onDone: rs.terminate);
|
|
|
|
hijack(ctrl.foreign);
|
|
|
|
}).catchError((e, st) {
|
|
|
|
stderr.writeln('An error occurred while hijacking a shelf request: $e');
|
|
|
|
stderr.writeln(st);
|
2017-06-12 23:53:08 +00:00
|
|
|
});
|
2018-11-11 17:35:37 +00:00
|
|
|
};
|
|
|
|
} else {
|
|
|
|
throw new UnsupportedError(
|
|
|
|
'`embedShelf` is only supported for HTTP and HTTP2 requests in Angel.');
|
|
|
|
}
|
|
|
|
|
|
|
|
var url = req.uri;
|
|
|
|
|
|
|
|
if (p.isAbsolute(url.path)) {
|
|
|
|
url = url.replace(path: url.path.substring(1));
|
2017-06-12 23:53:08 +00:00
|
|
|
}
|
|
|
|
|
2018-11-11 17:35:37 +00:00
|
|
|
return new shelf.Request(req.method, requestedUri,
|
|
|
|
protocolVersion: protocolVersion,
|
2017-06-12 23:53:08 +00:00
|
|
|
headers: headers,
|
|
|
|
handlerPath: handlerPath,
|
2018-11-11 17:35:37 +00:00
|
|
|
url: url,
|
|
|
|
body: (await req.parseRawRequestBuffer()) ?? [],
|
|
|
|
context: {'angel_shelf.request': req}
|
|
|
|
..addAll({'angel_shelf.container': req.container})
|
2017-06-12 23:53:08 +00:00
|
|
|
..addAll(context ?? {}),
|
|
|
|
onHijack: onHijack);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Applies the state of the [shelfResponse] into the [angelResponse].
|
|
|
|
///
|
|
|
|
/// Merges all headers, sets the status code, and writes the body.
|
|
|
|
///
|
|
|
|
/// In addition, the response's context will be available in `angelResponse.properties`
|
|
|
|
/// as `shelf_context`.
|
|
|
|
Future mergeShelfResponse(
|
2018-01-09 14:44:59 +00:00
|
|
|
shelf.Response shelfResponse, ResponseContext angelResponse) {
|
2017-06-12 23:53:08 +00:00
|
|
|
angelResponse.headers.addAll(shelfResponse.headers);
|
|
|
|
angelResponse.statusCode = shelfResponse.statusCode;
|
|
|
|
angelResponse.properties['shelf_context'] = shelfResponse.context;
|
2017-06-20 16:23:10 +00:00
|
|
|
angelResponse.properties['shelf_response'] = shelfResponse;
|
2018-01-09 14:44:59 +00:00
|
|
|
return shelfResponse.read().pipe(angelResponse);
|
2017-06-12 23:53:08 +00:00
|
|
|
}
|