rknell / alfred Goto Github PK
View Code? Open in Web Editor NEWA performant, expressjs like server framework with a few gadgets that make life even easier.
License: Other
A performant, expressjs like server framework with a few gadgets that make life even easier.
License: Other
In my last framework the was string that allowed me all connections:
app.use(CorsMiddleware.use(origin: '*', allowedHeaders: ['Content-Type']));
In alfred I tried next string:
app.all('*', cors());
and:
app.all('*', cors(origin: '*'));
But after starting Flutter project in Chrome I am getting error:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
What I am doing wrong?
I really like this package and appreciate your work.
In my application I need to stop the server on a button press but I did not find any helper method to stop the running server.
import 'package:alfred/alfred.dart';
void main() async {
final app = Alfred();
app.get('/example', (req, res) => 'Hello world');
await app.listen();
// something like below
// app.isRuuning();
// app.stop();
}
Hey @felixblaschke,
Do you have anything on your roadmap thats got to be done before we push out 0.1.0?
I've put it into production now for a couple of apps but probably haven't done a huge amount of creative dev for weird edge cases. Ultimately I was able to replace a very large Angel app (large but using only the basics) and it hasn't had any troubles at all - this is pretty high traffic mission critical app.
I just want to be mindful about not changing the API too heavily so the community can really run with it, so is there anything you are itching to change or something you haven't raised yet?
All verb methods (like Alfred.get
) accepts middleware
parameter. But only the get
method defers it into HttpRoute
object.
Any thoughts about that?
During migration to Alfred I faced with next error:
Unhandled exception:
Bad state: Stream has already been listened to.
#0 _StreamController._subscribe (dart:async/stream_controller.dart:635:7)
#1 _ControllerStream._createSubscription (dart:async/stream_controller.dart:786:19)
#2 _StreamImpl.listen (dart:async/stream_impl.dart:473:9)
#3 new _ForwardingStreamSubscription (dart:async/stream_pipe.dart:114:10)
#4 _ForwardingStream._createSubscription (dart:async/stream_pipe.dart:86:16)
#5 _ForwardingStream.listen (dart:async/stream_pipe.dart:81:12)
#6 _HttpIncoming.listen (dart:_http/http_impl.dart:444:8)
#7 _HttpRequest.listen (dart:_http/http_impl.dart:524:22)
#8 new _SinkTransformerStreamSubscription (dart:async/stream_transformers.dart:49:16)
#9 _BoundSinkStream.listen (dart:async/stream_transformers.dart:171:9)
#10 Stream.fold (dart:async/stream.dart:798:14)
#11 _process.asText (package:alfred/src/body_parser/http_body.dart:238:58)
#12 _process.asText (package:alfred/src/body_parser/http_body.dart:233:26)
#13 _process (package:alfred/src/body_parser/http_body.dart:281:28)
#14 HttpBodyHandler.processRequest (package:alfred/src/body_parser/http_body.dart:119:24)
#15 RequestHelpers.body (package:alfred/src/extensions/request_helpers.dart:16:30)
#16 main.<anonymous closure> (file:///D:/code/zakupki/parser_monitor_server/bin/parser_monitor_server.dart:173:21)
<asynchronous suspension>
#17 Alfred._incomingRequest (package:alfred/src/alfred.dart:291:15)
<asynchronous suspension>
#18 _QueuedFuture.execute (package:queue/src/dart_queue_base.dart:26:16)
<asynchronous suspension>
What can cause it?
My app have several handlers and flutter app send at start few requests to them.
I have a folder called web which contains HTML, CSS, and Javascript files.
I need to serve the HTML file with CSS and Javascript as well how can I do that in Alfred.
something like res.render('index.html')
in Node.Js
The Javascript File and the CSS file or not shown while the HTML is loaded.
I am not sure if there is any best-practice about how to use handlers, but mine is look next:
app.post('/count', (req, res) async {
print('count handler');
final body = await req.body; //JSON body
try {
var r = await db.getCountForProcessing(body as Map<String, dynamic>).timeout(Duration(minutes: 2));
await res.json({'status': 'success', 'data': r}); // yes I will replace it to return later
} catch (e) {
print('[ERROR] count handler: $e');
}
});
Future<dynamic> getCountForProcessing(Map body) async {
try {
List<List<dynamic>> result = await connection.query(body['sql']).timeout(Duration(minutes: 5));
if (result.isNotEmpty) {
print('COUNT for processing: ${result[0][0]}');
return result[0][0];
} else {
print('COUNT: No files for processing');
return [];
}
} on PostgreSQLException catch (e) {
print(e);
}
}
The problem that body
in alfred have type Object?
, but my handler expect: Map body
. As temporal solution I used: body as Map<String, dynamic>
but don't sure if if correct, and maybe I should change functions to accept Object?
instead of Map
?
It is not clear to me how to stop server.
I use VSCode and have find pid to kill server process.
It inconvenient for many reasons!
Would be nice to see any advise!
Thanks!
following this repo https://github.com/google/cronet.dart
would be interesting to support cronet
Hello. First of all, thank you for this useful package.
I want to get large files, for example 2 GB.
Is the file received in memory?
Is it possible for me to give the file path to be saved in pieces?
I understand that you are used to write strings with double quotes and like to explicit name types. On the other hand Dart is a language with a convention over configuration philosophy. Therefore I recommend using the default linter style that comes with pedantic.
The main advantage is easily readable and well maintainable code which leads to more acceptance to contribute and use Alfred.
You already acknowledge to enforce dartfmt code formatting in CI build. So we can go a step further and disable both custom rules in analysis_options.yaml
.
Dart will automatically fix the code for you:
dart fix --apply
This looks like a bug
In certain scenarios we want middleware to pass data down the pipeline. For example if you have middleware that resolves credential information based on JWT-style token.
Here is an example:
import 'dart:async';
import "dart:io";
import "package:alfred/alfred.dart";
FutureOr credentialResolver(HttpRequest req, HttpResponse res) async {
var token = req.headers.value('X-SESSION-TOKEN');
var user = await authority.tokenToUser(token);
req.store.set('user', user);
}
Future<void> main() async {
final app = Alfred();
app.all('*', credentialResolver);
app.get("/some/path", (req, res) async {
var user = req.store.get<User>('user');
return "Hello ${user.name}";
});
await app.listen();
}
This can also be used for performance measurements or general tracking.
This mechanism can also be used to address issue #14
If such feature comes we need an option to continue pipeline processing although the response writer is closed, i.e. tracking middleware. A flag like req.isDone
comes handy to check this.
I was looking for a server framework for my flutter web app, and after some research I stumbled upon Alfred, which, I must say, fits perfectly my needs in term of simplicity and independence.
Since I need to have SSL support, I looked around in docs and issues, found nothing, so I started right away to add the functionality myself by extending Alfred and writing a secureListen
method that would mimic the original listen
but that would use Http.bindSecure instead. But then I noticed that there's the _incomingRequest
method that's not visible from my custom Alfred, that in turn uses other private methods.
Do you know any simple way to make this work?
I have got follow code:
app.post('/sql-insert', (req, res) async {
print("sql-insert handler");
final body = await req.body; //JSON body
try {
var insertResult = await db.sqlInsert(body as Map<String, dynamic>).timeout(Duration(seconds: 140));
switch (insertResult) {
case dbEnums.success:
return res.json({'status': 'success'});
case dbEnums.insertFailed:
return res.json({'status': 'failed'});
default:
return res.json({'status': 'unkownStatus'});
}
} on TimeoutException catch (e) {
print("Too long request: ${e.message}");
print("Too long request duration: ${e}");
} catch (e) {
print("[ERROR] sql-insert exception: $e");
}
});
This was correct in old http framework, but it seems that in alfred instead of:
return res.json({'status': 'failed'});
I should write:
await res.json({'status': 'failed'});
Because with my code I am getting error:
2021-09-12 12:04:11.206949 - error - No type handler found for _HttpResponse / Instance of '_HttpResponse'
Could you show me right version of how code should look like?
I am getting error on receiving JSON with curl.
curl -X POST http://127.0.0.1:3000/ctr \
-H 'Content-Type: application/json' \
-d '{"selected_region": ["Республика Адыгея"]}'
it's seems that it can't decode Cyrillic.
error:
2021-10-12 22:39:28.528959 - info - POST - /ctr
2021-10-12 22:39:28.541953 - error - FormatException: Missing extension byte (at offset 23)
2021-10-12 22:39:28.542922 - error - #0 _Utf8Decoder.convertChunked (dart:convert-patch/convert_patch.dart:1892:7)
#1 _Utf8ConversionSink.addSlice (dart:convert/string_conversion.dart:314:28)
#2 _Utf8ConversionSink.add (dart:convert/string_conversion.dart:310:5)
#3 _ConverterStreamEventSink.add (dart:convert/chunked_conversion.dart:72:18)
#4 _SinkTransformerStreamSubscription._handleData (dart:async/stream_transformers.dart:111:24)
#5 _RootZone.runUnaryGuarded (dart:async/zone.dart:1620:10)
#6 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
#7 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)
#8 _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)
#9 _HandleErrorStream._handleData (dart:async/stream_pipe.dart:253:10)
#10 _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)
#11 _RootZone.runUnaryGuarded (dart:async/zone.dart:1620:10)
#12 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
#13 _DelayedData.perform (dart:async/stream_impl.dart:591:14)
#14 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:706:11)
#15 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:663:7)
#16 _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#17 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
#18 _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:119:13)
#19 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:188:5)
app code:
// ....
final app = Alfred();
app.post('/ctr', (req, res) async { // complex tender request
var data = await req.body;
print('data: $data');
await res.json({'data': 'ok'});
});
await app.listen();
Thanks for your work. I really enjoy your package.
How about adding an ability to create a websocket server?
See GetServer https://pub.dev/packages/get_server :
I love to see such a project come to life!
it would be great if there was:
discord
or maybe slack
thanks for the attention
Ive been trying to integrate the socket_io package with alfred but socket_io seems to only allow StreamServers not HttpServer.
Has anyone found a way to integrate both?
Is this production ready?
Since dart has built in Futures feature, why the lambda passed to "get", "post", etc, need a "resp" paramether? Why just not simply a lambda with the signature: Future<Response> Function(Request)
?
A clear example is needed on How to use the package inside Flutter since it is mentioned that it can be used for Flutter.
I would like to create simple site with authorization. So it's seems the only way to keep users login between browser restart (correct me if not) to have cookies support. Is it possible with Alfred?
I'm getting this exception while reading req.body twice, once in the middleware, and once in the actual function.
the code i'm using:
import 'dart:convert';
import 'dart:typed_data';
import 'package:alfred/alfred.dart';
void main() async {
final app = Alfred();
app.get('/example', (HttpRequest req, HttpResponse res) async {
final body = await req.body;
final jsonBody = jsonEncode(body.toString());
return jsonBody;
}, middleware: [
(HttpRequest req, HttpResponse res) async {
final body = await req.body;
if (body is Uint8List) {
throw AlfredException(401, {'message': 'authentication failed'});
}
}
]);
await app.listen(3000, '127.0.0.1');
}
Route matching is based on regular expressions that are currently built by the RouteMatcher
class each time Alfred receives a request. The current implementation also eagerly evaluates all routes even if the first route will do.
I proposed a change to build the RegExp
when the route is created and avoid creating the same RegExp
over and over again. I also turned the match()
method into a generator to avoid systematic evaluation of all routes.
I also considered changing the way routes ending with a *
segment works but this would be a breaking change. Currently, if the last segment is a *
, the RegExp
is terminated with:
/// Generously match path if last segment is wildcard (*)
/// Example: 'some/path/*' => should match 'some/path'
matcher += '/?.*';
How would you consider the following change? The idea is to match some/path
, some/path/
, some/path/whatever
, but not some/pathbreaking
.
/// Generously match path if last segment is wildcard (*)
/// Example: 'some/path/*' => should match 'some/path' but not 'some/pathology'
matcher += '(?:/.*|)';
I also considered using named groups in the RegExp
and capture parameters at match time to avoid additional parsing done in getParams()
, but I'm not sure how it would play with NotMatchingRouteException
. This would certainly be a breaking change too, although I believe the current implementation is broken because it expects the input Uri to contain as many segment as the route does. This will not work with wildcards eg.
/some/*/thing/:id
should match Uri /some/path/to/thing/abc
but getParams()
will throw (4 segments vs. 5)/some/thing/:id/*
should match Uri /some/thing/abc
but getParams()
will throw (4 segments vs. 3)Leveraging named groups would correctly capture parameters for such routes and getParams()
could be removed altogether. If the route matches, there should be no reason why getParams()
throws.
In LICENSE.md
, the software is referenced as dart_webserver
I reviewed your implementation. As I understood from your posting you want a very modular approach on a backend framework.
I recommend adding a generic "add route" method that's getting used by extension methods get(), post(), patch(), delete()...
. (And optionally making the route matching algorithm exchangeable)
Also the static file serving can be implemented as a "regular route". Currently it's very hardwired into the core.
This pattern opens up Alfred for 3rd party modulare extension and leads to an open design.
I see an own implementation of an unawaited()
method in order to silence the Linter issue.
Pedantic (that's also a dependency in Alfred) already comes with an implementation of that.
Reference: https://pub.dev/documentation/pedantic/latest/pedantic/unawaited.html
It would be great to have a way of passing data around the middlewares or to the final endpoint. A use case could be an authentication middleware that obtains the user information so that it is available when you reach the GET/POST method.
For inspiration, here is how shelf
handles it: https://pub.dev/documentation/shelf/latest/shelf/Request/context.html
Third party packages could use that to integrate with alfred (sessions, cookies, validation...)
please add the exist()
method to RequestStore.
bool exist(String key){
return _data[key] != null;
}
thanks
Currently Alfred only support a few HTTP methods:
enum RequestMethod { get, post, put, delete }
Some are missing, especially PATCH (used in REST) and OPTIONS (for CORS implementation)
Reference: https://developer.mozilla.org/de/docs/Web/HTTP/Methods
Hi All!
My current deployment strategy is to use Aqueducts heroku buildpacks or to use a docker compose script to fire up a dart VM and build a Linux binary, then upload that to my server and use PM2 to keep it running. I put nginx in front of it to manage SSL certs.
There has to be a better way!
In any case I want to document a deployment flow as I feel it's the missing piece.
Any community suggestions about what you do?
While reviewing the routing algorithms I noticed a flaw which makes onNotFound
handler not working as intended.
Alfred looks for any matching route. If he doesn't find any, it's a "not found" for him. This assumption is wrong because any middleware acts like an effective route. This results in using any middleware makes the onNotFound
handler not functional.
Here is an example:
import 'package:alfred/alfred.dart';
Future<void> main() async {
final app = Alfred(
onNotFound: (req, res) => '404'
);
app.all('*', (req, res) { // mandatory middleware
res.headers.set('X-Powered-By', 'Alfred');
});
app.get('/valid', (req, res) => 'Valid route');
await app.listen();
// GET 'http://localhost:3000/notvalid' => no 404
}
I tried to figure out the perfect moment for saying "not found". I realized the location where Alfred checks non-done request and prints out a warning is perfect for being "not found":
/// If you got here and isDone is still false, you forgot to close
/// the response, or your didn't return anything. Either way its an error,
/// but instead of letting the whole server hang as most frameworks do,
/// lets at least close the connection out
///
if (!isDone) {
if (request.response.contentLength == -1) {
print(
"Warning: Returning a response with no content. ${effectiveRoutes.map((e) => e.route).join(", ")}");
}
await request.response.close();
}
app.get( "/cache", (req, res) async {
final toUrl = req.requestedUri.queryParameters["url"];
if (toUrl == null) {
res.statusCode = 400;
return null;
}
final jsonStr = GetStorage().read<String>(toUrl);
Map<String, String> info = jsonStr != null
? (json.decode(jsonStr).cast<String, String>())
: await getting.putIfAbsent(toUrl, () => getFile(toUrl));
if (info["contentType"] != null) {
res.headers.contentType = ContentType.parse(info["contentType"]!);
}
getting.remove(toUrl);
final file = File("${storeDir.path}/${info["md5"]!}");
return file;
},
);
but it didn't work?
I looked for an approach on solving "route declaration passing" to retrieve the route parameters in the handlers. I see we have an architectural problem cause we are only relying on Dart:io's HttpRequest
object instead of having an own "Context".
I would solve this by introducing Alfred's own Request
and Response
classes. Those are 1:1 mirroring the API of Dart:io classes. Additionally we can use theRequest
for transporting necessary information down the pipeline.
Example:
import 'package:alfred/alfred.dart';
class Request {
/// HttpRequest instance from the dart:io library
final HttpRequest nativeRequest;
/// Route declaration that matches the current request
HttpRoute? matchedRoute;
Request({
required this.nativeRequest,
});
// (!) From here everything copied and mapped from the dart:io library (!)
/// The content length of the request body.
///
/// If the size of the request body is not known in advance,
/// this value is -1.
int get contentLength => nativeRequest.contentLength;
/// The method, such as 'GET' or 'POST', for the request.
String get method => nativeRequest.method;
/// The URI for the request.
///
/// This provides access to the
/// path and query string for the request.
Uri get uri => nativeRequest.uri;
// ... rest of HttpRequest
}
This solution has on caveat. It raises your LOC counter 😄 .
Is there any equivalent to Express use method or Shelf mount to handle requests and middlewares in a subroute?
Shelf has the concept of Routers which you can nest however you want.
Here is a simple example project from Creative Bracket in shelf.
Entrypoint of the server app:
https://github.com/graphicbeacon/dart_spa_boilerplate/blob/main/spa_server/bin/spa_server.dart
Then the user api router has a scoped middleware to check authorizations.
That's one of the reasons I propose using shelf under the hood like some other people suggested on Reddit 😅 But I've seen that you want to answer that question in more detail.
BTW, congrats on the project! It seems to be getting some traction 😄
Hello Ryan,
Lines 165 to 166 in 32e3039
fyi
final response = await Response.fromStream(
await http.Client().send(
http.Request("OPTIONS", Uri.parse("http://localhost:$port/test")),
),
);
expect(response.body, "test string");
https://pub.dev/packages/shelf
We really encourage folks to build on top of shelf
. It's designed to be more easily composed.
Let me know if you want to chat!
Hi! Could you explain me the situation when multi threading and isolates can be helpful?
My app have two handlers:
/count
/sql-upsert
/statistic
I have 4 instance of ParserApp. Every instance after run:
sql-upsert
result of parsing as SQL plain text statement,/statistic handler is for big and complex request that run can take up to 30 minutes.
So could the single isolate (that used by default) be bottleneck in my case? And could isolates be helpful?
It just seems to me that all IO falls on the database or am I wrong?
upd:
Could be PostgreSQL connection object is bottleneck? If I am using it from handler above? I am using this driver: https://pub.dev/packages/postgres
Maybe add your framework to FrameworkBenchmarks so you can compare its performance?
Kevin Moo reported the discontinuity of the http_server
package.
https://www.reddit.com/r/dartlang/comments/mkafju/palace_server_side_dart_framework/gtmmbp4?utm_source=share&utm_medium=web2x&context=3
Since this is a package that resembles expressjs very much, I think some implementation of the express-validator would be great. Is there any way to implement something like this?
I like how Alfred can distinguish between several return types.
It would be great to modularize that and add the ability to add custom response handlers. The build in can also get implemented via this API.
Third party addons can add more features this way.
In the README there is the following example:
import 'dart:async';
import 'dart:io';
import 'package:alfred/alfred.dart';
FutureOr exampleMiddlware(HttpRequest req, HttpResponse res) {
// Do work
}
void main() async {
final app = Alfred();
app.all("/example/:id/:name", (req, res) {
req.params["id"] != null; //true
req.params["name"] != null; //true;
}, middleware: [exampleMiddlware]);
await app.listen(); //Listening on port 3000
}
middleware
doesn't fit here, because it does not give a cue on execution order. Maybe introduce before: [exampleMiddleware]
or after: [exampleMiddleware]
for pre/postprocessing?Hey, I figured I'd open a tracking ticket, since I like the way this project is progressing. Most server-side frameworks have ways (built-in or third party) to authenticate users; do you think that's something you'd be interested in building out down the line?
Obviously, there's a cost in terms of complexity and ease of maintenance; on the other hand, authentication is probably the most universal of the currently unmet needs in terms of the Dart server ecosystem.
// [[ ], [ ], [ ]] <-- answer from DB is this format should be: [{}, {}, {}]
static List<Map<dynamic, dynamic>> listFromArrays(lists) {
var result = <Map<dynamic, dynamic>>[];
int index = 0;
for (List list in lists) {
result.add({index: list[0]}); // do not work
// result.add({'foo': list[0]});
}
return result;
}
I am trying to create list of objects like:
[{1:"notifications"}]
with String
as key all works fine. But with int I am getting an error:
2021-09-15 16:06:59.523712 - error - Converting object to an encodable object failed: _LinkedHashMap len:1
2021-09-15 16:06:59.523712 - error - #0 _JsonStringifier.writeObject (dart:convert/json.dart:688:7)
#1 _JsonStringifier.writeList (dart:convert/json.dart:736:7)
#2 _JsonStringifier.writeJsonValue (dart:convert/json.dart:718:7)
#3 _JsonStringifier.writeObject (dart:convert/json.dart:679:9)
#4 _JsonStringifier.writeMap (dart:convert/json.dart:769:7)
#5 _JsonStringifier.writeJsonValue (dart:convert/json.dart:724:21)
#6 _JsonStringifier.writeObject (dart:convert/json.dart:679:9)
#7 _JsonStringStringifier.printOn (dart:convert/json.dart:877:17)
#8 _JsonStringStringifier.stringify (dart:convert/json.dart:862:5)
#9 JsonEncoder.convert (dart:convert/json.dart:262:30)
#10 JsonCodec.encode (dart:convert/json.dart:172:45)
#11 jsonEncode (dart:convert/json.dart:81:10)
#12 _jsonHandler (package:alfred/src/type_handlers/json_type_handlers.dart:9:13)
#13 Alfred._handleResponse (package:alfred/src/alfred.dart:368:36)
#14 Alfred._incomingRequest (package:alfred/src/alfred.dart:290:17)
<asynchronous suspension>
#15 _QueuedFuture.execute (package:queue/src/dart_queue_base.dart:26:16)
<asynchronous suspension>
upd there is issue google/json_serializable.dart#434
But what I should to do if client lib require integer as keyes? https://github.com/lcuis/search_choices/blob/master/example/lib/main.dart#L13
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.