How do I register multiple handlers for a HttpServer, in Dart? - dart

(I'm using the new lib v2 version of dart:io.)
I'd like to register multiple handlers for an HttpServer, specifically a WebSocketTransformer and other arbitrary handlers. Something like this:
// pseudo-code
var server = HttpServer;
server.register('/foo', someHandlerFunction); // 1
server.register('/bar', someOtherHandlerFunction); // 2
server.register('/ws', webSocketHandler); // 3
If #1 matches, then #2 isn't tested, and so on. So, it's greedy.
I've seen samples with just one handler. How do I register many handlers? Thanks in advance!

New answer: Use the route package: http://pub.dartlang.org/packages/route
Here's your example using route's serve() method:
HttpServer.bind('127.0.0.1', 8889).then((server) {
var router = new Router(server)
..serve('/ws').transform(new WebSocketTransformer()).listen(handleWebSocket)
..serve('/foo').listen((req) {
req.response..addString('foo')..close();
});
});
Router automatically catches unhandled requests and sends a 404, though soon you'll be able to override that with a defaultStream you can listen to.
Router also supports filters, useful for logging, auth, compression, etc.:
HttpServer.bind('127.0.0.1', 8889).then((server) {
var router = new Router(server)
..filter(new RegExp(r'/.*'), (req) {
//log all requests
_logger.info("request: $req");
return new Future.immediate(true); // keep processing request
})
..filter(new Regexp(r'/secure/.*'), (req) {
// check authentication asynchronously
return getUserFromRequest(req).then((user) {
if (user == null) {
sendRedirect('/login'); // sendRedirect coming soon
return false; // stop processing request
} else {
return true; // keep processing
}
});
})
..serve(/* ... */ );
});

Here's how the API docs recommend to register a WebSocket handler:
server
.where((request) => request.uri.path == "/ws")
.transform(new WebSocketTransformer()).listen((webSocket) => ...);
However, the server is a single-subscription stream. Once a listen is attached, you can't attach other listeners.
What I really want is for something to look at an event, decide if it can handle it, and if so then route it to another stream. Otherwise, pass it along. This way, the event (in this case an HttpRequest object) is passed along a chain until it's handled.
I built a TakeAndRoute class that extends StreamEventTransformer. The TakeAndRoute uses a function to determine if it should grab the event and route it to another stream, or simply forward it along.
Here's what I came up with:
import 'dart:io';
import 'dart:async';
handleWebSocket(WebSocket webSocket) {
webSocket.listen((event) {
if (event is MessageEvent) {
/* Handle message. */
} else if (event is CloseEvent) {
/* Handle closed. */
}
});
}
typedef bool ShouldTake(e);
typedef void RouteTo(Stream stream);
typedef void HandleEvent(e);
class TakeAndRoute<S, T> extends StreamEventTransformer<S, T> {
ShouldTake shouldTake;
RouteTo routeTo;
StreamController controller = new StreamController();
HandleEvent handler;
TakeAndRoute(this.shouldTake, {this.routeTo, this.handler}) {
if (routeTo != null) routeTo(controller.stream);
}
handleData(event, StreamSink sink) {
print("handling");
if (shouldTake(event)) {
if (routeTo != null) {
controller.add(event);
}
if (handler != null) {
handler(event);
}
} else {
sink.add(event);
}
}
}
main() {
HttpServer.bind('127.0.0.1', 8888)
.then((HttpServer server) {
server
.transform(new TakeAndRoute<HttpRequest, HttpRequest>(
(req) => req.uri.path == '/ws',
routeTo: (stream) => stream.transform(new WebSocketTransformer()).listen(handleWebSocket)))
.transform(new TakeAndRoute<HttpRequest, HttpRequest>(
(req) => req.uri.path == '/foo',
handler: (req) {
print('got foo');
req.response.addString("foo");
req.response.close();
}))
.listen((req) {
print("got 404 for ${req.uri}");
req.response.statusCode = 404;
req.response.close();
});
});
}
Admittedly, this might be overkill.

Here is a more manual, but shorter way to do it:
HttpServer.bind('127.0.0.1', 8889)
.then((HttpServer server) {
var sc = new StreamController();
sc.stream.transform(new WebSocketTransformer()).listen(handleWebSocket);
server.listen((HttpRequest request) {
print("new connection from ${request.uri.scheme} ${request.uri}");
// See https://code.google.com/p/dart/issues/detail?id=8825
//if (request.uri.scheme == 'ws') {
if (request.uri.path == '/ws') {
sc.add(request);
} else if (request.uri.path == '/foo') {
request.response.addString('foo');
request.response.close();
} else {
print("got 404 for ${request.uri}");
request.response.statusCode = 404;
request.response.close();
}
});
});
Notice how I had to create a StreamController so I could pump events to WebSocketTransformer

Related

How to set timeout function for RawDatagramSocket in Dart

I have a Dart application which receives udp datagram every 10ms. I want to know when data stream is stopped. I have a RawDatagramSocket as _udpSocket I can listen datagram by using this function:
RawDatagramSocket? _udpSocket;
Future<void> bindSocket() async {
_udpSocket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port);
setTimeout();
}
Future<void> listen() async {
_udpSocket?.listen((event) async {
Datagram? d = _udpSocket?.receive();
if (d == null) {
return;
}
//handle received data
});
}
And also I have a function to handle timeout:
void setTimeout() {
//if there is no data receiving in a couple of cycle, time out will be triggered.
//1 cycle == 10ms, I want timeout to be triggered after 10 cycles. (100ms)
_udpSocket?.timeout(timeoutDuration, onTimeout: (sink) {
//Handle time out
});
}
I am able to receive and process incoming data, but timeout function is not working.
Is there anything wrong with my code, or is there any other method to do what I want.
I figured it out,I updated the listen function. Here is the update for those who would need it:
final Duration timeoutDuration = const Duration(milliseconds: 100);
#override
Future<void> listen() async {
_udpSocket?.timeout(timeoutDuration, onTimeout: ((sink) {
//do your work when data stream closed
})).listen((event) async {
Datagram? d = _udpSocket?.receive();
if (d == null) {
return;
}
//handle received data
});
}
I hope it will be useful.

