requestrustmiddlewareiron

How do I read an Iron Request in both middleware and the handler?


I'm working on a small API in Rust and am not sure how to access a Request from Iron in two places.

The Authentication middleware reads the Request once for a token and the actual route tries to read it again if the path is allowed (currently there is no check). This gives me an EOF error as the request has already been read.

I can't seem to easily clone the request and I believe it must be mutable in order to read the body.

extern crate iron;
extern crate router;
extern crate rustc_serialize;

use iron::prelude::*;
use iron::{BeforeMiddleware, status};
use router::Router;
use rustc_serialize::json;
use rustc_serialize::json::Json;
use std::io::Read;

#[derive(RustcEncodable, RustcDecodable)]
struct Greeting {
    msg: String
}

struct Authentication;

fn main() {
    let mut request_body = String::new();

    impl BeforeMiddleware for Authentication {
        fn before(&self, request: &mut Request) -> IronResult<()> {
            let mut payload = String::new();
            request.body.read_to_string(&mut payload).unwrap();
            let json = Json::from_str(&payload).unwrap();

            println!("json: {}", json);

            let token = json.as_object()
                .and_then(|obj| obj.get("token"))
                .and_then(|token| token.as_string())
                .unwrap_or_else(|| {
                    panic!("Unable to get token");
                });

            println!("token: {}", token);

            Ok(())
        }
    }

    fn attr(input: String, attribute: &str) -> String {
        let json = Json::from_str(&input).unwrap();
        let output = json.as_object()
            .and_then(|obj| obj.get(attribute))
            .and_then(|a| a.as_string())
            .unwrap_or_else(|| {
                panic!("Unable to get attribute {}", attribute);
            });

        String::from(output)
    }

    fn hello_world(_: &mut Request) -> IronResult<Response> {
        let greeting = Greeting { msg: "Hello, world!".to_string() };
        let payload = json::encode(&greeting).unwrap();
        Ok(Response::with((status::Ok, payload)))
    }

    // Receive a message by POST and play it back if auth-key is correct.
    fn set_greeting(request: &mut Request) -> IronResult<Response> {
        let mut payload = String::new();
        request.body.read_to_string(&mut payload).unwrap();
        let json = Json::from_str(&payload).unwrap();

        println!("json: {}", json);

        let msg = attr(payload, "msg");

        println!("msg: {}", msg);

        let greeting = Greeting { msg: String::from(msg) };
        let payload = json::encode(&greeting).unwrap();

        Ok(Response::with((status::Ok, payload)))
    }

    let mut router = Router::new();

    router.get("/", hello_world);
    router.post("/set", set_greeting);

    let mut chain = Chain::new(router);
    chain.link_before(Authentication);

    Iron::new(chain).http("localhost:3000").unwrap();
}

Solution

  • Without knowing for sure, I don't think you can do anything to re-read the body (and you probably wouldn't want to for performance reasons). Instead, you could make your middleware parse the data and then store it in Request.extensions. Then your route would read it back out:

    struct AuthenticatedBody;
    
    impl iron::typemap::Key for AuthenticatedBody {
        type Value = Json;
    }
    
    struct Authentication;
    
    impl BeforeMiddleware for Authentication {
        fn before(&self, request: &mut Request) -> IronResult<()> {
            let mut payload = String::new();
            request.body.read_to_string(&mut payload).unwrap();
            let json = Json::from_str(&payload).unwrap();
    
            {
                let token = json.as_object()
                    .and_then(|obj| obj.get("token"))
                    .and_then(|token| token.as_string())
                    .unwrap_or_else(|| panic!("Unable to get token"));
            } // Scoped to end the borrow of `json`
    
            request.extensions.insert::<AuthenticatedBody>(json);
    
            Ok(())
        }
    }
    
    // ...
    
    fn set_greeting(request: &mut Request) -> IronResult<Response> {
        let json = request.extensions.get::<AuthenticatedBody>().unwrap();
        // ...
    }