I'm trying to write a Udp Client Actor using Actix. I've followed this example, UDP-Echo, but I can't seem to send a message to the server using the UdpFramed tokio struct.
Here's what I have so far, this is the Udp Client Actor implementation
use std::collections::HashMap;
use std::net::{SocketAddr};
use actix_rt::net::UdpSocket;
use actix::{Actor, Addr, AsyncContext, Context, Handler, StreamHandler, Message};
use actix::io::SinkWrite;
use actix_web::web::{Bytes, BytesMut};
use futures_util::stream::{SplitSink};
use futures_util::StreamExt;
use log::info;
use serde_json::Value;
use tokio_util::codec::BytesCodec;
use tokio_util::udp::UdpFramed;
use crate::rosclient::messages::Subscribe;
use std::io::Result;
mod messages;
type SinkItem = (Bytes, SocketAddr);
type UdpSink = SplitSink<UdpFramed<BytesCodec, UdpSocket>, SinkItem>;
pub struct UdpClientActor {
pub address: SocketAddr,
pub sink: SinkWrite<SinkItem, UdpSink>,
}
impl UdpClientActor {
pub fn start(udp: UdpSocket, address: SocketAddr) -> Addr<UdpClientActor> {
let framed = UdpFramed::new(udp, BytesCodec::new());
let (split_sink, split_stream) = framed.split();
UdpClientActor::create(|ctx| {
ctx.add_stream(split_stream.filter_map(
|item: Result<(BytesMut, SocketAddr)>| async {
item.map(|(data, sender)| UdpPacket(data, sender)).ok()
},
));
UdpClientActor {
address,
sink: SinkWrite::new(split_sink, ctx),
}
})
}
}
impl Actor for UdpClientActor {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
let mut hashmap = HashMap::new();
hashmap.insert(String::from("topic"), Value::String(String::from("/client_count")));
let subscription = Subscribe {
id: Default::default(),
op: "subscribe".to_string(),
extra: hashmap
};
ctx.notify(subscription);
}
}
#[derive(Message)]
#[rtype(result = "()")]
struct UdpPacket(BytesMut, SocketAddr);
impl StreamHandler<UdpPacket> for
UdpClientActor {
fn handle(&mut self, item: UdpPacket, _ctx: &mut Self::Context) {
println!("Received: ({:?}, {:?})", item.0, item.1);
self.sink.write((item.0.into(), item.1)).unwrap();
}
}
impl actix::io::WriteHandler<std::io::Error> for UdpClientActor {}
impl Handler<Subscribe> for UdpClientActor {
type Result = ();
fn handle(&mut self, msg: Subscribe, _ctx: &mut Self::Context) -> Self::Result {
let js = serde_json::json!(msg).to_string();
let _ = self.sink.write((Bytes::from(msg.to_string()), self.address));
info!("Subscribing to topic {}", js);
}
}
My main function creates the udp socket and spawns the actor.
fn main() {
////////////////////////////////////////////////////////////////////////////
let fut = async {
////////////////////////////////////////////////////////////////////////////
/////////// UDP_ACTOR
let sock = tokio::net::UdpSocket::bind("0.0.0.0:9091").await.unwrap();
let remote_addr = "172.30.89.169:9091".parse::<SocketAddr>().unwrap();
// let message = b"{ \"op\": \"subscribe\", \"topic\": \"/client_count\"}";
let _ = sock.connect(remote_addr).await;
// sock.send(message).await.unwrap();
let _udp_client = UdpClientActor::start(sock, remote_addr);
};
actix_rt::Arbiter::new().spawn(fut);
// system.block_on(fut);
system.run().unwrap();
}
If I remove the comments on
let message = b"{ \"op\": \"subscribe\", \"topic\": \"/client_count\"}";
and
sock.send(message).await.unwrap();
I can at least check that the server can actually receive messages. So I know that the problem must lie in my implementation of the actor. I do have another one which uses the LinesCodec instead of the BytesCodec, which follows the exact same implementation. Only difference is that SinkWrite becomes this:
SinkWrite<(String, SocketAddr), SplitSink<UdpFramed<codec::LinesCodec>,
(String, SocketAddr)>>
Here is my Cargo.toml for reference.
[package]
name = "local_websocket_client"
version = "0.1.0"
edition = "2018"
[dependencies]
actix="0.12"
actix-codec = "0.4"
actix-rt = "2.5"
bytestring = "1.0"
serde = {version="1.0", features=["serde_derive"]}
log = "0.4"
env_logger = "0.9.0"
chrono = "0.4"
dashmap = "4.0"
futures = "0.3"
openssl = "0.10"
tokio = { version = "1", features = ["full"] }
actix-web = "4.0.0-beta.15"
futures-util = "0.3"
tokio-util = { version="0.6", features=["net", "codec"] }
tokio-udp = "0.1.6"
bytes= { version="0.6", features=["serde"] }
[dependencies.awc]
features = ["openssl"]
version = "3.0.0-beta.9"
[dependencies.serde_json]
features = ["default"]
version = "1.0"
[dependencies.uuid]
features = ["v4", "serde", "v5"]
version = "0.8"
There are some extra crates there because I'm running 2 other websocket clients on the same application.
I would really appreciate some help on this matter.
Thank you
Solved it by wrapping the UdpSocket in an Arc and keeping the reference in the actor for later use. Using the socket to write messages works. The split stream used for the streamhandler needs no change, as it works as expected.
I have a problem with a script I wrote for GMail. I want to reformat and then forward every mail from a specific sender to a Mail2SMS provider.
When I receive the mail it will be tagged with an label - "Alarmanlage".
The formating works fine, but my script don't use the last mail from the sender the format and forward. I don't find the failure to get the last mail of a thread.
I hope someone can help!
function sendsms(){
var label = GmailApp.getUserLabelByName("Alarmanlage");
if(label == null){
GmailApp.createLabel('Alarmanlage');
}
else{
var threads = label.getThreads(0,1);
for (var i = 0; i < threads.length; i++) {
var count = threads[i].getMessageCount();
var message = threads[i].getMessages()[count];
var from = message.getFrom();
var subject = message.getSubject();
var date = message.getDate();
var msg = message.getBody();
msg = msg.replace("<div><br>","");
var endofmsg = msg.indexOf("<br>");
msg = msg.substring(0, endofmsg);
GmailApp.sendEmail("xxxh#mail2sms.com", msg)
message.moveToTrash()
}
label.removeFromThreads(threads);
}
}
For your var message use :
var thread = threads[i];
var messages = thread.getMessages();
var message= messages[thread.getMessageCount()-1];
Stéphane
When I browserify my coffeescript code the first time it runs fine. However, when I make a change to my source code and watchify tries to re-run the bundling, coffeeify seems to want to run coffeescript against my javascript - based on this error I get from gulp:
events.js:72
throw er; // Unhandled 'error' event
^
SyntaxError: reserved word 'var' while parsing file: /Volumes/dev/app/frontend/editor/main.coffee
This is my Gulpfile:
var gulp = require('gulp');
var gutil = require('gulp-util');
var watchify = require('watchify');
var browserify = require('browserify');
var coffeeify = require('coffeeify');
var source = require('vinyl-source-stream');
var stringify = require('stringify');
var _ = require('underscore');
var browserifyOpts = {
basedir: "./app/frontend/editor",
debug: true,
extensions: ['.coffee'],
entries: ['./main.coffee']
};
var opts = _.extend({}, watchify.args, browserifyOpts);
var bundler = browserify(opts);
var watch = watchify(bundler);
watch.on('update', bundle);
watch.on('log', gutil.log);
function bundle() {
var b = function() {
return bundler
.transform(stringify(['.html']))
.transform('coffeeify')
.bundle()
.pipe(source('main.js'))
.pipe(gulp.dest('app/assets/javascripts/bundles'));
};
return b();
}
gulp.task('default', ['browserify-main']);
gulp.task('browserify-main', bundle);
How can I make the bundling process work with watchify too?
Move your transforms outside of the bundle function:
var bundler = browserify(opts);
bundler.transform(stringify(['.html']))
bundler.transform('coffeeify')
var watch = watchify(bundler);
watch.on('update', bundle);
watch.on('log', gutil.log);
function bundle() {
var b = function() {
return bundler
.bundle()
.pipe(source('main.js'))
.pipe(gulp.dest('app/assets/javascripts/bundles'));
};
return b();
}
I am getting an error Please select an active sheet first. (line 4, file "Code") when trying to publish my apps script dashboard app based on this tutorial. I've read through some other posts about setting an active sheet but it only produced more errors. I'm at a loss as to how to correct this. Can someone please tell me what I'm doing wrong?
function doGet() {
var ss = SpreadsheetApp.openById('<sheetid>');
var data = ss.getDataRange();
var ageFilter = Charts.newNumberRangeFilter().setFilterColumnIndex(1).build();
var nameFilter = Charts.newStringFilter().setFilterColumnIndex(0).build();
var genderFilter = Charts.newStringFilter().setFilterColumnIndex(2).build();
var tableChart = Charts.newTableChart()
.setDataViewDefinition(Charts.newDataViewDefinition().set([0,1,2]))
.build();
var pieChart = Charts.newPieChart()
.setDataViewDefinition(Charts.newDataViewDefinition().set([0,,2]))
.build();
var dashboard = Charts.newDashboardPanel().setDataTable(data)
.bind([ageFilter,nameFilter,genderFilter], [tableChart, pieChart])
.build();
var app = UiApp.createApplication();
var filterPanel = app.createVerticalPanel();
var chartPanel = app.createHorizontalPanel();
filterPanel.add(ageFilter).add(nameFilter).add(genderFilter).setSpacing(10);
chartPanel.add(tableChart).add(pieChart).setSpacing(10);
dashboard.add(app.createVerticalPanel().add(filterPanel).add(chartPanel));
app.add(dashboard);
return app;
}
var ss = SpreadsheetApp.openById('<sheetid>');
var sheet = ss.getSheetByName('sheetNameHere');///////Need to get the sheet, not just the whole work book.
var data = sheet.getDataRange();
I want to post some variables to a new window.
The receiving c# will then generate a CSV which will stream as a download.
In flex this used to be achieved using loadVars and specifying _blank as the target.
I currently use the following:
var myRequest:URLRequest = new URLRequest(url);
var myLoader:URLLoader = new URLLoader();
var myVariables:URLVariables = new URLVariables();
myVariables.CurrentActiveUserID = currentUserID
myVariables.ReportRuleListID = SingleChartID
myRequest.method = URLRequestMethod.POST;
myRequest.data = myVariables;
myLoader.load(myRequest);
But it does not seem to support targeting of new windows.
Any ideas.
Please and thank you.
I finally sorted it with:
private function sendAndLoadCSVData():void {
var swfURL:String = this.loaderInfo.url;
swfURL = swfURL.substr(0,swfURL.lastIndexOf("/") + 1);
var tempDom:Array = swfURL.split("/");
var domURL:String = tempDom.slice(0,3).join("/") + "/";
var url:String = swfURL + "../Reporting/ExportChartCSV.aspx"
// var post_variable:LoadVars = new LoadVars();
var myRequest:URLRequest = new URLRequest(url);
var myLoader:URLLoader = new URLLoader();
var myVariables:URLVariables = new URLVariables();
myVariables.CurrentActiveUserID = currentUserID
myVariables.ReportRuleListID = SingleChartID
myRequest.method = URLRequestMethod.POST;
myRequest.data = myVariables;
navigateToURL(myRequest, '_blank')
//myLoader.load(myRequest);
// Alert.show(url);
}