I am trying to use session object with Redis
as the storage in a distributed system in the signin
, signup
and signout
resolvers to set and delete session for userid
but having issues with that because actix' Session
does not implement Send
and cannot be used across threads. It has type: Rc<RefCell<actix_session::SessionInner>>
async-graphql
?
I would like to do something like below:#[Object]
impl User {
async fn signin(&self, ctx: &Context<'_>) -> anyhow::Result<Vec<User>> {
let session = ctx.data_unchecked::<Session>();
session.insert("user_id", id);
session.get::<UserId>("user_id");
...
}
}
If I try the above, I get:
`Rc<RefCell<actix_session::SessionInner>>` cannot be shared between threads safely
within `actix_session::Session`, the trait `Sync` is not implemented for `Rc<RefCell<actix_session::SessionInner>>`
session
in async-graphql context? I am trying this but would face the same issue:#[post("/graphql")]
pub async fn index(
schema: web::Data<MyGraphQLSchema>,
req: HttpRequest,
gql_request: GraphQLRequest,
) -> GraphQLResponse {
let mut request = gql_request.into_inner();
let session = req.get_session();
request = request.data(session);
schema.execute(request).await.into()
}
My app:
let redis_key = Key::from(hmac_secret_from_env_var.expose_secret().as_bytes());
App::new()
.wrap(cors)
.wrap(
RedisSession::new(redis.get_url(), redis_key.master())
.cookie_http_only(true)
// allow the cookie only from the current domain
.cookie_same_site(cookie::SameSite::Lax),
)
.wrap(Logger::default())
I resolved this using a temporary hack. If you check session definition, you will notice that it wraps a RefCell as below and does not implement send
pub struct Session(Rc<RefCell<SessionInner>>);
Ideally, Session
should implement sync, so that we can use it in a multi-threaded context.
The approach I am using presently is to wrap in a SendWrapper, which isn't ideal as it is meant for when you're sure the wrapped item will be used in a single-threaded fashion.
use std::ops::Deref;
use actix_session::Session;
use actix_web::Error;
use send_wrapper::SendWrapper;
use uuid::Uuid;
#[derive(Clone, Debug)]
struct Shared<T>(pub Option<SendWrapper<T>>);
impl<T> Shared<T> {
pub fn new(v: T) -> Self {
Self(Some(SendWrapper::new(v)))
}
}
impl<T> Deref for Shared<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.0.as_deref().clone().unwrap()
}
}
type SessionShared = Shared<actix_session::Session>;
pub struct TypedSession(SessionShared);
impl TypedSession {
const USER_ID_KEY: &'static str = "user_id";
pub fn new(session: Session) -> Self {
Self(Shared::new(session))
}
pub fn renew(&self) {
self.0.renew();
}
pub fn insert_user_uuid(&self, user_id: Uuid) -> Result<(), Error> {
self.0.insert(Self::USER_ID_KEY, user_id)
}
pub fn get_user_uuid(&self) -> Result<Option<Uuid>, Error> {
self.0.get::<Uuid>(Self::USER_ID_KEY)
}
pub fn clear(&self) {
self.0.clear()
}
}