如何在 async-graphql 解析器中使用 actix-redis 会话
How to use actix-redis session in async-graphql resolvers
我正在尝试使用 Redis
的会话对象作为 signin
、signup
和 signout
解析器中分布式系统中的存储来设置和删除会话对于 userid
但有问题,因为 actix' Session
没有实现 Send
并且不能跨线程使用。它有类型:Rc<RefCell<actix_session::SessionInner>>
问题
- 在
async-graphql
中处理此类问题的惯用方法是什么?
我想做如下的事情:
#[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>>`
- 此外,在 async-graphql 上下文中创建
session
的正确位置在哪里?我正在尝试这个,但会面临同样的问题:
#[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()
}
}
我正在尝试使用 Redis
的会话对象作为 signin
、signup
和 signout
解析器中分布式系统中的存储来设置和删除会话对于 userid
但有问题,因为 actix' Session
没有实现 Send
并且不能跨线程使用。它有类型:Rc<RefCell<actix_session::SessionInner>>
问题
- 在
async-graphql
中处理此类问题的惯用方法是什么? 我想做如下的事情:
#[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>>`
- 此外,在 async-graphql 上下文中创建
session
的正确位置在哪里?我正在尝试这个,但会面临同样的问题:
#[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()
}
}