Published proxy and file_service

This commit is contained in:
thomashii 2021-06-10 16:47:05 +08:00
parent e25b67d41f
commit 70c4882727
21 changed files with 177 additions and 138 deletions

View file

@ -2,7 +2,7 @@ name: angel3_auth
description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more.
version: 4.0.4
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth
publish_to: none
#publish_to: none
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:

View file

@ -0,0 +1,12 @@
Primary Authors
===============
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
Thomas is the current maintainer of the code base. He has refactored and migrated the
code base to support NNBD.
* __[Tobe O](thosakwe@gmail.com)__
Tobe has written much of the original code prior to NNBD migration. He has moved on and
is no longer involved with the project.

View file

@ -1,3 +1,9 @@
# 4.0.0
* Migrated to support Dart SDK 2.12.x NNBD
# 3.0.0
* Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.0.1
* Pass everything through `_jsonifyToSD` when returning responses.

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 The Angel Framework
Copyright (c) 2021 dukefirehawk.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,6 +1,9 @@
# file_service
[![Pub](https://img.shields.io/pub/v/angel_file_service.svg)](https://pub.dartlang.org/packages/angel_file_service)
[![build status](https://travis-ci.org/angel-dart/file_service.svg)](https://travis-ci.org/angel-dart/file_service)
# angel3_file_service
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_file_service)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/file_service/LICENSE)
Angel service that persists data to a file on disk, stored as a JSON list. It uses a simple
mutex to prevent race conditions, and caches contents in memory until changes

View file

@ -1,3 +1,4 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View file

@ -1,5 +1,5 @@
import 'package:angel_file_service/angel_file_service.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel3_file_service/angel3_file_service.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:file/local.dart';
configureServer(Angel app) async {

View file

@ -1,18 +1,18 @@
import 'dart:async';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:file/file.dart';
import 'package:pool/pool.dart';
/// Persists in-memory changes to a file on disk.
class JsonFileService extends Service<String, Map<String, dynamic>> {
FileStat _lastStat;
FileStat? _lastStat;
final Pool _mutex = new Pool(1);
MapService _store;
late MapService _store;
final File file;
JsonFileService(this.file,
{bool allowRemoveAll: false, bool allowQuery: true, MapService store}) {
{bool allowRemoveAll: false, bool allowQuery: true, MapService? store}) {
_store = store ??
new MapService(
allowRemoveAll: allowRemoveAll == true,
@ -32,7 +32,7 @@ class JsonFileService extends Service<String, Map<String, dynamic>> {
if (_lastStat == null ||
stat.modified.millisecondsSinceEpoch >
_lastStat.modified.millisecondsSinceEpoch) {
_lastStat!.modified.millisecondsSinceEpoch) {
_lastStat = stat;
var contents = await file.readAsString();
@ -59,18 +59,18 @@ class JsonFileService extends Service<String, Map<String, dynamic>> {
@override
Future<List<Map<String, dynamic>>> index(
[Map<String, dynamic> params]) async =>
[Map<String, dynamic>? params]) async =>
_load()
.then((_) => _store.index(params))
.then((it) => it.map(_jsonifyToSD).toList());
@override
Future<Map<String, dynamic>> read(id, [Map<String, dynamic> params]) =>
Future<Map<String, dynamic>> read(id, [Map<String, dynamic>? params]) =>
_load().then((_) => _store.read(id, params)).then(_jsonifyToSD);
@override
Future<Map<String, dynamic>> create(data,
[Map<String, dynamic> params]) async {
[Map<String, dynamic>? params]) async {
await _load();
var created = await _store.create(data, params).then(_jsonifyToSD);
await _save();
@ -78,7 +78,8 @@ class JsonFileService extends Service<String, Map<String, dynamic>> {
}
@override
Future<Map<String, dynamic>> remove(id, [Map<String, dynamic> params]) async {
Future<Map<String, dynamic>> remove(id,
[Map<String, dynamic>? params]) async {
await _load();
var r = await _store.remove(id, params).then(_jsonifyToSD);
await _save();
@ -87,7 +88,7 @@ class JsonFileService extends Service<String, Map<String, dynamic>> {
@override
Future<Map<String, dynamic>> update(id, data,
[Map<String, dynamic> params]) async {
[Map<String, dynamic>? params]) async {
await _load();
var r = await _store.update(id, data, params).then(_jsonifyToSD);
await _save();
@ -96,7 +97,7 @@ class JsonFileService extends Service<String, Map<String, dynamic>> {
@override
Future<Map<String, dynamic>> modify(id, data,
[Map<String, dynamic> params]) async {
[Map<String, dynamic>? params]) async {
await _load();
var r = await _store.update(id, data, params).then(_jsonifyToSD);
await _save();

View file

@ -1,18 +1,13 @@
name: angel_file_service
version: 3.0.0
name: angel3_file_service
version: 4.0.0
description: Angel service that persists data to a file on disk.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/file_service
publish_to: none
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/file_service
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'
dependencies:
angel_framework:
git:
url: https://github.com/dukefirehawk/angel.git
ref: sdk-2.12.x
path: packages/framework
file: ^6.1.0
pool: ^1.0.0
angel3_framework: ^4.0.0
file: ^6.1.1
pool: ^1.5.0
dev_dependencies:
test: ^1.16.5
test: ^1.17.7
pedantic: ^1.11.0

View file

@ -1,12 +1,12 @@
import 'package:angel_file_service/angel_file_service.dart';
import 'package:angel3_file_service/angel3_file_service.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:test/test.dart';
main() {
void main() {
MemoryFileSystem fs;
File dbFile;
JsonFileService service;
late JsonFileService service;
setUp(() async {
fs = new MemoryFileSystem();

12
packages/proxy/AUTHORS.md Normal file
View file

@ -0,0 +1,12 @@
Primary Authors
===============
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
Thomas is the current maintainer of the code base. He has refactored and migrated the
code base to support NNBD.
* __[Tobe O](thosakwe@gmail.com)__
Tobe has written much of the original code prior to NNBD migration. He has moved on and
is no longer involved with the project.

View file

@ -1,3 +1,9 @@
# 4.0.0
* Migrated to support Dart SDK 2.12.x NNBD
# 3.0.0
* Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.2.0
* Use `http.Client` instead of `http.BaseClient`, and make it an
optional parameter.

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016 The Angel Framework
Copyright (c) 2021 dukefirehawk.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,15 +1,18 @@
# proxy
[![Pub](https://img.shields.io/pub/v/angel_proxy.svg)](https://pub.dartlang.org/packages/angel_proxy)
[![build status](https://travis-ci.org/angel-dart/proxy.svg)](https://travis-ci.org/angel-dart/proxy)
# angel3_proxy
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_proxy)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy/LICENSE)
Angel middleware to forward requests to another server (i.e. `webdev serve`).
Also supports WebSockets.
```dart
import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel3_proxy/angel3_proxy.dart';
import 'package:http/http.dart' as http;
main() async {
void main() async {
// Forward requests instead of serving statically.
// You can also pass a URI, instead of a string.
var proxy1 = Proxy('http://localhost:3000');

View file

@ -1,12 +1,12 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:angel3_proxy/angel3_proxy.dart';
import 'package:logging/logging.dart';
final Duration timeout = Duration(seconds: 5);
main() async {
void main() async {
var app = Angel();
// Forward any /api requests to pub.
@ -16,7 +16,7 @@ main() async {
publicPath: '/pub',
timeout: timeout,
);
app.all("/pub/*", pubProxy.handleRequest);
app.all('/pub/*', pubProxy.handleRequest);
// Surprise! We can also proxy WebSockets.
//

View file

@ -1,3 +1,3 @@
library angel_proxy;
library angel3_proxy;
export 'src/proxy_layer.dart';

View file

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
@ -14,7 +14,7 @@ final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
///
/// Supports WebSockets, in addition to regular HTTP requests.
class Proxy {
String _prefix;
String? _prefix;
/// The underlying [Client] to use.
final http.Client httpClient;
@ -26,29 +26,31 @@ class Proxy {
final String publicPath;
/// If `null` then no timout is added for requests
final Duration timeout;
final Duration? timeout;
Proxy(
baseUrl, {
http.Client httpClient,
http.Client? httpClient,
this.publicPath = '/',
this.recoverFromDead = true,
this.recoverFrom404 = true,
this.timeout,
}) : this.baseUrl = baseUrl is Uri ? baseUrl : Uri.parse(baseUrl.toString()),
this.httpClient = httpClient ?? http.Client() {
}) : baseUrl = baseUrl is Uri ? baseUrl : Uri.parse(baseUrl.toString()),
httpClient = httpClient ?? http.Client() {
if (!this.baseUrl.hasScheme || !this.baseUrl.hasAuthority) {
throw ArgumentError(
'Invalid `baseUrl`. URI must have both a scheme and authority.');
}
if (this.recoverFromDead == null) {
throw ArgumentError.notNull("recoverFromDead");
/*
if (recoverFromDead == null) {
throw ArgumentError.notNull('recoverFromDead');
}
if (this.recoverFrom404 == null) {
throw ArgumentError.notNull("recoverFrom404");
if (recoverFrom404 == null) {
throw ArgumentError.notNull('recoverFrom404');
}
*/
_prefix = publicPath?.replaceAll(_straySlashes, '') ?? '';
_prefix = publicPath.replaceAll(_straySlashes, '');
}
void close() => httpClient.close();
@ -58,7 +60,7 @@ class Proxy {
/// You can also limit this functionality to specific values of the `Accept` header, ex. `text/html`.
/// If [accepts] is `null`, OR at least one of the content types in [accepts] is present,
/// the view will be served.
RequestHandler pushState(String path, {Iterable accepts}) {
RequestHandler pushState(String path, {Iterable? accepts}) {
var vPath = path.replaceAll(_straySlashes, '');
if (_prefix?.isNotEmpty == true) vPath = '$_prefix/$vPath';
@ -67,7 +69,7 @@ class Proxy {
if (path == vPath) return Future<bool>.value(true);
if (accepts?.isNotEmpty == true) {
if (!accepts.any((x) => req.accepts(x, strict: true))) {
if (!accepts!.any((x) => req.accepts(x, strict: true))) {
return Future<bool>.value(true);
}
}
@ -80,8 +82,8 @@ class Proxy {
Future<bool> handleRequest(RequestContext req, ResponseContext res) {
var path = req.path.replaceAll(_straySlashes, '');
if (_prefix.isNotEmpty) {
if (!p.isWithin(_prefix, path) && !p.equals(_prefix, path)) {
if (_prefix!.isNotEmpty) {
if (!p.isWithin(_prefix!, path) && !p.equals(_prefix!, path)) {
return Future<bool>.value(true);
}
@ -100,12 +102,12 @@ class Proxy {
try {
if (req is HttpRequestContext &&
WebSocketTransformer.isUpgradeRequest(req.rawRequest)) {
WebSocketTransformer.isUpgradeRequest(req.rawRequest!)) {
res.detach();
uri = uri.replace(scheme: uri.scheme == 'https' ? 'wss' : 'ws');
try {
var local = await WebSocketTransformer.upgrade(req.rawRequest);
var local = await WebSocketTransformer.upgrade(req.rawRequest!);
var remote = await WebSocket.connect(uri.toString());
scheduleMicrotask(() => local.pipe(remote));
@ -121,23 +123,23 @@ class Proxy {
var headers = <String, String>{
'host': uri.authority,
'x-forwarded-for': req.remoteAddress.address,
'x-forwarded-port': req.uri.port.toString(),
'x-forwarded-port': req.uri!.port.toString(),
'x-forwarded-host':
req.headers.host ?? req.headers.value('host') ?? 'none',
req.headers!.host ?? req.headers!.value('host') ?? 'none',
'x-forwarded-proto': uri.scheme,
};
req.headers.forEach((name, values) {
req.headers!.forEach((name, values) {
headers[name] = values.join(',');
});
headers[HttpHeaders.cookieHeader] =
req.cookies.map<String>((c) => '${c.name}=${c.value}').join('; ');
List<int> body;
List<int>? body;
if (!req.hasParsedBody) {
body = await req.body
body = await req.body!
.fold<BytesBuilder>(BytesBuilder(), (bb, buf) => bb..add(buf))
.then((bb) => bb.takeBytes());
}
@ -153,7 +155,7 @@ class Proxy {
}
var future = accessRemote();
if (timeout != null) future = future.timeout(timeout);
if (timeout != null) future = future.timeout(timeout!);
rs = await future;
} on TimeoutException catch (e, st) {
if (recoverFromDead) return true;
@ -163,7 +165,7 @@ class Proxy {
stackTrace: st,
statusCode: 504,
message:
'Connection to remote host "$uri" timed out after ${timeout.inMilliseconds}ms.',
'Connection to remote host "$uri" timed out after ${timeout!.inMilliseconds}ms.',
);
} catch (e) {
if (recoverFromDead && e is! AngelHttpException) return true;
@ -176,7 +178,7 @@ class Proxy {
MediaType mediaType;
if (rs.headers.containsKey(HttpHeaders.contentTypeHeader)) {
try {
mediaType = MediaType.parse(rs.headers[HttpHeaders.contentTypeHeader]);
mediaType = MediaType.parse(rs.headers[HttpHeaders.contentTypeHeader]!);
} on FormatException catch (e, st) {
if (recoverFromDead) return true;

View file

@ -1,21 +1,18 @@
name: angel_proxy
name: angel3_proxy
version: 4.0.0
description: Angel middleware to forward requests to another server (i.e. pub serve).
version: 2.2.0
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/proxy
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy
environment:
sdk: ">=2.10.0 <2.12.0"
sdk: '>=2.12.0 <3.0.0'
dependencies:
angel_framework: #^2.0.0-alpha
path: ../framework
http: ^0.12.0
http_parser: ^3.0.0
path: ^1.0.0
angel3_framework: ^4.0.0
http: ^0.13.3
http_parser: ^4.0.0
path: ^1.8.0
dev_dependencies:
angel_test: #^2.0.0-alpha
path: ../test
logging:
mock_request:
pedantic: ^1.0.0
test: ^1.15.7
angel3_test: ^4.0.0
angel3_mock_request: ^2.0.0
logging: ^1.0.1
pedantic: ^1.11.0
test: ^1.17.7

View file

@ -1,22 +1,23 @@
import 'dart:convert';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:angel3_proxy/angel3_proxy.dart';
import 'package:http/io_client.dart' as http;
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'common.dart';
void main() {
Angel app;
Angel? app;
var client = http.IOClient();
HttpServer server, testServer;
String url;
//late HttpServer server;
late HttpServer testServer;
String? url;
setUp(() async {
app = Angel();
var appHttp = AngelHttp(app);
var appHttp = AngelHttp(app!);
testServer = await startTestServer();
@ -32,15 +33,15 @@ void main() {
print('Proxy 1 on: ${proxy1.baseUrl}');
print('Proxy 2 on: ${proxy2.baseUrl}');
app.all('/proxy/*', proxy1.handleRequest);
app.all('*', proxy2.handleRequest);
app!.all('/proxy/*', proxy1.handleRequest);
app!.all('*', proxy2.handleRequest);
app.fallback((req, res) {
app!.fallback((req, res) {
print('Intercepting empty from ${req.uri}');
res.write('intercept empty');
});
app.logger = Logger('angel');
app!.logger = Logger('angel');
Logger.root.onRecord.listen((rec) {
print(rec);
@ -53,32 +54,32 @@ void main() {
});
tearDown(() async {
await testServer?.close(force: true);
await server?.close(force: true);
await testServer.close(force: true);
//await server.close(force: true);
app = null;
url = null;
});
test('publicPath', () async {
final response = await client.get('$url/proxy/hello');
final response = await client.get(Uri.parse('$url/proxy/hello'));
print('Response: ${response.body}');
expect(response.body, equals('world'));
});
test('empty', () async {
var response = await client.get('$url/proxy/empty');
var response = await client.get(Uri.parse('$url/proxy/empty'));
print('Response: ${response.body}');
expect(response.body, 'intercept empty');
});
test('mapTo', () async {
final response = await client.get('$url/bar');
final response = await client.get(Uri.parse('$url/bar'));
print('Response: ${response.body}');
expect(response.body, equals('baz'));
});
test('original buffer', () async {
var response = await client.post('$url/proxy/body',
var response = await client.post(Uri.parse('$url/proxy/body'),
body: json.encode({'foo': 'bar'}),
headers: {'content-type': 'application/json'});
print('Response: ${response.body}');

View file

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:logging/logging.dart';
Future<HttpServer> startTestServer() {
@ -12,7 +12,7 @@ Future<HttpServer> startTestServer() {
app.get('/foo/bar', (req, res) => res.write('baz'));
app.post('/body', (RequestContext req, res) async {
var body = await req.parseBody().then((_) => req.bodyAsMap);
app.logger.info('Body: $body');
app.logger!.info('Body: $body');
return body;
});

View file

@ -1,59 +1,59 @@
import 'dart:convert';
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:angel_proxy/angel_proxy.dart';
import 'package:angel_test/angel_test.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:angel3_proxy/angel3_proxy.dart';
import 'package:angel3_test/angel3_test.dart';
import 'package:logging/logging.dart';
import 'package:mock_request/mock_request.dart';
import 'package:angel3_mock_request/angel3_mock_request.dart';
import 'package:test/test.dart';
main() {
Angel app, testApp;
TestClient client;
Proxy layer;
void main() {
Angel? app, testApp;
late TestClient client;
late Proxy layer;
setUp(() async {
testApp = Angel();
testApp.get('/foo', (req, res) async {
testApp!.get('/foo', (req, res) async {
res.useBuffer();
res.write('pub serve');
});
testApp.get('/empty', (req, res) => res.close());
testApp!.get('/empty', (req, res) => res.close());
testApp.responseFinalizers.add((req, res) async {
print('OUTGOING: ' + String.fromCharCodes(res.buffer.toBytes()));
testApp!.responseFinalizers.add((req, res) async {
print('OUTGOING: ' + String.fromCharCodes(res.buffer!.toBytes()));
});
testApp.encoders.addAll({'gzip': gzip.encoder});
testApp!.encoders.addAll({'gzip': gzip.encoder});
var server = await AngelHttp(testApp).startServer();
var server = await AngelHttp(testApp!).startServer();
app = Angel();
app.fallback((req, res) {
app!.fallback((req, res) {
res.useBuffer();
return true;
});
app.get('/bar', (req, res) => res.write('normal'));
app!.get('/bar', (req, res) => res.write('normal'));
layer = Proxy(
Uri(scheme: 'http', host: server.address.address, port: server.port),
publicPath: '/proxy',
);
app.fallback(layer.handleRequest);
app!.fallback(layer.handleRequest);
app.responseFinalizers.add((req, res) async {
app!.responseFinalizers.add((req, res) async {
print('Normal. Buf: ' +
String.fromCharCodes(res.buffer.toBytes()) +
String.fromCharCodes(res.buffer!.toBytes()) +
', headers: ${res.headers}');
});
app.encoders.addAll({'gzip': gzip.encoder});
app!.encoders.addAll({'gzip': gzip.encoder});
client = await connectTo(app);
client = await connectTo(app!);
app.logger = testApp.logger = Logger('proxy')
app!.logger = testApp!.logger = Logger('proxy')
..onRecord.listen((rec) {
print(rec);
if (rec.error != null) print(rec.error);
@ -63,8 +63,8 @@ main() {
tearDown(() async {
await client.close();
await app.close();
await testApp.close();
await app!.close();
await testApp!.close();
app = null;
testApp = null;
});
@ -72,9 +72,9 @@ main() {
test('proxied', () async {
var rq = MockHttpRequest('GET', Uri.parse('/proxy/foo'));
await rq.close();
var rqc = await HttpRequestContext.from(rq, app, '/proxy/foo');
var rqc = await HttpRequestContext.from(rq, app!, '/proxy/foo');
var rsc = HttpResponseContext(rq.response, app);
await app.executeHandler(layer.handleRequest, rqc, rsc);
await app!.executeHandler(layer.handleRequest, rqc, rsc);
var response = await rq.response
//.transform(gzip.decoder)
.transform(utf8.decoder)
@ -83,12 +83,12 @@ main() {
});
test('empty', () async {
var response = await client.get('/proxy/empty');
var response = await client.get(Uri.parse('/proxy/empty'));
expect(response.body, isEmpty);
});
test('normal', () async {
var response = await client.get('/bar');
var response = await client.get(Uri.parse('/bar'));
expect(response, hasBody('normal'));
});
}