Dart yield stream events from another stream listener

I have a function that generates stream of specific events. Now I have a stream coming from storage service which has its own events. Looking for a way to yield my events when something changes in the storage stream.
This code snippet doesn't do the trick.
Stream<BlocState> mapEventToState(
BlocEvent event,
) async* {
if (event is UploadData) {
yield UploadDataProgress(progress: 0.0);
final Storage storage = Storage();
final Stream<StorageEvent> upload = storage.upload(event.data);
upload.listen((StorageEvent storageEvent) async* {
print('***** Listener: ${storageEvent.type} - ${storageEvent.progress}');
if (storageEvent.type == StorageEventType.error) {
yield UploadDataError(errorMessage: storageEvent.error);
}
if (storageEvent.type == StorageEventType.success) {
yield UploadDataSuccess();
}
if (storageEvent.type == StorageEventType.progress) {
yield UploadDataProgress(progress: storageEvent.progress);
}
});
}
}
Output:
The debug print works but the events are not sent to listeners.
***** Listener: StorageEventType.progress - 0.01924033836457124
***** Listener: StorageEventType.progress - 0.044581091468101464
***** Listener: StorageEventType.progress - 0.6986233206170177
***** Listener: StorageEventType.progress - 1.0
Your yields are yielding from the anonymous function (StorageEvent storageEvent) async* { rather than from mapEventToState.
Simply replacing the listen() with an await for should work.
Stream<BlocState> mapEventToState(
BlocEvent event,
) async* {
if (event is UploadData) {
yield UploadDataProgress(progress: 0.0);
final Storage storage = Storage();
final Stream<StorageEvent> upload = storage.upload(event.data);
await for (StorageEvent storageEvent in upload) {
print('***** Listener: ${storageEvent.type} - ${storageEvent.progress}');
if (storageEvent.type == StorageEventType.error) {
yield UploadDataError(errorMessage: storageEvent.error);
}
if (storageEvent.type == StorageEventType.success) {
yield UploadDataSuccess();
}
if (storageEvent.type == StorageEventType.progress) {
yield UploadDataProgress(progress: storageEvent.progress);
}
}
}
}
For what it's worth. I had a similar problem, where I was subscribing to Firebase snapshots and trying to yield events based on the document data.
If I used the await-for paradigm, there was no subscription handle I would get. As a result, it became painful to stop reading the stream when I wanted to close the BLoC.
I found a round-about solution to this.
Stream<BlocState> mapEventToState(BlocEvent event) async* {
if (event is FetchEvent) {
yield LoadingState();
_subscription = SomeStream(event.someKey).listen((snapshot) {
add(OnSnapshotEvent(snapshot));
});
}
if (event is OnSnapshotEvent) {
if (SomeCondition(event.snapshot)) {
yield SomeResultState();
} else {
yield SomeOtherState();
}
}
}
This way, I have a handle to the subscription, so I can always nicely cleanup.

Dartlang server call my function more than once

I'm sending a data to the server like this:
save(){
var el = this.parent.nodes;
print(el);print(el.length);
request = new HttpRequest();
if(el.length == 1) print('No lines to save!');
else
{
var opt = el[i].shadowRoot.nodes[0].options[el[i].shadowRoot.nodes[0].selectedIndex].text;
print(this.parent.nodes.length);
for(var i=1; i < el.length; i++)
{
orderLines.add({
'poid': orderHeader[0]['OrderID'],
'ponum': orderHeader[0]['onum'],
'lnum' : i.toString(),
'itmid' :el[i].shadowRoot.nodes[0].value,
'icode' : opt,
'qty': el[i].shadowRoot.nodes[1].value,
'rqty': 0,
'bqty': el[i].shadowRoot.nodes[1].value,
'iqty': 0,
'biqty': el[i].shadowRoot.nodes[1].value,
'price': el[i].shadowRoot.nodes[2].value,
'rdd': orderHeader[0]['rdd'],
'eta': '',
'flag': 0
});
print(orderLines);
request.onReadyStateChange.listen(onData_save);
request.open('POST', host+'/sPO');
request.send(JSON.encode(orderLines));
}
}
}
and my server side function is:
void main() {
connections = new List<WebSocket>();
HttpServer.bind(HOST, PORT).then((HttpServer server) {
print('Server listening on port ${PORT}.');
server.listen((HttpRequest request) {
if (WebSocketTransformer.isUpgradeRequest(request)) {
WebSocketTransformer.upgrade(request).then(handleWS);
} else gotMSG(request);
});
});
}
handleWS(WebSocket ws){
connections.add(ws);
print('Client connected, there are now ${connections.length} client(s) connected.');
ws.listen((String message) {
for (WebSocket connection in connections) {
connection.add(message);
}
},
onDone: () {
connections.remove(ws);
print('Client disconnected, there are now ${connections.length} client(s) connected.');
});
}
void gotMSG(HttpRequest request) {
switch (request.method) {
case 'POST':
handlePost(request);
break;
case 'OPTIONS':
handleOptions(request);
break;
default:
defaultHandler(request);
}
}
void serveRequest(HttpRequest request){
print('Listening for GET and POST on http://$HOST:$PORT');
request.response.statusCode = HttpStatus.FORBIDDEN;
request.response.reasonPhrase = "WebSocket connections only";
request.response.close();
}
void handlePost(HttpRequest req) {
HttpResponse res = req.response;
switch (req.uri.path) {
case '/login': login(req); break;
...
case '/sPO': savePO(req); break;
default: break;
}
}
The /sPO => savePO is executed once if the order sent is of one line only, but if n lines in the order, the function is executed more than once, could not find a pattern for that,
In the SavePO I used oracledart pub, so thought something wrong in it, and tried postgresql pub, but got same results, the savePO function is:
void savePO(HttpRequest req){
HttpResponse res = req.response;
addCorsHeaders(res);
print('${req.method}: ${req.uri.path}');
Future future() => new Future.value(true);
req.listen((List<int> buffer) {
var theDataLines = JSON.decode(new String.fromCharCodes(buffer));
print(theDataLines);
connect(db).then((conn) {
for (var theData in theDataLines)
conn.execute("""
insert into pol
(poid,ponum,lnum,itmid,icode,qty,rqty,bqty,iqty,biqty,price,rdd, eta, flag)
values (#poid,#ponum,#lnum,#itmid,#icode,#qty,#rqty,#bqty,#iqty,#biqty,#price,
to_timestamp(#rdd,'YYYY-MM-DD'), to_timestamp(#eta,'YYYY-MM-DD'), #flag)
""",
{
'poid': theData['poid'],
'ponum': theData['ponum'],
'lnum' : theData['lnum'],
'itmid' : theData['itmid'],
'icode' : theData['icode'],
'qty': theData['qty'],
'rqty': theData['rqty'],
'bqty': theData['bqty'],
'iqty': theData['iqty'],
'biqty': theData['biqty'],
'price': theData['price'],
'rdd': theData['rdd'].toString(),
'eta': theData['eta'].toString(),
'flag': theData['flag']
})
.then((_)=>conn.query('commit').toList().then((rows) {print('committed');}))
.then((_){
res.write('done');
res.close();
});
}); // END of SQL
}, onError: printError); // End of server listen
} // END of function
I even tried to change the:
case '/sPO': savePO(req); break;
to be
case '/sPO': print(1); break;
the it printed the 1, 4 times after sending an order of 6 lines!!
It's hard to see for me what you actually try to accomplish.
The problem is very probably your save() method. You wrote how it behaves but not much about what you try to accomplish?
Why don't you put more lines into one JSON string and post them together in one request?
You create one request instance and call send repeatedly on this one request instance.
You also register the onReadyStateChange handler more than once on the same request object which results in onData_save being called several times when the event occurs just once.
I think you should either move request = new HttpRequest(); down just before
request.open('POST', host+'/sPO');
request.send(JSON.encode(orderLines));
or better move request.onReadyStateChange.listen(onData_save); up to request = new HttpRequest();,
add all orderlines into one JSON and call
request.open('POST', host+'/sPO');
request.send(JSON.encode(orderLines));
after the for loop.
Another problem I see is that you do a fire and forget. What if the send request fails for some reason?
I would create a sendJSON method that returns a future (with a Completer which completes on onDone and completeError when something goes wrong.
When you want to create more than one request in your save() you can use something like
// create your JSON
var futures = [];
for(i = 0; i < 5; i++) {
futures.add(sendData(myJson)); // collects the futures returned from sendData
}
// executes all futures and waits for all to respond and then returns another future
return Future.wait()
.then((results) {
results.forEach((r) {
// check result
});
});

How to pass message to isolate and handle error

I am trying to use dart isolate library to improve my application performance.
Look at following code:
import 'dart:isolate';
import 'package:dbcrypt/dbcrypt.dart';
main() {
var pwConPort = new ReceivePort();
pwConPort.listen((data) {
print(data);
pwConPort.close();
}, onError: (err) {
print(err);
});
Isolate.spawn(generatePasswordConcurrency, pwConPort.sendPort);
}
void generatePasswordConcurrency(SendPort sendPort) {
sendPort.send(_generateHashPassword('Passsowr1222!'));
}
String _generateHashPassword(String password) {
var regex = new RegExp(r'^.*(?=.{7,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).*$');
if (!regex.hasMatch(password)) {
throw new StateError('Errors');
}
return new DBCrypt().hashpw(password, new DBCrypt().gensalt());
}
Everything works fine but i can only pass a static password, or better to say, i don't know, how to pass something dynamically. Here you can see, password is hardcoded, but i want to pass a variable for example.
void generatePasswordConcurrency(SendPort sendPort) {
sendPort.send(_generateHashPassword('Passsowr1222!'));
}
If the method _generateHashPassword will throw an error, how can I handling this error? I try to catch the error on listen method from ReceivePort
pwConPort.listen((data) {
print(data);
pwConPort.close();
}, onError: (err) {
print(err);
});
but still got unhandling exceptions message.
Observatory listening on http://127.0.0.1:51433
in ShutdownIsolate: Unhandled exception:
Bad state: Errors
#0 _generateHashPassword (file:///D:/Dart/samples/bin/isolate_error.dart:26:9)
#1 generatePasswordConcurrency (file:///D:/Dart/samples/bin/isolate_error.dart:19:40)
#2 _startIsolate.isolateStartHandler (dart:isolate-patch/isolate_patch.dart:221)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)
Conclusion my question:
How can I pass a variable to called method on isolate?
How can I handling error on isolate?
First of all,
Isolate are not thread, they are independant process more like a fork() than a thread
dartApi: Isolate
Concurrent programming using isolates:
independent workers that are similar to threads but don't share memory, communicating only via
messages.
So, you can't access to the same variable than your parent process. It's a choice made by the dart team, because it's a mechanism usable when you compile your dart code in js. So it need to be possible in JS
How can I pass a variable to called method on isolate?
To do this, you need to see ReceivePort() like a unidirectionnal way of communication, so to pass variable in two way, you need two.
So on you main process:
pwConPort.listen((data) {
if (isolateSendPort == null && data is SendPort) {
isolateSendPort = data; // Receive the communication object of the isolate
isolateSendPort.send("Passsowr1222!");
} else {
print("Generated password: ${data}");
pwConPort.close();
}
}, onError: (err) {
print("SendPortError: ${err}");
});
});
In you isolate entry point :
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
// code ....
});
Note: be careful of what message you send. message send between one process and another need to respect some rules
DartApi: SendPort
The content of message can be: primitive values (null, num, bool,
double, String), instances of SendPort, and lists and maps whose
elements are any of these. List and maps are also allowed to be
cyclic.
How can I handling error on isolate?
Isolate get one method to listen throw error send by the isolate : addErrorListner
That is a useful function.
BUT ! this method is not implement in every plate-forme, so you need to do this in a others.
The way i chose is to send 2 SendPort in the entry point function :
One for the communication
One for the error.
So the spawn function looks like :
Isolate.spawn(generatePasswordConcurrency, [pwConPort.sendPort, errorPort.sendPort])
and the generatePasswordConcurrency :
void generatePasswordConcurrency(List<SendPort> commList) {
var sendPort = commList[0];
var errorPort = commList[1];
var isolateConPort = new ReceivePort();
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
try {
sendPort.send(_generateHashPassword(data));
} catch (e) {
errorPort.send("error: ${e.toString()}");
}
});
}
Here the full code :
import 'dart:isolate';
import 'package:dbcrypt/dbcrypt.dart';
main() {
var pwConPort = new ReceivePort();
var errorPort = new ReceivePort();
SendPort isolateSendPort = null;
Isolate.spawn(generatePasswordConcurrency, [pwConPort.sendPort, errorPort.sendPort])
.then((Isolate pcs) {
errorPort.listen((err) {
print("Error: ${err}");
pwConPort.close();
errorPort.close();
});
print(pcs);
pwConPort.listen((data) {
if (isolateSendPort == null && data is SendPort) {
isolateSendPort = data;
isolateSendPort.send("Passsowr1222!");
} else {
print("Generated password: ${data}");
pwConPort.close();
errorPort.close();
//pcs.kill();
}
}, onError: (err) {
print("SendPortError: ${err}");
});
});
}
void generatePasswordConcurrency(List<SendPort> commList) {
var sendPort = commList[0];
var errorPort = commList[1];
var isolateConPort = new ReceivePort();
sendPort.send(isolateConPort.sendPort);
isolateConPort.listen((data) {
try {
sendPort.send(_generateHashPassword(data));
} catch (e) {
errorPort.send("error: ${e.toString()}");
}
});
}
String _generateHashPassword(String password) {
var regex = new RegExp(r'^.*(?=.{7,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).*$');
if (!regex.hasMatch(password)) {
throw new StateError('Errors');
}
return new DBCrypt().hashpw(password, new DBCrypt().gensalt());
}

