关于rust:如何阅读基于Tokio的Hyper请求的整个内容?

How do I read the entire body of a Tokio-based Hyper request?

我想使用Hyper的当前主分支编写服务器,该分支保存由POST请求传递的消息,并将此消息发送到每个传入的GET请求。

我有这个,主要是从超级示例目录中复制的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
extern crate futures;
extern crate hyper;
extern crate pretty_env_logger;

use futures::future::FutureResult;

use hyper::{Get, Post, StatusCode};
use hyper::header::{ContentLength};
use hyper::server::{Http, Service, Request, Response};
use futures::Stream;

struct Echo {
    data: Vec<u8>,
}

impl Echo {
    fn new() -> Self {
        Echo {
            data:"text".into(),
        }
    }
}

impl Service for Echo {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = FutureResult<Response, hyper::Error>;

    fn call(&self, req: Self::Request) -> Self::Future {
        let resp = match (req.method(), req.path()) {
            (&Get,"/") | (&Get,"/echo") => {
                Response::new()
                    .with_header(ContentLength(self.data.len() as u64))
                    .with_body(self.data.clone())
            },
            (&Post,"/") => {
                //self.data.clear(); // argh. &self is not mutable :(
                // even if it was mutable... how to put the entire body into it?
                //req.body().fold(...) ?
                let mut res = Response::new();
                if let Some(len) = req.headers().get::<ContentLength>() {
                    res.headers_mut().set(ContentLength(0));
                }
                res.with_body(req.body())
            },
            _ => {
                Response::new()
                    .with_status(StatusCode::NotFound)
            }
        };
        futures::future::ok(resp)
    }

}


fn main() {
    pretty_env_logger::init().unwrap();
    let addr ="127.0.0.1:12346".parse().unwrap();

    let server = Http::new().bind(&addr, || Ok(Echo::new())).unwrap();
    println!("Listening on http://{} with 1 thread.", server.local_addr().unwrap());
    server.run().unwrap();
}

如何将req.body()(似乎是ChunksStream)变成Vec<u8>?我假设我必须以某种方式返回消耗StreamFuture并将其转换为单个Vec<u8>,也许使用fold()。但是我不知道该怎么做。

  • 如何为Hyper处理程序共享可变状态?回答了您问题的一半,因此,我已将您的问题改写为重点,着重于独特的方面,并避免投票不足。
  • 感谢您对@Shepmaster的首次编辑。在那之后,它看起来真的很漂亮。但是,我不喜欢您的第二次编辑。我看不到链接的问题如何回答我的问题。他们甚至没有实施特质服务。
  • 您始终可以从修订列表中回滚您不同意的任何编辑,或执行进一步的编辑。
  • 但是,答案是相同的,您将需要线程安全的内部可变性,例如MutexAtomic*RwLock
  • 请注意,每个问题也应该有一个问题。
  • 好的,那么我将为第一个问题打开一个新问题。谢谢你。
  • 听起来不错。在这种情况下,我也建议您创建一个最小的可复制示例,并使用Hyper的发行版。链接到我上面建议的问题,并说明为什么它对您的案例无效,这也将有很长的路要走!
  • @Shepmaster似乎Tokio开发人员想要修改甚至删除Service特性。因此,一旦tokio-service-0.2发布,我的另一个问题可能会解决。
  • 很高兴知道,但是tokio::Service可能与hyper::Service不同,对吧?即使Tokio删除了它,Hyper也会保留吗?
  • @Shepmaster我假设Hyper在他们开始迁移到Tokio时才复制了特性。但是,是的,即使Tokio不这样做,他们也可能会保留。


Hyper 0.13为此提供了body::to_bytes函数。

1
2
3
4
5
6
7
use hyper::body;
use hyper::{Body, Response};

pub async fn read_response_body(res: Response<Body>) -> Result<String, hyper::Error> {
    let bytes = body::to_bytes(res.into_body()).await?;
    Ok(String::from_utf8(bytes.to_vec()).expect("response was not valid utf-8"))
}

我将简化问题,仅返回总字节数,而不是回显整个流。

期货0.3
超级0.13 TryStreamExt::try_fold

如果只希望将所有数据作为一个大块,请参阅euclio关于hyper::body::to_bytes的答案。

访问流可进行更细粒度的控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use futures::TryStreamExt; // 0.3.7
use hyper::{server::Server, service, Body, Method, Request, Response}; // 0.13.9
use std::convert::Infallible;
use tokio; // 0.2.22

#[tokio::main]
async fn main() {
    let addr ="127.0.0.1:12346".parse().expect("Unable to parse address");

    let server = Server::bind(&addr).serve(service::make_service_fn(|_conn| async {
        Ok::<_, Infallible>(service::service_fn(echo))
    }));

    println!("Listening on http://{}.", server.local_addr());

    if let Err(e) = server.await {
        eprintln!("Error: {}", e);
    }
}

async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let (parts, body) = req.into_parts();
    match (parts.method, parts.uri.path()) {
        (Method::POST,"/") => {
            let entire_body = body
                .try_fold(Vec::new(), |mut data, chunk| async move {
                    data.extend_from_slice(&chunk);
                    Ok(data)
                })
                .await;

            entire_body.map(|body| {
                let body = Body::from(format!("Read {} bytes", body.len()));
                Response::new(body)
            })
        }
        _ => {
            let body = Body::from("Can only POST to /");
            Ok(Response::new(body))
        }
    }
}

不幸的是,Bytes的当前实现不再与TryStreamExt::try_concat兼容,因此我们必须切换回fold。

期货0.1
超级0.12 Stream::concat2

自期货0.1.14起,您可以使用Stream::concat2将所有数据合并为一个:

