如何从产生大量数据的慢速处理侧线程流式传输超级请求的主体?

时间:2019-06-03 22:47:19

标签: rust rust-tokio hyper

我有一个程序,它会缓慢生成数据(可以说它是计算密集型的,就像计算pi的数字一样)。它产生很多数据;每个响应可以是1GiB,不会容纳在内存中,并且必须必须按需生成。我正在使用hyper编写Web服务以在请求时生成内容。

我们跳过样板(service_fnServer::bind)。

缓慢生成数据的API可能类似于

use std::io;

impl SlowData {
    fn new(initial: &str) -> SlowData {
        unimplemented!()
    }

    fn next_block(&self) -> io::Result<&[u8]> {
        unimplemented!()
    }
}

type ResponseFuture = Box<Future<Item = Response, Error = GenericError> + Send>;

fn run(req: Request) -> ResponseFuture {
    // spawn a thread and:
    // initialize the generator
    // SlowData::new(&req.uri().path());

    // spawn a thread and call slow.next_block() until len()==0
    // each byte which comes from next_block should go to the client
    // as part of the Body
}

请注意,SlowData::new的计算量很大。

最理想的是,我们将副本最小化并将&[u8]直接发送到hyper,而不必将其复制到Vec或其他东西中。

如何从侧面线程实现超级请求的主体?

1 个答案:

答案 0 :(得分:0)

启动线程并通过通道发送块。该通道实现了Stream,可以使用wrap_streamBody构造超级Stream

use futures::{sync::mpsc, Future, Sink, Stream}; // 0.1.27
use hyper::{service::service_fn_ok, Body, Response, Server}; // 0.12.29
use std::{io, thread, time::Duration};

struct SlowData;
impl SlowData {
    fn new(_initial: &str) -> SlowData {
        thread::sleep(Duration::from_secs(1));
        Self
    }

    fn next_block(&self) -> io::Result<&[u8]> {
        thread::sleep(Duration::from_secs(1));
        Ok(b"data")
    }
}

fn stream() -> impl Stream<Item = Vec<u8>, Error = ()> {
    let (tx, rx) = mpsc::channel(10);

    thread::spawn(move || {
        let sd = SlowData::new("dummy");

        let mut tx = tx.wait();

        for _ in 0..3 {
            tx.send(sd.next_block().unwrap().to_vec()).unwrap();
        }
    });

    rx
}

fn main() {
    // Construct our SocketAddr to listen on...
    let addr = ([127, 0, 0, 1], 3000).into();

    // And a MakeService to handle each connection...
    let make_service = || {
        service_fn_ok(|_req| {
            let data = stream();

            Response::new(Body::wrap_stream(data.map_err(|_| "dummy error")))
        })
    };

    // Then bind and serve...
    let server = Server::bind(&addr).serve(make_service);

    // Finally, spawn `server` onto an Executor...
    hyper::rt::run(server.map_err(|e| {
        eprintln!("server error: {}", e);
    }));
}

目前,无法避免将切片复制到Vec。编译器的async / await支持应该允许这样做。

另请参阅: