sunng87 / handlebars-iron

Handlebars middleware for Iron web framework
MIT License
119 stars 20 forks source link

Add the ability to gzip content before Responding #57

Closed FlogramMatt closed 7 years ago

FlogramMatt commented 7 years ago

It's easy enough to add gzip to non-handlebars iron responses: https://github.com/gsquire/compress (which might be a great example of how to use the other library)

BUT, when using handlebars, it's much trickier to do. I was working on my own AfterMiddleware handler that would mutate the output of handlerbars.. but still being new to Rust, and the AfterMIddleware piece has been a little tricky. Should be fairly simple given you already have that piece working if it's directly integrating into the library... and probably an appreciated feature by all. https://github.com/alexcrichton/flate2-rs

Here's the snippet of code I already have working, to be added to the AfterMiddleware, potentially adding gzip as an extra option: let raw_accept_encoding = request.headers.get_raw("Accept-Encoding").unwrap();

    for accept_encoding in raw_accept_encoding
    {
        println!("raw_accept_encoding: {}", str::from_utf8(accept_encoding).unwrap());

        if str::from_utf8(accept_encoding).unwrap().to_lowercase().contains("gzip")
        {
            response.headers.set(ContentEncoding(vec![Encoding::Gzip]));
            let mut encoder = GzEncoder::new(Vec::new(), Compression::Default);
            encoder.write_all("Insert Handlerbars output here");

           let compressed_bytes = encoder.finish().unwrap();  //except don't unwrap
            //then return compressed_bytes. 
      }

}

sunng87 commented 7 years ago

Hi @FractalMatt, is it possible to read the body from response in your AfterMiddleware and gz it? I think that's how the middlewarepattern designed to be.

mczarnek commented 7 years ago

Yeah that probably is the 'correct' way to do it. I've had a lot of trouble with the AfterMiddleware part though. Specifically the reading the old body response body. Maybe you would have some ideas or pointers there?

I can send you the complete code I have so far in a little bit when I have access to my home computer.

FlogramMatt commented 7 years ago

So I could copy and paste in the code but I guess the problem is I tried a few different approaches.. and none of them were working.

sunng87 commented 7 years ago

Can you just call response.body to get a boxed reference to rendered string generated by handlebars middleware? Then you can gz it and set the compressed bytes as body.

FlogramMatt commented 7 years ago

I tried that, response.body gets me a "ResponseBody".. I then tried using "write_all" to try to get access to the actual body.

For example this snippet: match response.body { Some(body) => //Who? { let mut respBody: ResponseBody; let mut ActualContent: [u8];

                    body.write_body(&mut respBody);

                    respBody.write_all(&mut ActualContent);
                    //let ptr = (respBody as Box<Any + 'static>).downcast::<WriteBody>();

                    encoder.write_all(body);

                    let compressed_bytes = encoder.finish().unwrap();

                    Ok(compressed_bytes);
                    //compressed_bytes.modify(res);
                },
                None => Ok(response),
            }
The biggest issue I'm then getting is this error: error[E0277]: the trait bound [u8]: std::marker::Sized is not satisfied --> src/main.rs:389:29 389 let mut ActualContent: [u8]; ^^^^^^^^^^^^^^^^^ the trait std::marker::Sized is not implemented for [u8]
= note: `[u8]` does not have a constant size known at compile-time
= note: all local variables must have a statically known size

Any thoughts? Thank you!!! You'd be welcome to include this MiddleWare into handlebars if we get it working, would be great if others didn't have to go through this to get this working.

sunng87 commented 7 years ago

Thank you and I will look into this issue later. It seems that we cannot easliy cast response body to concrete type.

But I still suggest to do the compression task outside the template middleware. Ideally if we can find a way to access the body, we will add another AfterMiddleware. And if we failed, I'm strongly recommend you to use nginx for the gzip thing.

sunng87 commented 7 years ago

I have a compression middleware that should work for you:

pub struct GzMiddleware;

impl AfterMiddleware for GzMiddleware {
    fn after(&self, _: &mut Request, mut resp: Response) -> IronResult<Response> {

        let compressed_bytes = resp.body.as_mut().map(|mut b| {
            let mut encoder = GzEncoder::new(Vec::new(), Compression::Best);
            {
                let _ = b.write_body(&mut encoder);
            }
            encoder.finish().unwrap()
        });

        if let Some(b) = compressed_bytes {
            resp.headers.set(ContentEncoding(vec![Encoding::Gzip]));
            resp.set_mut(b);
        }

        Ok(resp)
    }
}

And this middleware is pretty generic which could work with most Iron app. You just need to put it at the end of chain.

Full code: https://github.com/sunng87/handlebars-iron/blob/master/examples/compress.rs

FlogramMatt commented 7 years ago

Thank you sunng87! I very much appreciate it.

Still getting an error when trying to plug that into my code and compile it though: [leo@new-host-2 user-panel]$ sudo cargo build --no-default-features Compiling fractal-website v0.2.0 (file:///home/leo/Fractal/user-panel) error[E0308]: mismatched types --> src/main.rs:365:38 365 let _ = b.write_body(&mut encoder); ^^^^^^^^^^^^ expected struct iron::response::ResponseBody, found struct flate2::write::EncoderWriter
= note: expected type `&mut iron::response::ResponseBody<'_>`
= note:    found type `&mut flate2::write::EncoderWriter<std::vec::Vec<u8>>`

error: aborting due to previous error

Is that code compiling for you? I will play with this a little bit myself.

FlogramMatt commented 7 years ago

I think should be writing the body into the encoder, this looks like it's trying to write the encoder into the body.

sunng87 commented 7 years ago

What version of Iron you were using?

FlogramMatt commented 7 years ago

Thanks sunng87, I was using iron ^0.4, I switched to ^0.5 I'm now getting 330 errors.. and I can't tell for certain but I'm not seeing these ones amongst them.

Thanks though, that probably fixed the gzip error...

Now the question is, do I switch to rocket or fix all of these errors? Will think about it.

sunng87 commented 7 years ago

Rocket has a different programming model. Personally I still believe Iron's middleware architecture is elegant and simple to understand. It's totally up to you :)