1
2
3
4
fn concat2(self) -> Concat2<Self>
where
    Self: Sized,
    Self::Item: Extend<<Self::Item as IntoIterator>::Item> + IntoIterator + Default,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use futures::{
    future::{self, Either},
    Future, Stream,
}; // 0.1.25

use hyper::{server::Server, service, Body, Method, Request, Response}; // 0.12.20

use tokio; // 0.1.14

fn main() {
    let addr ="127.0.0.1:12346".parse().expect("Unable to parse address");

    let server = Server::bind(&addr).serve(|| service::service_fn(echo));

    println!("Listening on http://{}.", server.local_addr());

    let server = server.map_err(|e| eprintln!("Error: {}", e));
    tokio::run(server);
}

fn echo(req: Request<Body>) -> impl Future<Item = Response<Body>, Error = hyper::Error> {
    let (parts, body) = req.into_parts();

    match (parts.method, parts.uri.path()) {
        (Method::POST,"/") => {
            let entire_body = body.concat2();
            let resp = entire_body.map(|body| {
                let body = Body::from(format!("Read {} bytes", body.len()));
                Response::new(body)
            });
            Either::A(resp)
        }
        _ => {
            let body = Body::from("Can only POST to /");
            let resp = future::ok(Response::new(body));
            Either::B(resp)
        }
    }
}

您还可以通过entire_body.to_vec()Bytes转换为Vec<u8>,然后将其转换为String

另请参见:

  • 如何将字节向量(u8)转换为字符串

超级0.11 Stream::fold

类似于Iterator::foldStream::fold带有一个累加器(称为init)和一个对累加器进行操作的功能以及流中的项。该函数的结果必须是另一个具有与原始错误类型相同的错误类型的将来。总的结果本身就是未来。

1
2
3
4
5
6
fn fold<F, T, Fut>(self, init: T, f: F) -> Fold<Self, F, Fut, T>
where
    F: FnMut(T, Self::Item) -> Fut,
    Fut: IntoFuture<Item = T>,
    Self::Error: From<Fut::Error>,
    Self: Sized,

我们可以使用Vec作为累加器。 BodyStream实现返回一个Chunk。这实现了Deref<[u8]>,因此我们可以使用它来将每个块的数据附加到Vec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
extern crate futures; // 0.1.23
extern crate hyper;   // 0.11.27

use futures::{Future, Stream};
use hyper::{
    server::{Http, Request, Response, Service}, Post,
};

fn main() {
    let addr ="127.0.0.1:12346".parse().unwrap();

    let server = Http::new().bind(&addr, || Ok(Echo)).unwrap();
    println!(
       "Listening on http://{} with 1 thread.",
        server.local_addr().unwrap()
    );
    server.run().unwrap();
}

struct Echo;

impl Service for Echo {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<futures::Future<Item = Response, Error = Self::Error>>;

    fn call(&self, req: Self::Request) -> Self::Future {
        match (req.method(), req.path()) {
            (&Post,"/") => {
                let f = req.body()
                    .fold(Vec::new(), |mut acc, chunk| {
                        acc.extend_from_slice(&*chunk);
                        futures::future::ok::<_, Self::Error>(acc)
                    })
                    .map(|body| Response::new().with_body(format!("Read {} bytes", body.len())));

                Box::new(f)
            }
            _ => panic!("Nope"),
        }
    }
}

您还可以将Vec<u8> Body转换为String

另请参见:

  • 如何将字节向量(u8)转换为字符串

输出

从命令行调用时,我们可以看到结果:

1
2
$ curl -X POST --data hello http://127.0.0.1:12346/
Read 5 bytes

警告

所有这些解决方案都允许恶意的最终用户发布无限大小的文件,这将导致计算机内存不足。根据预期的用途,您可能希望对读取的字节数设置某种上限,从而有可能在某个断点处写入文件系统。

另请参见:

  • 如何对futures :: Stream :: concat2读取的字节数施加限制?

  • 尽管您已从type Future = ...中删除了FutureResult,但您是否也可以解释为什么fold方法中包含futures::future:ok()
  • @JayStrictor,因为赋予fold的闭包需要返回将来的自身:F: FnMut(T, Self::Item) -> Fut。这允许操作本身花费时间。由于extend_from_slice是同步的,因此我们使用future::ok"提升"结果。这与type Future = FutureResult完全不同,type Future = FutureResult用作处理程序的返回值(出于惰性,我将其装箱)。
  • Stream::fold(...)可以用做相同功能的Stream::concat2()代替。 Chunk本身就是Extend,因此concat2的结果将是包含整个主体的单个Chunk
  • @Arnavion谢谢!当我最初编写此答案时,concat2甚至不存在!
  • 使用concat2在输入1MiB之后停止(例如)停止的技术是什么?
  • @proc如何对futures :: Stream :: concat2读取的字节数施加限制?
  • 如果将数据写入文件系统以避免内存不足,则只允许攻击者填满服务器的文件系统。不要那样做
  • @ M.Leonhard您可能希望对读取的字节数设置某种上限,可能会在某个断点处写入文件系统。我的建议是不要写文件系统,而不仅仅是使用内存,而是要添加一个现有的上限。


有关此主题的大多数答案都已过时或过于复杂。解决方案非常简单:

1
2
3
4
5
6
7
8
/*
    WARNING for beginners!!! This use statement
    is important so we can later use .data() method!!!
*/
use hyper::body::HttpBody;

let my_vector: Vec<u8> = request.into_body().data().await.unwrap().unwrap().to_vec();
let my_string = String::from_utf8(my_vector).unwrap();

您也可以使用body::to_bytes作为@euclio的答案。两种方法都是直截了当的!不要忘记正确处理unwrap

  • data记录为"返回到下一个数据块(如果有)的将来"。因此,我很确定此答案不正确/不完整。