platform/lib/src/proxy_layer.dart

119 lines
3.3 KiB
Dart
Raw Normal View History

2016-11-23 22:03:06 +00:00
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?');
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
String _pathify(String path) {
var p = path.replaceAll(_straySlashes, '');
Map<String, String> replace = {};
for (Match match in _param.allMatches(p)) {
if (match[3] != null) replace[match[0]] = ':${match[1]}';
}
replace.forEach((k, v) {
p = p.replaceAll(k, v);
});
return p;
}
2017-01-09 02:11:20 +00:00
/// Copies HTTP headers ;)
void copyHeaders(HttpHeaders from, HttpHeaders to) {
2017-01-12 00:12:23 +00:00
from.forEach(to.add);
2017-01-09 02:11:20 +00:00
to
..chunkedTransferEncoding = from.chunkedTransferEncoding
..contentLength = from.contentLength
..contentType = from.contentType
..date = from.date
..expires = from.expires
..host = from.host
..ifModifiedSince = from.ifModifiedSince
..persistentConnection = from.persistentConnection
..port = from.port;
}
2016-11-23 22:03:06 +00:00
class ProxyLayer {
HttpClient _client;
String _prefix;
final bool debug;
final String host, mapTo, publicPath;
final int port;
2017-01-09 02:11:20 +00:00
final String protocol;
2016-11-23 22:03:06 +00:00
ProxyLayer(this.host, this.port,
{this.debug: false,
this.mapTo: '/',
this.publicPath: '/',
2017-01-09 02:11:20 +00:00
this.protocol: 'http',
2016-11-23 22:03:06 +00:00
SecurityContext securityContext}) {
_client = new HttpClient(context: securityContext);
_prefix = publicPath.replaceAll(_straySlashes, '');
}
call(AngelBase app) async => serve(app);
_printDebug(msg) {
if (debug == true) print(msg);
}
void close() => _client.close(force: true);
void serve(Router router) {
_printDebug('Public path prefix: "$_prefix"');
handler(RequestContext req, ResponseContext res) async {
var path = req.path.replaceAll(_straySlashes, '');
return serveFile(path, req, res);
}
router.get('$publicPath/*', handler);
}
serveFile(String path, RequestContext req, ResponseContext res) async {
var _path = path;
if (_prefix.isNotEmpty) {
_path = path.replaceAll(new RegExp('^' + _pathify(_prefix)), '');
}
res
..willCloseItself = true
..end();
// Create mapping
2017-01-12 00:12:23 +00:00
_printDebug('Serving path $_path via proxy');
2016-11-23 22:03:06 +00:00
final mapping = '$mapTo/$_path'.replaceAll(_straySlashes, '');
2017-01-12 00:12:23 +00:00
_printDebug('Mapped path $_path to path $mapping on proxy $host:$port');
2016-11-23 22:03:06 +00:00
final rq = await _client.open(req.method, host, port, mapping);
2017-01-12 00:12:23 +00:00
_printDebug('Opened client request');
2017-01-09 02:11:20 +00:00
rq.headers
2017-01-12 00:12:23 +00:00
..set('X-Forwarded-For', req.connectionInfo.remoteAddress.address)
..set('X-Forwarded-Port', req.connectionInfo.remotePort.toString())
..set('X-Forwarded-Host',
2017-01-09 02:11:20 +00:00
req.headers.host ?? req.headers.value(HttpHeaders.HOST) ?? 'none')
2017-01-12 00:12:23 +00:00
..set('X-Forwarded-Proto', protocol);
_printDebug('Added X-Forwarded headers');
copyHeaders(req.headers, rq.headers);
_printDebug('Copied headers');
rq.cookies.addAll(req.cookies ?? []);
_printDebug('Added cookies');
2017-01-09 02:11:20 +00:00
2016-11-23 22:03:06 +00:00
await rq.addStream(req.io);
final HttpClientResponse rs = await rq.close();
final HttpResponse r = res.io;
2017-01-12 00:12:23 +00:00
_printDebug(
'Proxy responded to $mapping with status code ${rs.statusCode}');
2016-11-23 22:03:06 +00:00
r.statusCode = rs.statusCode;
r.headers.contentType = rs.headers.contentType;
2017-01-12 00:12:23 +00:00
copyHeaders(rs.headers, r.headers);
2016-11-23 22:03:06 +00:00
await r.addStream(rs);
await r.flush();
await r.close();
}
}