rust 如何在async-graphql和axum之间共享状态

tyky79it  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(269)

我现在尝试使用axumasync-graphql构建一个graphql服务器。
最大的问题是graphql和axum之间的数据共享不好,我之所以要这么做,是因为我想把axum收到的用于认证的http头传递给graphql的世界。
谁能解决这个问题或提出另一种方法?
Cargo.toml

[package]
name = "app"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { version = "0.6.1", features = ["headers"] }
tokio = { version = "1.19.2", features = ["rt-multi-thread", "macros"] }
serde = { version = "1.0.136", features = ["derive"] }
async-graphql = { version = "5.0.4", features = ["chrono"] }
sqlx = { version = "0.6.2", features = [ "runtime-actix-native-tls", "postgres", "chrono" ] }
dotenv = "0.15.0"
tower-http = { version = "0.3.5", features = ["cors", "trace"] }
tokio-stream = "0.1.11"
chrono = "0.4.23"
jsonwebtoken = "8.2.0"
thiserror = "1.0.38"
async-trait = "0.1.60"

main.rs

mod db;
mod repositories;
mod resolvers;

use async_graphql::{
    http::{playground_source, GraphQLPlaygroundConfig},
    Request, Response, Schema,
};
use axum::{
    extract::{Extension, State},
    http::{
        header::{ACCEPT, AUTHORIZATION},
        HeaderValue, Method, Request as AxumRequest,
    },
    middleware::Next,
    response::{Html, IntoResponse, Response as AxumResponse},
    routing::get,
    Json, Router,
};
use dotenv::dotenv;
use resolvers::{QueryRoot, Subscription};
use std::net::SocketAddr;
use tower_http::cors::CorsLayer;

use crate::{db::DB, resolvers::Mutation};

pub type MainSchema = Schema<QueryRoot, Mutation, Subscription>;

async fn graphql_handler(schema: Extension<MainSchema>, req: Json<Request>) -> Json<Response> {
    schema.execute(req.0).await.into()
}

async fn graphql_playground() -> impl IntoResponse {
    Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[derive(Clone, Debug)]
pub struct AppState {
    db: DB,
    token: Option<String>,
}

impl AppState {
    fn set_token(mut self, token: String) {
        self.token = Some(token)
    }
}

async fn propagate_header<B>(
    State(state): State<&AppState>,
    req: AxumRequest<B>,
    next: Next<B>,
) -> AxumResponse {
    let token = req.headers().get("Authorization");

    if token.is_some() {
        // TODO: Put token in state.
    };

    next.run(req).await
}

#[tokio::main]
async fn main() {
    dotenv().ok();
    let server = async {
        let db = DB::new().await;
        let state = AppState { db, token: None };
        let schema = Schema::build(QueryRoot, Mutation, Subscription)
            // .limit_depth(5)
            .data(&state)
            .finish();

        let cors_layer = CorsLayer::new()
            .allow_origin("*".parse::<HeaderValue>().unwrap())
            .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
            .allow_headers(vec![AUTHORIZATION, ACCEPT]);

        let app = Router::new()
            .route("/", get(graphql_playground).post(graphql_handler))
            .layer(cors_layer)
            .layer(axum::middleware::from_fn_with_state(
                &state,
                propagate_header,
            ))
            .layer(Extension(schema));

        let addr = SocketAddr::from(([0, 0, 0, 0], 8009));
        axum::Server::bind(&addr)
            .serve(app.into_make_service())
            .await
            .unwrap();
    };

    tokio::join!(server);
}

误差

error[E0597]: `state` does not live long enough
  --> src/main.rs:71:19
   |
69 |           let schema = Schema::build(QueryRoot, Mutation, Subscription)
   |  ______________________-
70 | |             // .limit_depth(5)
71 | |             .data(&state)
   | |___________________^^^^^^- argument requires that `state` is borrowed for `'static`
   |                     |
   |                     borrowed value does not live long enough
...
93 |       };
   |       - `state` dropped here while still borrowed
vyswwuz2

vyswwuz21#

在这里您可能需要使用Arc,请参见here

let state = AppState { db, token: None };
let arced = Arc::new(state);
let schema = Schema::build(QueryRoot, Mutation, Subscription)
    // .limit_depth(5)
    .data(arced.clone())
    .finish();

... etc ...

我能看到的下一个问题是,基于对token字段的查看,你的状态似乎是可变的,这不能单独使用arc来实现,你可能还需要添加一个Mutex,或者其他一些锁(例如RwLock等)。

相关问题