Search code examples
rustiron

Share session information to iron handler


I'm trying to implement a web API using Iron as a practical exercise. My session is the following struct, which will be encoded as a JWT. Every time I receive requests from clients, some handlers will need to access user_id;

#[derive(Serialize, Deserialize)]
struct Session {
    session_id: i32,
    user_id: Option<i32>,
    expiration: u64,
}

There are several ways to do this, but I don't know the most idiomatic and least verbose. I was thinking something like:

fn handle(&self, req: &mut Request) -> IronResult<Response> {
    let session = match get_session(req) {
        Err(err) => return Ok(Response::with((status::BadRequest, err.description()))),
        Ok(session) => session,
    };
    Ok(Response::with((status::Ok, err.description())))
}

But this way I'll need this snippet on several endpoints.

I could use a middleware, but I don't know how to catch the struct value between middlewares.


Solution

  • I ended up using a wrapper:

    pub trait SessionHandler {
        fn session_manager(&self) -> &SessionManager;
        fn authenticated(&self) -> bool {
            false
        }
        fn handle_session(&self, session: &mut Session, req: &mut Request) -> IronResult<Response>;
    
        fn handle(&self, req: &mut Request) -> IronResult<Response> {
            let mut session = match self.session_manager().get_request_session(req) {
                None => return Ok(Response::with((status::Unauthorized, ""))),
                Some(session) => {
                    if self.authenticated() {
                        if let None = session.user_id {
                            return Ok(Response::with((status::Forbidden, "")));
                        }
                    }
                    session
                }
            };
            self.handle_session(&mut session, req)
        }
    }
    
    pub struct SessionHandlerBox<T> {
        pub s: T
    }
    
    impl <T> Handler for SessionHandlerBox<T> where T: SessionHandler +  Send + Sync + 'static {
        fn handle(&self, r: &mut Request) -> IronResult<Response> {
            self.s.handle(r)
        }
    }
    

    So I use:

    struct FileDelete {
        db: Arc<Pool<PostgresConnectionManager>>,
        sm: Arc<SessionManager>,
    }
    
    impl SessionHandler for FileDelete {
        fn session_manager(&self) -> &SessionManager {
            self.sm.as_ref()
        }
        fn handle_session(&self, session: &mut Session, req: &mut Request) -> IronResult<Response> {
            Ok(Response::with((status::Ok, "")))
        }
    }
    

    There's still boilerplate, but there's less "business logic". Any better solutions are welcome.

    usage example:

     pub fn register_handlers<'s>(db: Pool<PostgresConnectionManager>, r: &'s mut Router, sm : Arc<SessionManager>) {
        let file_delete = FileDelete { db: Arc::new(db), sm: sm.clone() };
        r.delete("/file", SessionHandlerBox {s: file_delete}, "file_delete");
    }