如何在 async-graphql 解析器中使用 actix-redis 会话

How to use actix-redis session in async-graphql resolvers

我正在尝试使用 Redis 的会话对象作为 signinsignupsignout 解析器中分布式系统中的存储来设置和删除会话对于 userid 但有问题,因为 actix' Session 没有实现 Send 并且不能跨线程使用。它有类型:Rc<RefCell<actix_session::SessionInner>>

问题

#[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");
        ...
    }
}

如果我尝试以上操作,我得到:

`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>>`
#[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()
}

我的应用程序:

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

我使用临时 hack 解决了这个问题。如果您检查会话定义,您会注意到它包装了一个 RefCell,如下所示并且没有实现 send

pub struct Session(Rc<RefCell<SessionInner>>);

理想情况下,Session 应该实现同步,以便我们可以在 multi-threaded 上下文中使用它。

我目前使用的方法是在 SendWrapper 中包装,这并不理想,因为它适用于当您确定包装的项目将以 single-threaded 方式使用时。

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()
    }
}