我在这方面取得了一些进展,使用了into_actor().spawn(),但我很难访问异步块中的ctx变量.

首先,我将展示web套接字处理程序的一个编译片段,然后是该处理程序的一个失败片段,然后参考完整的代码示例.

工作片段:

关注比赛 case Ok(ws::Message::Text(text))

/// Handler for `ws::Message`
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(text)) => {
                let future = async move {
                    let reader = processrunner::run_process(text).await;
                    let mut reader = reader.ok().unwrap();
                    while let Some(line) = reader.next_line().await.unwrap() {
                        // ctx.text(line);
                        println!("line = {}", line);
                    }
                };

                future.into_actor(self).spawn(ctx);
            }
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(),
        }
    }
}

Not working snippet with ctx line uncommented.

/// Handler for `ws::Message`
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(text)) => {
                let future = async move {
                    let reader = processrunner::run_process(text).await;
                    let mut reader = reader.ok().unwrap();
                    while let Some(line) = reader.next_line().await.unwrap() {
                         ctx.text(line);
                        println!("line = {}", line);
                    }
                };

                future.into_actor(self).spawn(ctx);
            }
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(),
        }
    }
}

完整的代码片段拆分为两个文件.

主要的rs

//! Simple echo websocket server.
//! Open `http://localhost:8080/ws/index.html` in browser
//! or [python console client](https://github.com/actix/examples/blob/master/websocket/websocket-client.py)
//! could be used for testing.
mod processrunner;
use std::time::{Duration, Instant};

use actix::prelude::*;
use actix_files as fs;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;

/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

/// do websocket handshake and start `MyWebSocket` actor
async fn ws_index(r: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
    println!("{:?}", r);
    let res = ws::start(MyWebSocket::new(), &r, stream);
    println!("{:?}", res);
    res
}

/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket {
    /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
    /// otherwise we drop connection.
    hb: Instant,
}

impl Actor for MyWebSocket {
    type Context = ws::WebsocketContext<Self>;

    /// Method is called on actor start. We start the heartbeat process here.
    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
}

/// Handler for `ws::Message`
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(text)) => {
                let future = async move {
                    let reader = processrunner::run_process(text).await;
                    let mut reader = reader.ok().unwrap();
                    while let Some(line) = reader.next_line().await.unwrap() {
                        // ctx.text(line);
                        println!("line = {}", line);
                    }
                };

                future.into_actor(self).spawn(ctx);
            }
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(),
        }
    }
}

impl MyWebSocket {
    fn new() -> Self {
        Self { hb: Instant::now() }
    }

    /// helper method that sends ping to client every second.
    ///
    /// also this method checks heartbeats from client
    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            // check client heartbeats
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                // heartbeat timed out
                println!("Websocket Client heartbeat failed, disconnecting!");

                // stop actor
                ctx.stop();

                // don't try to send a ping
                return;
            }

            ctx.ping(b"");
        });
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
            // enable logger
            .wrap(middleware::Logger::default())
            // websocket route
            .service(web::resource("/ws/").route(web::get().to(ws_index)))
            // static files
            .service(fs::Files::new("/", "static/").index_file("index.html"))
    })
    // start http server on 127.0.0.1:8080
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

processrunner.rs

extern crate tokio;
use tokio::io::*;
use tokio::process::Command;

use std::process::Stdio;

//#[tokio::main]
pub async fn run_process(
    text: String,
) -> std::result::Result<
    tokio::io::Lines<BufReader<tokio::process::ChildStdout>>,
    Box<dyn std::error::Error>,
> {
    let mut cmd = Command::new(text);
    cmd.stdout(Stdio::piped());

    let mut child = cmd.spawn().expect("failed to spawn command");

    let stdout = child
        .stdout
        .take()
        .expect("child did not have a handle to stdout");

    let lines = BufReader::new(stdout).lines();

    // Ensure the child process is spawned in the runtime so it can
    // make progress on its own while we await for any output.
    tokio::spawn(async {
        let status = child.await.expect("child process encountered an error");

        println!("child status was: {}", status);
    });
    Ok(lines)
}

错误:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/主要的rs:57:41
   |
57 |                   let future = async move {
   |  _________________________________________^
58 | |                     let reader = processrunner::run_process(text).await;
59 | |                     let mut reader = reader.ok().unwrap();
60 | |                     while let Some(line) = reader.next_line().await.unwrap() {
...  |
63 | |                     }
64 | |                 };
   | |_________________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 45:5...
  --> src/主要的rs:45:5
   |
45 | /     fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
46 | |         // process websocket messages
47 | |         println!("WS: {:?}", msg);
48 | |         match msg {
...  |
74 | |         }
75 | |     }
   | |_____^
note: ...so that the types are compatible
  --> src/主要的rs:57:41
   |
