From a626364e5c1c1d862de81facc677760d8a7e0754 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sat, 9 Mar 2024 18:50:43 +0545 Subject: [PATCH 01/15] rename to utopia http --- example/pubspec.yaml | 4 ++-- ...mework_example.dart => utopia_http_example.dart} | 8 ++++---- lib/src/{app.dart => http.dart} | 13 ++++++------- lib/{utopia_framework.dart => utopia_http.dart} | 2 +- pubspec.yaml | 2 +- 5 files changed, 14 insertions(+), 15 deletions(-) rename example/{utopia_framework_example.dart => utopia_http_example.dart} (90%) rename lib/src/{app.dart => http.dart} (98%) rename lib/{utopia_framework.dart => utopia_http.dart} (94%) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0f827b0..eab1d6d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: utopia_framework_example +name: utopia_http_example description: A starting point for Dart libraries or applications. version: 1.0.0 publish_to: none @@ -9,7 +9,7 @@ environment: dependencies: shelf: - utopia_framework: + utopia_http: path: ../ dev_dependencies: diff --git a/example/utopia_framework_example.dart b/example/utopia_http_example.dart similarity index 90% rename from example/utopia_framework_example.dart rename to example/utopia_http_example.dart index 3386ef6..072b2d7 100644 --- a/example/utopia_framework_example.dart +++ b/example/utopia_http_example.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; void main() async { - final app = App(); + final app = Http(); app .get('/') .inject('request') @@ -72,8 +72,8 @@ void main() async { }); final address = InternetAddress.anyIPv4; - final port = App.getEnv('PORT', 8080); - await App.serve(app, ShelfServer(address, port), threads: 8); + final port = Http.getEnv('PORT', 8080); + await app.serve(ShelfServer(address, port), threads: 8); print("server started at http://${address.address}:$port"); print('press any key to exit.'); stdin.readByteSync(); diff --git a/lib/src/app.dart b/lib/src/http.dart similarity index 98% rename from lib/src/app.dart rename to lib/src/http.dart index 3606a93..42d8755 100644 --- a/lib/src/app.dart +++ b/lib/src/http.dart @@ -11,8 +11,8 @@ import 'router.dart'; import 'server.dart'; import 'validation_exception.dart'; -class App { - App() { +class Http { + Http() { di = DI(); _router = Router(); } @@ -47,18 +47,17 @@ class App { /// Memory cached result for chosen route Route? route; - static Future> serve( - App app, + Future> serve( Server server, { String? path, int threads = 1, }) async { - app._servers = await server.serve( - app.run, + _servers = await server.serve( + run, path: path, threads: threads, ); - return app._servers; + return _servers; } Route get(String url) { diff --git a/lib/utopia_framework.dart b/lib/utopia_http.dart similarity index 94% rename from lib/utopia_framework.dart rename to lib/utopia_http.dart index 6b92c68..e2a19cc 100644 --- a/lib/utopia_framework.dart +++ b/lib/utopia_http.dart @@ -3,8 +3,8 @@ /// library utopia_framework; -export 'src/app.dart'; export 'src/app_mode.dart'; +export 'src/http.dart'; export 'src/request.dart'; export 'src/response.dart'; export 'src/route.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index cabc495..dac3d61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: utopia_framework +name: utopia_http description: A light and easy to get started with HTTP framework for Dart version: 0.0.1 homepage: https://github.com/utopia-dart/utopia_framework From aa72659be4b6a6a1d66ec95019f5a1044abaf5c5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sat, 9 Mar 2024 18:51:01 +0545 Subject: [PATCH 02/15] format --- test/unit/router_test.dart | 43 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/test/unit/router_test.dart b/test/unit/router_test.dart index 2423381..d9d15e3 100644 --- a/test/unit/router_test.dart +++ b/test/unit/router_test.dart @@ -19,9 +19,13 @@ void main() { expect(router.match(Request.get, '/'), equals(routeIndex)); expect( - router.match(Request.get, '/about'), equals(routeAbout),); - expect(router.match(Request.get, '/about/me'), - equals(routeAboutMe),); + router.match(Request.get, '/about'), + equals(routeAbout), + ); + expect( + router.match(Request.get, '/about/me'), + equals(routeAboutMe), + ); }); test('Can match URL with placeholder', () { @@ -30,8 +34,7 @@ void main() { final routeBlogAuthorsComments = Route(Request.get, '/blog/authors/comments'); final routeBlogPost = Route(Request.get, '/blog/:post'); - final routeBlogPostComments = - Route(Request.get, '/blog/:post/comments'); + final routeBlogPostComments = Route(Request.get, '/blog/:post/comments'); final routeBlogPostCommentsSingle = Route(Request.get, '/blog/:post/comments/:comment'); @@ -43,18 +46,26 @@ void main() { router.addRoute(routeBlogPostCommentsSingle); expect(router.match(Request.get, '/blog'), equals(routeBlog)); - expect(router.match(Request.get, '/blog/authors'), - equals(routeBlogAuthors),); - expect(router.match(Request.get, '/blog/authors/comments'), - equals(routeBlogAuthorsComments),); - expect(router.match(Request.get, '/blog/:post'), - equals(routeBlogPost),); - expect(router.match(Request.get, '/blog/:post/comments'), - - equals(routeBlogPostComments),); expect( - router.match(Request.get, '/blog/:post/comments/:comment'), - equals(routeBlogPostCommentsSingle),); + router.match(Request.get, '/blog/authors'), + equals(routeBlogAuthors), + ); + expect( + router.match(Request.get, '/blog/authors/comments'), + equals(routeBlogAuthorsComments), + ); + expect( + router.match(Request.get, '/blog/:post'), + equals(routeBlogPost), + ); + expect( + router.match(Request.get, '/blog/:post/comments'), + equals(routeBlogPostComments), + ); + expect( + router.match(Request.get, '/blog/:post/comments/:comment'), + equals(routeBlogPostCommentsSingle), + ); }); test('Can match HTTP method', () { From 51d2e465e2cf1af9b053e7ffc5ade2dedeb381c9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sat, 9 Mar 2024 19:04:35 +0545 Subject: [PATCH 03/15] fix versions and tests --- example/pubspec.yaml | 6 ++-- lib/src/http.dart | 28 +++++++++---------- pubspec.yaml | 16 +++++------ .../{framework_test.dart => http_test.dart} | 14 +++++----- test/e2e/server.dart | 28 +++++++++---------- test/unit/{app_test.dart => http_test.dart} | 16 +++++------ test/unit/route_test.dart | 2 +- test/unit/router_test.dart | 2 +- 8 files changed, 56 insertions(+), 56 deletions(-) rename test/e2e/{framework_test.dart => http_test.dart} (91%) rename test/unit/{app_test.dart => http_test.dart} (70%) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index eab1d6d..2d6d54a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: none # homepage: https://www.example.com environment: - sdk: '>=2.17.5 <3.0.0' + sdk: '>=2.17.5 <4.0.0' dependencies: shelf: @@ -13,5 +13,5 @@ dependencies: path: ../ dev_dependencies: - lints: ^2.0.0 - test: ^1.16.0 + lints: ^3.0.0 + test: ^1.25.2 diff --git a/lib/src/http.dart b/lib/src/http.dart index 42d8755..e5e6084 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -124,10 +124,10 @@ class Http { Function callback, { List injections = const [], }) => - di.setResource(name, callback, injections: injections); + di.set(name, callback, injections: injections); dynamic getResource(String name, {bool fresh = false}) => - di.getResource(name, fresh: fresh); + di.get(name, fresh: fresh); Route? match(Request request) { var method = request.method; @@ -151,7 +151,7 @@ class Http { }); for (var injection in hook.injections) { - args[injection] = di.getResource(injection); + args[injection] = di.get(injection); } return args; } @@ -235,9 +235,9 @@ class Http { globalHooksFirst: false, ); - return response ?? di.getResource('response'); + return response ?? di.get('response'); } on Exception catch (e) { - di.setResource('error', () => e); + di.set('error', () => e); await _executeHooks( _errors, groups, @@ -251,20 +251,20 @@ class Http { ); if (e is ValidationException) { - final response = di.getResource('response'); + final response = di.get('response'); response.status = 400; } } - return di.getResource('response'); + return di.get('response'); } FutureOr run(Request request) async { - di.setResource('request', () => request); + di.set('request', () => request); try { - di.getResource('response'); + di.get('response'); } catch (e) { - di.setResource('response', () => Response('')); + di.set('response', () => Response('')); } var method = request.method.toUpperCase(); @@ -294,20 +294,20 @@ class Http { globalHook: true, globalHooksFirst: false, ); - return di.getResource('response'); + return di.get('response'); } on Exception catch (e) { for (final hook in _errors) { - di.setResource('error', () => e); + di.set('error', () => e); if (hook.getGroups().contains('*')) { hook.getAction().call( _getArguments(hook, requestParams: await request.getParams()), ); } } - return di.getResource('response'); + return di.get('response'); } } - final response = di.getResource('response'); + final response = di.get('response'); response.text('Not Found'); response.status = 404; diff --git a/pubspec.yaml b/pubspec.yaml index dac3d61..69dcf48 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,17 +4,17 @@ version: 0.0.1 homepage: https://github.com/utopia-dart/utopia_framework environment: - sdk: '>=2.18.0 <3.0.0' + sdk: '>=2.18.0 <4.0.0' dependencies: - mime: ^1.0.4 + mime: ^1.0.5 http_parser: ^4.0.2 string_scanner: ^1.2.0 - shelf: ^1.4.0 - shelf_static: ^1.1.1 - utopia_di: 0.0.1 + shelf: ^1.4.1 + shelf_static: ^1.1.2 + utopia_di: ^0.0.2 dev_dependencies: - lints: ^2.0.1 - test: ^1.23.1 - http: ^0.13.5 + lints: ^3.0.0 + test: ^1.25.2 + http: ^1.2.1 diff --git a/test/e2e/framework_test.dart b/test/e2e/http_test.dart similarity index 91% rename from test/e2e/framework_test.dart rename to test/e2e/http_test.dart index c55470e..edec344 100644 --- a/test/e2e/framework_test.dart +++ b/test/e2e/http_test.dart @@ -2,15 +2,15 @@ import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:test/test.dart'; -import 'package:utopia_framework/src/app.dart'; +import 'package:utopia_http/utopia_http.dart'; import 'server.dart' as server; void main() { - group('Framework Shelf Server', () { - App? app; + group('Http Shelf Server', () { + Http? http; setUp(() async { - app = await server.shelfServer(); + http = await server.shelfServer(); }); test('Basic Response', basicResponseTest); @@ -28,7 +28,7 @@ void main() { test('file upload', fileUpload); tearDown(() async { - await app?.closeServer(); + await http?.closeServer(); }); }); } @@ -69,8 +69,8 @@ void jsonTest() async { "email": "email@gmail.com", "name": "myname" }; - final stream = Stream.value(utf8.encode(jsonEncode(data))); - final res = await stream.pipe(req) as HttpClientResponse; + req.write(jsonEncode(data)); + final res = await req.close(); final output = await utf8.decodeStream(res); expect(res.headers.contentType.toString(), ContentType.json.toString()); expect( diff --git a/test/e2e/server.dart b/test/e2e/server.dart index bfbea03..713b6b3 100644 --- a/test/e2e/server.dart +++ b/test/e2e/server.dart @@ -1,8 +1,8 @@ import 'package:utopia_di/utopia_validators.dart'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; -void initApp(App app) { - app +void initHttp(Http http) { + http .error() .inject('error') .inject('response') @@ -14,13 +14,13 @@ void initApp(App app) { return response; }); - app.get('/').action(() { + http.get('/').action(() { return Response('Hello!'); }); - app.get('/empty').action(() {}); + http.get('/empty').action(() {}); - app + http .post('/create') .param(key: 'userId') .param(key: 'file') @@ -32,7 +32,7 @@ void initApp(App app) { return response; }); - app + http .get('/hello') .inject('request') .inject('response') @@ -41,7 +41,7 @@ void initApp(App app) { return response; }); - app + http .get('/users/:userId') .param( key: 'userId', @@ -55,7 +55,7 @@ void initApp(App app) { return response; }); - app + http .post('/users') .param(key: 'userId') .param(key: 'name') @@ -78,9 +78,9 @@ void initApp(App app) { }); } -Future shelfServer() async { - final app = App(); - initApp(app); - await App.serve(app, ShelfServer('localhost', 3030), path: 'test/e2e/public'); - return app; +Future shelfServer() async { + final http = Http(); + initHttp(http); + await http.serve(ShelfServer('localhost', 3030), path: 'test/e2e/public'); + return http; } diff --git a/test/unit/app_test.dart b/test/unit/http_test.dart similarity index 70% rename from test/unit/app_test.dart rename to test/unit/http_test.dart index 6ea6dd8..f180206 100644 --- a/test/unit/app_test.dart +++ b/test/unit/http_test.dart @@ -2,21 +2,21 @@ import 'dart:math'; import 'package:test/test.dart'; import 'package:utopia_di/utopia_validators.dart'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; void main() async { - final app = App(); - app.setResource('rand', () => Random().nextInt(100)); - app.setResource( + final http = Http(); + http.setResource('rand', () => Random().nextInt(100)); + http.setResource( 'first', (String second) => 'first-$second', injections: ['second'], ); - app.setResource('second', () => 'second'); + http.setResource('second', () => 'second'); - group('App', () { + group('Http', () { test('resource injection', () async { - final resource = app.getResource('rand'); + final resource = http.getResource('rand'); final route = Route('GET', '/path'); route @@ -34,7 +34,7 @@ void main() async { validator: Text(length: 200), ) .action((int rand, String x, String y) => Response("$x-$y-$rand")); - final res = await app.execute(route, Request('GET', Uri.parse('/path'))); + final res = await http.execute(route, Request('GET', Uri.parse('/path'))); expect(res.body, 'x-def-y-def-$resource'); }); }); diff --git a/test/unit/route_test.dart b/test/unit/route_test.dart index 844c326..b496eda 100644 --- a/test/unit/route_test.dart +++ b/test/unit/route_test.dart @@ -1,5 +1,5 @@ import 'package:test/test.dart'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; void main() async { final route = Route('GET', '/'); diff --git a/test/unit/router_test.dart b/test/unit/router_test.dart index d9d15e3..4632b60 100644 --- a/test/unit/router_test.dart +++ b/test/unit/router_test.dart @@ -1,5 +1,5 @@ import 'package:test/test.dart'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; void main() { final router = Router(); From b51353d4421f6ece4fb7b6142ac481fc8afdfc42 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:13:37 +0545 Subject: [PATCH 04/15] support for context --- example/utopia_http_example.dart | 11 +++++---- lib/src/http.dart | 38 +++++++++++++++++++++++--------- lib/src/request.dart | 2 +- lib/src/server.dart | 2 +- lib/src/servers/shelf.dart | 23 ++++++++++++------- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/example/utopia_http_example.dart b/example/utopia_http_example.dart index 072b2d7..e1ba7bc 100644 --- a/example/utopia_http_example.dart +++ b/example/utopia_http_example.dart @@ -2,7 +2,10 @@ import 'dart:io'; import 'package:utopia_http/utopia_http.dart'; void main() async { - final app = Http(); + final address = InternetAddress.anyIPv4; + final port = Http.getEnv('PORT', 8080); + final app = Http(ShelfServer(address, port), threads: 8); + app .get('/') .inject('request') @@ -71,10 +74,6 @@ void main() async { return response; }); - final address = InternetAddress.anyIPv4; - final port = Http.getEnv('PORT', 8080); - await app.serve(ShelfServer(address, port), threads: 8); + await app.start(); print("server started at http://${address.address}:$port"); - print('press any key to exit.'); - stdin.readByteSync(); } diff --git a/lib/src/http.dart b/lib/src/http.dart index e5e6084..4d9e315 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -12,11 +12,19 @@ import 'server.dart'; import 'validation_exception.dart'; class Http { - Http() { + Http( + this.server, { + this.path, + this.threads = 1, + }) { di = DI(); _router = Router(); } + final Server server; + final int threads; + final String? path; + late DI di; final Map> _routes = { Request.get: {}, @@ -47,11 +55,7 @@ class Http { /// Memory cached result for chosen route Route? route; - Future> serve( - Server server, { - String? path, - int threads = 1, - }) async { + Future> start() async { _servers = await server.serve( run, path: path, @@ -138,6 +142,7 @@ class Http { Map _getArguments( Hook hook, { + required String context, required Map requestParams, Map values = const {}, }) { @@ -198,7 +203,11 @@ class Http { } } - FutureOr execute(Route route, Request request) async { + FutureOr execute( + Route route, + Request request, + String context, + ) async { final groups = route.getGroups(); final pathValues = route.getPathValues(request); @@ -208,6 +217,7 @@ class Http { groups, (hook) async => _getArguments( hook, + context: context, requestParams: await request.getParams(), values: pathValues, ), @@ -216,6 +226,7 @@ class Http { final args = _getArguments( route, + context: context, requestParams: await request.getParams(), values: pathValues, ); @@ -228,6 +239,7 @@ class Http { groups, (hook) async => _getArguments( hook, + context: context, requestParams: await request.getParams(), values: pathValues, ), @@ -243,6 +255,7 @@ class Http { groups, (hook) async => _getArguments( hook, + context: context, requestParams: await request.getParams(), values: pathValues, ), @@ -258,7 +271,7 @@ class Http { return di.get('response'); } - FutureOr run(Request request) async { + FutureOr run(Request request, String context) async { di.set('request', () => request); try { @@ -281,7 +294,7 @@ class Http { } if (route != null) { - return execute(route, request); + return execute(route, request, context); } else if (method == Request.options) { try { _executeHooks( @@ -289,6 +302,7 @@ class Http { groups, (hook) async => _getArguments( hook, + context: context, requestParams: await request.getParams(), ), globalHook: true, @@ -300,7 +314,11 @@ class Http { di.set('error', () => e); if (hook.getGroups().contains('*')) { hook.getAction().call( - _getArguments(hook, requestParams: await request.getParams()), + _getArguments( + hook, + context: context, + requestParams: await request.getParams(), + ), ); } } diff --git a/lib/src/request.dart b/lib/src/request.dart index cf35f88..6c84b7b 100644 --- a/lib/src/request.dart +++ b/lib/src/request.dart @@ -105,7 +105,7 @@ class Request { value = { "file": part, "filename": filename, - "mimeType": part.headers['Content-Type'] + "mimeType": part.headers['Content-Type'], }; } else { value = (encoding ?? utf8).decodeStream(part); diff --git a/lib/src/server.dart b/lib/src/server.dart index 7944e1b..22f5d9a 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'request.dart'; import 'response.dart'; -typedef Handler = FutureOr Function(Request); +typedef Handler = FutureOr Function(Request, String); abstract class Server { final int port; diff --git a/lib/src/servers/shelf.dart b/lib/src/servers/shelf.dart index 6bc4f1b..2847dfa 100644 --- a/lib/src/servers/shelf.dart +++ b/lib/src/servers/shelf.dart @@ -8,17 +8,19 @@ import '../request.dart'; import '../response.dart'; import '../server.dart'; -class IsolateMessage { +class _IsolateMessage { final Handler handler; final SecurityContext? securityContext; final dynamic address; final int port; final String? path; + final String context; - IsolateMessage({ + _IsolateMessage({ required this.handler, required this.address, required this.port, + required this.context, this.path, this.securityContext, }); @@ -39,20 +41,23 @@ class ShelfServer extends Server { }) async { this.handler = handler; this.path = path; + iso.ReceivePort(); await _spawnOffIsolates(threads); return _servers; } - static Future _onIsolateMain(IsolateMessage message) async { + static Future _onIsolateMain(_IsolateMessage message) async { final server = await shelf_io.serve( message.path != null ? shelf.Cascade() .add(createStaticHandler(message.path!)) .add( - (request) => _handleRequest(request, message.handler), + (request) => + _handleRequest(request, message.context, message.handler), ) .handler - : (request) => _handleRequest(request, message.handler), + : (request) => + _handleRequest(request, message.context, message.handler), message.address, message.port, securityContext: message.securityContext, @@ -63,9 +68,10 @@ class ShelfServer extends Server { Future _spawnOffIsolates(int num) async { for (var i = 0; i < num; i++) { - await iso.Isolate.spawn( + await iso.Isolate.spawn<_IsolateMessage>( _onIsolateMain, - IsolateMessage( + _IsolateMessage( + context: i.toString(), handler: handler!, address: address, port: port, @@ -78,10 +84,11 @@ class ShelfServer extends Server { static FutureOr _handleRequest( shelf.Request sheflRequest, + String context, Handler handler, ) async { final request = _fromShelfRequest(sheflRequest); - final response = await handler.call(request); + final response = await handler.call(request, context); return _toShelfResponse(response); } From 62e119d57e5b5bdb0676000850276c3d23bc076a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:17:44 +0545 Subject: [PATCH 05/15] print worker ready message --- lib/src/servers/shelf.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/servers/shelf.dart b/lib/src/servers/shelf.dart index 2847dfa..28661dd 100644 --- a/lib/src/servers/shelf.dart +++ b/lib/src/servers/shelf.dart @@ -63,6 +63,7 @@ class ShelfServer extends Server { securityContext: message.securityContext, shared: true, ); + print('Worker ${message.context} ready'); _servers.add(server); } From a9797f21137350c26fbb91cc04f2a7dc5e1a6378 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:18:23 +0545 Subject: [PATCH 06/15] fix test --- test/e2e/server.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/server.dart b/test/e2e/server.dart index 713b6b3..c7d433d 100644 --- a/test/e2e/server.dart +++ b/test/e2e/server.dart @@ -79,8 +79,8 @@ void initHttp(Http http) { } Future shelfServer() async { - final http = Http(); + final http = Http(ShelfServer('localhost', 3030), path: 'test/e2e/public'); initHttp(http); - await http.serve(ShelfServer('localhost', 3030), path: 'test/e2e/public'); + await http.start(); return http; } From ff8efea38e976b6156520822ced7b6e73ca90937 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:30:15 +0545 Subject: [PATCH 07/15] fix tests --- lib/src/http.dart | 11 ++++++++--- test/unit/http_test.dart | 11 +++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/src/http.dart b/lib/src/http.dart index 4d9e315..e284a48 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -126,12 +126,17 @@ class Http { void setResource( String name, Function callback, { + String context = 'utopia', List injections = const [], }) => - di.set(name, callback, injections: injections); + di.set(name, callback, injections: injections, context: context); - dynamic getResource(String name, {bool fresh = false}) => - di.get(name, fresh: fresh); + dynamic getResource( + String name, { + bool fresh = false, + String context = 'utopia', + }) => + di.get(name, fresh: fresh, context: context); Route? match(Request request) { var method = request.method; diff --git a/test/unit/http_test.dart b/test/unit/http_test.dart index f180206..76e0445 100644 --- a/test/unit/http_test.dart +++ b/test/unit/http_test.dart @@ -5,7 +5,7 @@ import 'package:utopia_di/utopia_validators.dart'; import 'package:utopia_http/utopia_http.dart'; void main() async { - final http = Http(); + final http = Http(ShelfServer('localhost', 8080)); http.setResource('rand', () => Random().nextInt(100)); http.setResource( 'first', @@ -34,7 +34,14 @@ void main() async { validator: Text(length: 200), ) .action((int rand, String x, String y) => Response("$x-$y-$rand")); - final res = await http.execute(route, Request('GET', Uri.parse('/path'))); + final res = await http.execute( + route, + Request( + 'GET', + Uri.parse('/path'), + ), + 'utopia', + ); expect(res.body, 'x-def-y-def-$resource'); }); }); From ce4dbb4fd84705e6e2d12d6454df88ce5906d9d3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:38:48 +0545 Subject: [PATCH 08/15] update versions --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 69dcf48..2256275 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: utopia_http description: A light and easy to get started with HTTP framework for Dart -version: 0.0.1 +version: 0.1.0 homepage: https://github.com/utopia-dart/utopia_framework environment: @@ -12,7 +12,7 @@ dependencies: string_scanner: ^1.2.0 shelf: ^1.4.1 shelf_static: ^1.1.2 - utopia_di: ^0.0.2 + utopia_di: ^0.1.0 dev_dependencies: lints: ^3.0.0 From c82cf632fa86c21c14006966cfcc598ade1010ff Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 15:41:56 +0545 Subject: [PATCH 09/15] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9035290..b151e23 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,4 @@ void main() async { ## Copyright and license -The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) +The MIT License (MIT) [https://www.opensource.org/licenses/mit-license.php](https://www.opensource.org/licenses/mit-license.php) From 0d6ce6214d97fc0afaad91d0e206327a64b399ec Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 10 Mar 2024 16:05:37 +0545 Subject: [PATCH 10/15] add documentation comments --- example/utopia_http_example.dart | 14 ++--- lib/src/app_mode.dart | 1 + lib/src/http.dart | 105 +++++++++++++++++++++++++------ lib/src/request.dart | 33 ++++++++++ lib/src/response.dart | 17 +++++ 5 files changed, 143 insertions(+), 27 deletions(-) diff --git a/example/utopia_http_example.dart b/example/utopia_http_example.dart index e1ba7bc..9ba6658 100644 --- a/example/utopia_http_example.dart +++ b/example/utopia_http_example.dart @@ -6,14 +6,12 @@ void main() async { final port = Http.getEnv('PORT', 8080); final app = Http(ShelfServer(address, port), threads: 8); - app - .get('/') - .inject('request') - .inject('response') - .action((Request request, Response response) { - response.text('Hello world'); - return response; - }); + app.get('/').inject('request').inject('response').action( + (Request request, Response response) { + response.text('Hello world'); + return response; + }, + ); app .get('/hello-world') .inject('request') diff --git a/lib/src/app_mode.dart b/lib/src/app_mode.dart index d985cfb..716f09b 100644 --- a/lib/src/app_mode.dart +++ b/lib/src/app_mode.dart @@ -1 +1,2 @@ +/// Application mode enum AppMode { development, stage, production } diff --git a/lib/src/http.dart b/lib/src/http.dart index e284a48..52906f3 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -11,21 +11,48 @@ import 'router.dart'; import 'server.dart'; import 'validation_exception.dart'; +/// Http class used to bootstrap your Http server +/// You need to use one of the server adapters. Currently only +/// Shelf adapter is available +/// +/// Example: +/// ```dart +/// void main() async { +/// final address = InternetAddress.anyIPv4; +/// final port = Http.getEnv('PORT', 8080); +/// final app = Http(ShelfServer(address, port), threads: 8); +/// // setup routes +/// app.get('/').inject('request').inject('response').action( +/// (Request request, Response response) { +/// response.text('Hello world'); +/// return response; +/// }, +/// ); +/// // sart the server +/// await app.start(); +/// } +/// ``` class Http { Http( this.server, { this.path, this.threads = 1, }) { - di = DI(); + _di = DI(); _router = Router(); } + /// Server adapter, currently only shelf server is supported final Server server; + + /// Number of threads (isolates) to spawn final int threads; + + /// Path to server static files from final String? path; - late DI di; + late DI _di; + final Map> _routes = { Request.get: {}, Request.post: {}, @@ -34,7 +61,10 @@ class Http { Request.delete: {}, Request.head: {}, }; + + /// Configured routes for different methods Map> get routes => _routes; + final List _errors = []; final List _init = []; final List _shutdown = []; @@ -45,16 +75,25 @@ class Http { Route? _wildcardRoute; + /// Application mode AppMode? mode; + /// Is application running in production mode bool get isProduction => mode == AppMode.production; + + /// Is application running in development mode bool get isDevelopment => mode == AppMode.development; + + /// Is application running in staging mode bool get isStage => mode == AppMode.stage; + + /// List of servers running List get servers => _servers; /// Memory cached result for chosen route Route? route; + /// Start the servers Future> start() async { _servers = await server.serve( run, @@ -64,80 +103,102 @@ class Http { return _servers; } + /// Initialize a GET route Route get(String url) { return addRoute(Request.get, url); } + /// Initialize a POST route Route post(String url) { return addRoute(Request.post, url); } + /// Initialize a PATCH route Route patch(String url) { return addRoute(Request.patch, url); } + /// Initialize a PUT route Route put(String url) { return addRoute(Request.put, url); } + /// Initialize a DELETE route Route delete(String url) { return addRoute(Request.delete, url); } + /// Initialize a wildcard route Route wildcard() { _wildcardRoute = Route('', ''); return _wildcardRoute!; } + /// Initialize a init hook + /// Init hooks are ran before executing each request Hook init() { final hook = Hook()..groups(['*']); _init.add(hook); return hook; } + /// Initialize shutdown hook + /// Shutdown hooks are ran after executing the request, before the response is sent Hook shutdown() { final hook = Hook()..groups(['*']); _shutdown.add(hook); return hook; } + /// Initialize options hook + /// Options hooks are ran for OPTIONS requests Hook options() { final hook = Hook()..groups(['*']); _options.add(hook); return hook; } + /// Initialize error hooks + /// Error hooks are ran for each errors Hook error() { final hook = Hook()..groups(['*']); _errors.add(hook); return hook; } + /// Get environment variable static dynamic getEnv(String key, [dynamic def]) { return Platform.environment[key] ?? def; } + /// Initialize route Route addRoute(String method, String path) { final route = Route(method, path); _router.addRoute(route); return route; } + /// Set resource + /// Once set, you can use `inject` to inject + /// these resources to set other resources or in the hooks + /// and routes void setResource( String name, Function callback, { String context = 'utopia', List injections = const [], }) => - di.set(name, callback, injections: injections, context: context); + _di.set(name, callback, injections: injections, context: context); - dynamic getResource( + /// Get a resource + T getResource( String name, { bool fresh = false, String context = 'utopia', }) => - di.get(name, fresh: fresh, context: context); + _di.get(name, fresh: fresh, context: context); + /// Match route based on request Route? match(Request request) { var method = request.method; method = (method == Request.head) ? Request.get : method; @@ -145,6 +206,7 @@ class Http { return route; } + /// Get arguments for hooks Map _getArguments( Hook hook, { required String context, @@ -161,11 +223,12 @@ class Http { }); for (var injection in hook.injections) { - args[injection] = di.get(injection); + args[injection] = _di.get(injection); } return args; } + /// Execute list of given hooks Future _executeHooks( List hooks, List groups, @@ -208,6 +271,7 @@ class Http { } } + /// Execute request FutureOr execute( Route route, Request request, @@ -252,9 +316,9 @@ class Http { globalHooksFirst: false, ); - return response ?? di.get('response'); + return response ?? _di.get('response'); } on Exception catch (e) { - di.set('error', () => e); + _di.set('error', () => e); await _executeHooks( _errors, groups, @@ -269,20 +333,21 @@ class Http { ); if (e is ValidationException) { - final response = di.get('response'); + final response = _di.get('response'); response.status = 400; } } - return di.get('response'); + return _di.get('response'); } + /// Run the execution for given request FutureOr run(Request request, String context) async { - di.set('request', () => request); + _di.set('request', () => request); try { - di.get('response'); + _di.get('response'); } catch (e) { - di.set('response', () => Response('')); + _di.set('response', () => Response('')); } var method = request.method.toUpperCase(); @@ -313,10 +378,10 @@ class Http { globalHook: true, globalHooksFirst: false, ); - return di.get('response'); + return _di.get('response'); } on Exception catch (e) { for (final hook in _errors) { - di.set('error', () => e); + _di.set('error', () => e); if (hook.getGroups().contains('*')) { hook.getAction().call( _getArguments( @@ -327,14 +392,14 @@ class Http { ); } } - return di.get('response'); + return _di.get('response'); } } - final response = di.get('response'); + final response = _di.get('response'); response.text('Not Found'); response.status = 404; - di.reset(); // for each run, resources should be re-generated from callbacks + _di.reset(); // for each run, resources should be re-generated from callbacks return response; } @@ -354,9 +419,10 @@ class Http { } } + /// Reset various resources void reset() { _router.reset(); - di.reset(); + _di.reset(); _errors.clear(); _init.clear(); _shutdown.clear(); @@ -364,6 +430,7 @@ class Http { mode = null; } + /// Close all the servers Future closeServer({bool force = false}) async { for (final server in _servers) { await server.close(force: force); diff --git a/lib/src/request.dart b/lib/src/request.dart index 6c84b7b..7d9d2f7 100644 --- a/lib/src/request.dart +++ b/lib/src/request.dart @@ -4,21 +4,49 @@ import 'package:mime/mime.dart'; import 'package:string_scanner/string_scanner.dart'; class Request { + /// GET method static const String get = 'GET'; + + /// POST method static const String post = 'POST'; + + /// PUT method static const String put = 'PUT'; + + /// PATCH method static const String patch = 'PATCH'; + + /// DELETE method static const String delete = 'DELETE'; + + /// HEAD method static const String head = 'HEAD'; + + /// OPTIONS method static const String options = 'OPTIONS'; + /// Uri final Uri url; + + /// Method final String method; + + /// Headers final Map headers; + + /// All headers final Map> headersAll; + + /// Encoding final Encoding? encoding; + + /// Content type final String? contentType; + + /// Body final Stream>? body; + + /// Payload Map? _payload; Request( @@ -31,6 +59,7 @@ class Request { this.body, }); + /// Get parameter with matching key or return default value dynamic getParam(String key, {dynamic defaultValue}) async { switch (method) { case put: @@ -44,6 +73,7 @@ class Request { } } + /// Get all the parameters Future> getParams() async { switch (method) { case put: @@ -57,6 +87,7 @@ class Request { } } + /// Get payload dynamic getPayload(String key, {dynamic defaultValue}) async { await _generateInput(); return _payload![key] ?? defaultValue; @@ -82,10 +113,12 @@ class Request { return _payload!; } + /// Get query parameter dynamic getQuery(String key, {dynamic defaultValue}) { return url.queryParameters[key] ?? defaultValue; } + /// Parse multipart forma data Future> _multipartFormData() async { final data = await _parts .map<_FormData?>((part) { diff --git a/lib/src/response.dart b/lib/src/response.dart index 71627c5..b76cd32 100644 --- a/lib/src/response.dart +++ b/lib/src/response.dart @@ -2,13 +2,21 @@ import 'dart:convert'; import 'dart:io'; class Response { + /// Response body String body; + + /// HTTP status int status = 200; + + /// Content type ContentType contentType = ContentType.text; + + /// Disable payload bool disablePayload = false; final Map _headers; final List _cookies = []; + /// Get headers Map get headers { _headers[HttpHeaders.contentTypeHeader] = contentType.toString(); _headers[HttpHeaders.setCookieHeader] = @@ -16,48 +24,57 @@ class Response { return _headers; } + /// Get cookies List get cookies => _cookies; Response(this.body, {this.status = 200, Map? headers}) : _headers = headers ?? {}; + /// Add header Response addHeader(String key, String value) { _headers[key] = value; return this; } + /// Remove header Response removeHeader(String key) { _headers.remove(key); return this; } + /// Add cookie Response addCookie(Cookie cookie) { _cookies.add(cookie); return this; } + /// Remove cookie Response removeCookie(Cookie cookie) { _cookies.removeWhere((element) => element.name == cookie.name); return this; } + /// Set json response void json(Map data, {int status = HttpStatus.ok}) { contentType = ContentType.json; body = jsonEncode(data); } + /// Set text response void text(String data, {int status = HttpStatus.ok}) { contentType = ContentType.text; this.status = status; body = data; } + /// Set HTML response void html(String data, {int status = HttpStatus.ok}) { contentType = ContentType.html; this.status = status; body = data; } + /// Set empty response void noContent() { status = HttpStatus.noContent; body = ''; From e56c6880cc05813ad51175d2a28868724864b0ff Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 11 Mar 2024 16:50:15 +0545 Subject: [PATCH 11/15] reset dependencies only per context --- lib/src/http.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/src/http.dart b/lib/src/http.dart index 52906f3..274baa5 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -342,12 +342,12 @@ class Http { /// Run the execution for given request FutureOr run(Request request, String context) async { - _di.set('request', () => request); + setResource('request', () => request, context: context); try { - _di.get('response'); + getResource('response', context: context); } catch (e) { - _di.set('response', () => Response('')); + setResource('response', () => Response(''), context: context); } var method = request.method.toUpperCase(); @@ -378,7 +378,7 @@ class Http { globalHook: true, globalHooksFirst: false, ); - return _di.get('response'); + return getResource('response', context: context); } on Exception catch (e) { for (final hook in _errors) { _di.set('error', () => e); @@ -392,14 +392,15 @@ class Http { ); } } - return _di.get('response'); + return getResource('response', context: context); } } - final response = _di.get('response'); + final response = getResource('response', context: context); response.text('Not Found'); response.status = 404; - _di.reset(); // for each run, resources should be re-generated from callbacks + // for each run, resources should be re-generated from callbacks + resetResources(context); return response; } @@ -419,6 +420,11 @@ class Http { } } + /// Reset dependencies + void resetResources([String? context]) { + _di.reset(context); + } + /// Reset various resources void reset() { _router.reset(); From 2f24a87cc9b76ed538734bcfca195295ec47e4ed Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 11 Mar 2024 17:52:32 +0545 Subject: [PATCH 12/15] fixes and more docs --- lib/src/http.dart | 12 ++++++------ lib/src/route.dart | 14 ++++++++++++++ lib/src/router.dart | 7 +++++++ lib/src/server.dart | 10 +++++++++- lib/src/servers/shelf.dart | 6 +++++- lib/src/validation_exception.dart | 1 + 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/src/http.dart b/lib/src/http.dart index 274baa5..d578593 100644 --- a/lib/src/http.dart +++ b/lib/src/http.dart @@ -95,7 +95,7 @@ class Http { /// Start the servers Future> start() async { - _servers = await server.serve( + _servers = await server.start( run, path: path, threads: threads, @@ -223,7 +223,7 @@ class Http { }); for (var injection in hook.injections) { - args[injection] = _di.get(injection); + args[injection] = getResource(injection, context: context); } return args; } @@ -316,7 +316,7 @@ class Http { globalHooksFirst: false, ); - return response ?? _di.get('response'); + return response ?? getResource('response', context: context); } on Exception catch (e) { _di.set('error', () => e); await _executeHooks( @@ -333,11 +333,11 @@ class Http { ); if (e is ValidationException) { - final response = _di.get('response'); + final response = getResource('response', context: context); response.status = 400; } } - return _di.get('response'); + return getResource('response', context: context); } /// Run the execution for given request @@ -422,7 +422,7 @@ class Http { /// Reset dependencies void resetResources([String? context]) { - _di.reset(context); + _di.resetResources(context); } /// Reset various resources diff --git a/lib/src/route.dart b/lib/src/route.dart index c8acde3..9a5cc8a 100644 --- a/lib/src/route.dart +++ b/lib/src/route.dart @@ -2,9 +2,17 @@ import 'package:utopia_di/utopia_di.dart'; import 'request.dart'; +/// Route +/// +/// A http route class Route extends Hook { + /// HTTP method String method = ''; + + /// Whether or not hook is enabled bool hook = true; + + /// Route path String path; static int counter = 0; final List _aliases = []; @@ -17,9 +25,11 @@ class Route extends Hook { order = counter; } + /// Get route aliases List get aliases => _aliases; Map get pathParams => _pathParams; + /// Add a route alias Route alias(String path) { if (!_aliases.contains(path)) { _aliases.add(path); @@ -28,10 +38,12 @@ class Route extends Hook { return this; } + /// Set path params void setPathParam(String key, int index) { _pathParams[key] = index; } + /// Get values for path params Map getPathValues(Request request) { var pathValues = {}; var parts = request.url.path.split('/').where((part) => part.isNotEmpty); @@ -45,11 +57,13 @@ class Route extends Hook { return pathValues; } + /// Set route label Route label(String key, String value) { labels[key] = value; return this; } + /// Get route label String? getLabel(String key, {String? defaultValue}) { return labels[key] ?? defaultValue; } diff --git a/lib/src/router.dart b/lib/src/router.dart index 144433d..ba56f14 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -2,7 +2,9 @@ import 'dart:collection'; import 'route.dart'; +/// Router class Router { + /// Placeholder token for route static const String placeholderToken = ':::'; Map> _routes = { @@ -15,10 +17,12 @@ class Router { List _params = []; + /// Get list of all the routes UnmodifiableMapView> getRoutes() { return UnmodifiableMapView(_routes); } + /// Add a route void addRoute(Route route) { List result = preparePath(route.path); String path = result[0]; @@ -45,6 +49,7 @@ class Router { } } + /// Match a route for given method and path Route? match(String method, String path) { if (!_routes.containsKey(method)) { return null; @@ -88,6 +93,7 @@ class Router { return result; } + /// Prepare path List preparePath(String path) { List parts = path.split('/').where((p) => p.isNotEmpty).toList(); String prepare = ''; @@ -113,6 +119,7 @@ class Router { return [prepare, params]; } + /// Reset router void reset() { _params = []; _routes = { diff --git a/lib/src/server.dart b/lib/src/server.dart index 22f5d9a..a9d2e96 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -3,16 +3,24 @@ import 'dart:io'; import 'request.dart'; import 'response.dart'; +/// Server request handler typedef Handler = FutureOr Function(Request, String); +/// Server adapter abstract class Server { + /// Server port final int port; + + /// Server address final dynamic address; + + /// Server security context final SecurityContext? securityContext; Server(this.address, this.port, {this.securityContext}); - Future> serve( + /// Start the server + Future> start( Handler handler, { String? path, int threads = 1, diff --git a/lib/src/servers/shelf.dart b/lib/src/servers/shelf.dart index 28661dd..e7dc330 100644 --- a/lib/src/servers/shelf.dart +++ b/lib/src/servers/shelf.dart @@ -26,6 +26,9 @@ class _IsolateMessage { }); } +/// ShelfServer +/// +/// Create a server class ShelfServer extends Server { static final List _servers = []; Handler? handler; @@ -33,8 +36,9 @@ class ShelfServer extends Server { ShelfServer(super.address, super.port, {super.securityContext}); + /// Start the server @override - Future> serve( + Future> start( Handler handler, { String? path, int threads = 1, diff --git a/lib/src/validation_exception.dart b/lib/src/validation_exception.dart index df55d10..9a3f7a5 100644 --- a/lib/src/validation_exception.dart +++ b/lib/src/validation_exception.dart @@ -1,5 +1,6 @@ import 'dart:io'; +/// ValidationException class ValidationException extends HttpException { ValidationException(super.message); } From a6db8c826dcba81df3cce23d778acd676d701f99 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 11 Mar 2024 17:54:16 +0545 Subject: [PATCH 13/15] changelog --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b78d64c..9d9e6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ -## 0.0.1 +## 0.1.0 -- Initial version. +- Multi threaded HTTP server +- Easy to use +- Customizable +- Inbuilt dependency injection From b2131d704e89ba2876f6db2faee737b14cef3817 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 11 Mar 2024 17:55:33 +0545 Subject: [PATCH 14/15] update utopia di version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2256275..ce4990f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: string_scanner: ^1.2.0 shelf: ^1.4.1 shelf_static: ^1.1.2 - utopia_di: ^0.1.0 + utopia_di: ^0.2.0 dev_dependencies: lints: ^3.0.0 From 5201223e66b3a1f3374bf72781acf7b3f154936d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 11 Mar 2024 18:00:21 +0545 Subject: [PATCH 15/15] update readme --- README.md | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b151e23..71d80aa 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,6 @@ -# Utopia Dart Framework +# Utopia HTTP Server -**NOT READY FOR PRODUCTION** - -Light and Fast Dart Framework to build awesome Dart applications. Inspired from [Utopia PHP Framework](https://github.com/utopia-php/framework). - -## ⚠️ Warning! - -This library is highly volatile and heavily under development. +Light and Fast Dart HTTP library to build awesome Dart server side applications. Inspired from [Utopia PHP ecosystem](https://github.com/utopia-php). ## Getting Started @@ -14,33 +8,30 @@ First add the dependency in your pubspec.yaml ```yaml dependencies: - utopia_framework: - git: https://github.com/utopia-dart/utopia_framework + utopia_http: ^0.1.0 ``` Now, in main.dart, you can ```dart import 'dart:io'; -import 'package:utopia_framework/utopia_framework.dart'; +import 'package:utopia_http/utopia_http.dart'; void main() async { - final app = App(); - app - .get('/') - .inject('response') - .action((Response response) { - response.text('Hello World!'); - return response; - }); - final address = InternetAddress.anyIPv4; - final port = App.getEnv('PORT', 8080); - await App.serve(app, ShelfServer(address, port), threads: 3); - print("server started at ${address.address}:$port"); - print('press any key to exit.'); - stdin.readByteSync(); + final port = Http.getEnv('PORT', 8080); + final app = Http(ShelfServer(address, port), threads: 8); + + app.get('/').inject('request').inject('response').action( + (Request request, Response response) { + response.text('Hello world'); + return response; + }, + ); + + await app.start(); } + ``` ## Copyright and license