3

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?

Wes
  • 2,166
  • 1
  • 20
  • 22

2 Answers2

1

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>).

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • I get, "the trait `iron::response::WriteBody` is not implemented for the type `Box` [E0277]" on the "Box::new(Box::new(read))". [I tried extracting the the inner box](https://gist.github.com/wezm/a45b751a62c44a878623) but it then complains that, "Send` is not implemented for the type `std::io::Read`". – Wes Sep 20 '15 at 22:58
  • See my rewritten answer. – Francis Gagné Sep 21 '15 at 02:42
0

If you are OK with buffering everything in memory (and I think that's what is already happening), you can just use a Vec<u8> plus a Cursor:

use std::io::{self, Read, Write, Cursor};
use std::borrow::BorrowMut;

fn writer<W>(mut w: W) -> io::Result<()>
    where W: Write
{
    writeln!(w, "I am the writer")
}

fn reader<R>(mut r: R) -> io::Result<String>
    where R: Read
{
    let mut s = String::new();
    try!(r.read_to_string(&mut s));
    Ok(s)
}

fn inner_main() -> io::Result<()> {
    let mut buffer = vec![];

    try!(writer(&mut buffer));
    let s = try!(reader(Cursor::new(buffer.borrow_mut())));

    println!("Got >>{}<<", s);

    Ok(())
}

fn main() {
    inner_main().unwrap();
}

Cursor keeps track of how far you are in a buffer, so that you always read or write without re-reading or overwriting existing data.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks, that helped. A cursor was actually what I tried initially before trying the pipe but I had trouble working out how to populate the buffer. Looking at your code, turns out this is as simple as passing the buffer to the image save function and the then the populated buffer can simply be passed to Response::with and it all works: https://gist.github.com/wezm/acf394cdb6dad315589f – Wes Sep 20 '15 at 23:12