57 |                   let future = async move {
   |  _________________________________________^
58 | |                     let reader = processrunner::run_process(text).await;
59 | |                     let mut reader = reader.ok().unwrap();
60 | |                     while let Some(line) = reader.next_line().await.unwrap() {
...  |
63 | |                     }
64 | |                 };
   | |_________________^
   = note: expected  `&mut actix_web_actors::ws::WebsocketContext<MyWebSocket>`
              found  `&mut actix_web_actors::ws::WebsocketContext<MyWebSocket>`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `actix::fut::FutureWrap<impl std::future::Future, MyWebSocket>` will meet its required lifetime bounds
  --> src/主要的rs:66:41
   |
66 |                 future.into_actor(self).spawn(ctx);
   |                                         ^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.

Cargo

[package]
name = "removed"
version = "0.1.0"
authors = ["removed"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/Cargo /reference/manifest.html

[dependencies]
tokio = { version = "0.2", features = ["full"] }
actix = "0.10"
actix-codec = "0.3"
actix-web = "3"
actix-web-actors = "3"
actix-files = "0.3"
awc = "2"
env_logger = "0.7"
futures = "0.3.1"
bytes = "0.5.3"

推荐答案

以下是基本情况.你可能需要在这里或那里做一些工作,但这是有效的.

use actix::prelude::*;
use tokio::process::Command;
use actix_web::{ web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;
use tokio::io::{ AsyncBufReadExt};
use actix::AsyncContext;
use tokio::stream::{ StreamExt};

use tokio::io::{BufReader};

use std::process::Stdio;

#[derive(Message)]
#[rtype(result = "Result<(), ()>")]
struct CommandRunner(String);


/// Define HTTP actor
struct MyWs;

impl Actor for MyWs {
    type Context = ws::WebsocketContext<Self>;
}

#[derive(Debug)]
struct Line(String);

impl StreamHandler<Result<Line, ws::ProtocolError>> for MyWs {
    fn handle(
        &mut self,
        msg: Result<Line, ws::ProtocolError>,
        ctx: &mut Self::Context,
    ) {
        match msg {
            Ok(line) => ctx.text(line.0),
            _ => () //Handle errors
        }
    }
}

/// Handler for ws::Message message
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
    fn handle(
        &mut self,
        msg: Result<ws::Message, ws::ProtocolError>,
        ctx: &mut Self::Context,
    ) {
        match msg {
            Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
            Ok(ws::Message::Text(text)) => {
                ctx.notify(CommandRunner(text.to_string()));
            },
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            _ => (),
        }
    }
}

impl Handler<CommandRunner> for MyWs {
    type Result = Result<(), ()>;
    fn handle(&mut self, msg: CommandRunner, ctx: &mut Self::Context) -> Self::Result  {
        let mut cmd = Command::new(msg.0);

        // Specify that we want the command's standard output piped back to us.
        // By default, standard input/output/error will be inherited from the
        // current process (for example, this means that standard input will
        // come from the keyboard and standard output/error will go directly to
        // the terminal if this process is invoked from the command line).
        cmd.stdout(Stdio::piped());

        let mut child = cmd.spawn()
            .expect("failed to spawn command");

        let stdout = child.stdout.take()
            .expect("child did not have a handle to stdout");

        let reader = BufReader::new(stdout).lines();
        
        // Ensure the child process is spawned in the runtime so it can
        // make progress on its own while we await for any output.
        let fut = async move {
            let status = child.await
                .expect("child process encountered an error");
    
            println!("child status was: {}", status);
        };
        let fut = actix::fut::wrap_future::<_, Self>(fut);
        ctx.spawn(fut);
        ctx.add_stream(reader.map(|l| Ok(Line(l.expect("Not a line")))));
        Ok(())
    }
}

async fn index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
    let resp = ws::start(MyWs {}, &req, stream);
    println!("{:?}", resp);
    resp
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/ws/", web::get().to(index)))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}

ls看起来像这样.

Example

Rust相关问答推荐

什么是谓词的简短和简洁类型

当为a Self:IntoIterator设置trait bind `时,获取`a T `不是迭代器"&'"<'>&'

使用pyo3::Types::PyIterator的无限内存使用量

在不重写/专门化整个函数的情况下添加单个匹配手臂到特征的方法?

在Rust中显式装箱受生存期限制的转换闭包

有没有办法避免在While循环中多次borrow `*分支`

我如何制作一个变异迭代器来锁定内部数据直到删除?

为什么Option类型try块需要类型注释?

为什么BufReader实际上没有缓冲短寻道?

为什么HashMap::get和HashMap::entry使用不同类型的密钥?

我应该如何表达具有生命周期参数的类型的总排序,同时允许与不同生命周期进行比较?

类型生命周期绑定的目的是什么?

`use` 和 `crate` 关键字在 Rust 项目中效果不佳

如何在 Rust 中将函数项变成函数指针

Rust并发读写引起的死锁问题

&self 参数在 trait 的功能中是必需的吗?

如何将 Rust 字符串转换为 i8(c_char) 数组?

从现有系列和 map 值创建新系列

如何使用 rust bindgen 生成的 std_vector

当引用不再被borrow 时,Rust 不会得到它