How can I access the result of the response of HttpRequest in Dart?

After many attempts to get the content of the response in HttpRequest, I failed completely to know or understand why I can't have what I want, and I must mention that I can log and manipulate the response only inside an onReadyStateChange (onLoad and onLoadEnd are giving me the same results!), but I really want that value outside the callback.
Here is the part of code that I'm stuck with
Map responsData;
req=new HttpRequest()
..open(method,url)
..send(infojson);
req.onReadyStateChange.listen((ProgressEvent e){
if (req.readyState == HttpRequest.DONE ){
if(req.status == 200){
responsData = {'data': req.responseText};
print("data receaved: ${ req.responseText}");
//will log {"data":mydata}
}
if(req.status == 0){
responsData = {'data':'No server'};
print(responsData );
//will log {"data":No server}
}
}
});
//anything here to get responsData won't work
You have to assign an onLoad callback before you call send.
I'm not sure what you mean with only inside an onReadyStateChange.
Maybe you want to assign the responseText to a variable outside the the callback.
Create a method:
Future<String> send(String method, String url, String infojson) {
var completer = new Completer<String>();
// var result;
req=new HttpRequest()
..open(method,url)
..onLoad.listen((event) {
//print('Request complete ${event.target.reponseText}'))
// result = event.target.responseText;
completer.complete(event.target.responseText);
})
..send(infojson);
return completer.future;
}
and call this method like
var result;
send(method, url).then(
(e) {
// result = e;
print('Request complete ${e}'));
});

Resources