实现hyper :: client :: Connect进行测试

时间:2017-08-16 11:19:32

标签: rust

我试图通过使用静态响应实现我自己的hyper::Client来测试一些使用hyper::client::Connect的代码。我已经找到了类型,但无法找出tokio-proto抱怨说request / response mismatch的运行时问题。这是我的代码的简化版本,用于演示失败:

extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;

use futures::{future, Future, Stream};
use std::str::from_utf8;
use std::io::Cursor;

struct Client<'a, C: 'a> {
    client: &'a hyper::Client<C>,
    url: &'a str,
}

impl<'a, C: hyper::client::Connect> Client<'a, C> {
    fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
        Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
            let body = Vec::new();
            res.body()
                .fold(body, |mut acc, chunk| {
                    acc.extend_from_slice(chunk.as_ref());
                    Ok::<_, hyper::Error>(acc)
                })
                .and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
        }))
    }
}

struct StaticConnector<'a> {
    body: &'a [u8],
}

impl<'a> StaticConnector<'a> {
    fn new(body: &'a [u8]) -> StaticConnector {
        StaticConnector { body: body }
    }
}

impl<'a> hyper::server::Service for StaticConnector<'a> {
    type Request = hyper::Uri;
    type Response = Cursor<Vec<u8>>;
    type Error = std::io::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, _: Self::Request) -> Self::Future {
        Box::new(future::ok(Cursor::new(self.body.to_vec())))
    }
}

fn main() {
    let mut core = tokio_core::reactor::Core::new().unwrap();
    let handle = core.handle();

    // My StaticConnector for testing
    let hyper_client = hyper::Client::configure()
        .connector(StaticConnector::new(
            b"\
                 HTTP/1.1 200 OK\r\n\
                 Content-Length: 8\r\n\
                 \r\n\
                 Maldives\
                 ",
        ))
        .build(&handle);

    // Real Connector
    /*
    let hyper_client = hyper::Client::configure().build(&handle);
    */

    let client = Client {
        client: &hyper_client,
        url: "http://ifconfig.co/country",
    };
    let result = core.run(client.get()).unwrap();
    println!("{}", result);
}

Playground

我猜测我使用的Cursor Io在某种程度上是不完整的,但我没有调试并取得进展。一种想法是,Cursor hyper::Clientsink的写入可能不符合预期。也许我需要组合DEBUG_FILE = ./debug/debug.txt INPUTS0 = syn.tcl OUTPUTS0 = syn.out OUTPUT_ILA = $(DEBUG_FILE) ############################################################################ # # Dependencies / rules # ############################################################################ ifdef DEBUG_FILE all: ila else all: syn endif ## Synthesis $(OUTPUTS0): $(INPUTS0) echo "Synthesis Process." touch $(OUTPUTS0) $(OUTPUT_ILA): $(OUTPUTS0) echo "Debug Probe Insertion." touch $(OUTPUT_ILA) syn: $(OUTPUTS0) ila: $(OUTPUT_ILA) 用于写入和静态内容用于读取?所有想法都未能在使用中取得进展!

1 个答案:

答案 0 :(得分:1)

原始代码无法正常工作的原因是因为读者方在客户端发送请求之前提供了响应,因此tokio-protorequest / response mismatch产生了错误。修复原来是非常重要的,首先我们需要安排读者阻止,或者更具体地说是std::io::ErrorKind::WouldBlock错误输出,以向事件循环表明还没有任何东西,但是不要认为它是EOF。另外,一旦我们得到表示请求已经发送并且tokio-proto机器正在等待响应的写入,我们使用futures::task::current.notify来取消阻止读取。这是一个按预期工作的更新实现:

extern crate futures;
extern crate hyper;
extern crate tokio_core;
extern crate tokio_io;

use futures::{future, Future, Stream, task, Poll};
use std::str::from_utf8;
use std::io::{self, Cursor, Read, Write};
use tokio_io::{AsyncRead, AsyncWrite};

struct Client<'a, C: 'a> {
    client: &'a hyper::Client<C>,
    url: &'a str,
}

impl<'a, C: hyper::client::Connect> Client<'a, C> {
    fn get(&self) -> Box<Future<Item = String, Error = hyper::Error>> {
        Box::new(self.client.get(self.url.parse().unwrap()).and_then(|res| {
            let body = Vec::new();
            res.body()
                .fold(body, |mut acc, chunk| {
                    acc.extend_from_slice(chunk.as_ref());
                    Ok::<_, hyper::Error>(acc)
                })
                .and_then(move |value| Ok(String::from(from_utf8(&value).unwrap())))
        }))
    }
}

struct StaticStream {
    wrote: bool,
    body: Cursor<Vec<u8>>,
}

impl Read for StaticStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if self.wrote {
            self.body.read(buf)
        } else {
            Err(io::ErrorKind::WouldBlock.into())
        }
    }
}

impl Write for StaticStream {
    fn write<'a>(&mut self, buf: &'a [u8]) -> io::Result<usize> {
        self.wrote = true;
        task::current().notify();
        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl AsyncRead for StaticStream {}

impl AsyncWrite for StaticStream {
    fn shutdown(&mut self) -> Poll<(), io::Error> {
        Ok(().into())
    }
}

struct StaticConnector<'a> {
    body: &'a [u8],
}

impl<'a> StaticConnector<'a> {
    fn new(body: &'a [u8]) -> StaticConnector {
        StaticConnector { body: body }
    }
}

impl<'a> hyper::server::Service for StaticConnector<'a> {
    type Request = hyper::Uri;
    type Response = StaticStream;
    type Error = std::io::Error;
    type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;

    fn call(&self, _: Self::Request) -> Self::Future {
        Box::new(future::ok(StaticStream {
            wrote: false,
            body: Cursor::new(self.body.to_vec()),
        }))
    }
}

fn main() {
    let mut core = tokio_core::reactor::Core::new().unwrap();
    let handle = core.handle();

    // My StaticConnector for testing
    let hyper_client = hyper::Client::configure()
        .connector(StaticConnector::new(
            b"\
                 HTTP/1.1 200 OK\r\n\
                 Content-Length: 8\r\n\
                 \r\n\
                 Maldives\
                 ",
        ))
        .build(&handle);

    // Real Connector
    /*
    let hyper_client = hyper::Client::configure().build(&handle);
    */

    let client = Client {
        client: &hyper_client,
        url: "http://ifconfig.co/country",
    };
    let result = core.run(client.get()).unwrap();
    println!("{}", result);
}

Playground

注意:此实现适用于简单的情况,但我还没有测试过更复杂的场景。例如,我不确定的一件事是请求/响应有多大,因为它们可能涉及多于一次的读/写调用。