Search code examples
piperustiron

Connect function that takes Write to function that takes Read


I am exploring the Iron web framework for Rust and have created a small handler that will read an image derived from the request URL, resize it and then deliver the result. From what I can tell an Iron Response can be built from a several different types, including types that implement the Read trait.

The save function in the image crate takes a type that implements the Write trait.

It feels like these two functions should be able to be hooked up such that the writer writes to a buffer that the reader reads from. I found the pipe crate, which seems to implement this behaviour but I'm having trouble getting the Read end of the pipe into something that Iron will accept.

A slightly simplified version of my function:

fn artwork(req: &mut Request) -> IronResult<Response> {
    let mut filepath = PathBuf::from("artwork/sample.png");

    let img = match image::open(&filepath) {
        Ok(img) => img,
        Err(e) => return Err(IronError::new(e, status::InternalServerError))
    };

    let (mut read, mut write) = pipe::pipe();

    thread::spawn(move || {
        let thumb = img.resize(128, 128, image::FilterType::Triangle);
        thumb.save(&mut write, image::JPEG).unwrap();
    });

    let mut res = Response::new();
    res.status = Some(iron::status::Ok);
    res.body = Some(Box::new(read));

    Ok(res)
}

The error I'm receiving:

src/main.rs:70:21: 70:35 error: the trait `iron::response::WriteBody` is not implemented for the type `pipe::PipeReader` [E0277]
src/main.rs:70     res.body = Some(Box::new(read));
                                   ^~~~~~~~~~~~~~

PipeReader implements Read and WriteBody is implemented for Read so I feel this should work. I also tried:

let reader: Box<Read> = Box::new(read);

let mut res = Response::new();
res.status = Some(iron::status::Ok);
res.body = Some(reader);

but this gives the error:

src/main.rs:72:21: 72:27 error: mismatched types:
 expected `Box<iron::response::WriteBody + Send>`,
    found `Box<std::io::Read>`
(expected trait `iron::response::WriteBody`,
    found trait `std::io::Read`) [E0308]
src/main.rs:72     res.body = Some(reader);
                                   ^~~~~~

How can I hook up the save function to the Iron response body?


Solution

  • You cannot use the impl for Box<Read> here, because Rust cannot guarantee that it implements Send. If you had a Box<Read + Send>, that would be the case, though. Unfortunately, while Box<Read> implements WriteBody, Box<Read + Send> does not, so you cannot use this type.

    Looking at the source code for WriteBody and its implementations, there is a commented out implementation that would implement WriteBody for all types that implement Read, but it does not compile as of now (as the comment says, this requires specialization, which is hopefully coming to the language soon).

    You could submit a pull request to Iron to add an impl for WriteBody on Box<Read + Send>; then, you could use that type (demo). Another option is to define a wrapper struct for PipeReader and implement WriteBody yourself (possibly based on the implementation for Box<Read>).