Web App Multitenant

Hi,

Is it possible to prepare a multi tenant application with Actix, ntex or axum?

For example, I want to get the database settings after checking the request header and getting the site name. (X-Application-Site) We can achieve this using the code request.headers.get('X-Application-Site-Name') with Python. If I can create a structure this way, I can get the settings from the site's folder and use separate database for each site.

But I couldn't find how to go about it. I am planning to read settings from json file by checking database and all settings in request header.

Example json file:

{
 "db_name": "_452bb8a54116ac67",
 "db_password": "4eFlqrOeu6abyZTR",
 "db_type": "postgresql",
 "limits": {
  "emails": 1500,
  "expiry": "2030-01-01",
  "space": 50.0,
  "users": 50
 },
}

I'll be happy if you can help me.

I suppose you could do something like this

use axum::{
    async_trait,
    extract::{Extension, FromRequest, RequestParts},
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    let databases = Databases {};

    let app = Router::new()
        .route("/", get(handler))
        .layer(Extension(databases));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

#[derive(Clone)]
struct Databases {}

impl Databases {
    fn get_database_for_site(&self, application_site_name: &str) -> Option<DatabaseForSite> {
        todo!()
    }
}

#[async_trait]
impl<B> FromRequest<B> for DatabaseForSite
where
    B: Send,
{
    type Rejection = Response;

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        // look up the header
        let application_site_name = req
            .headers()
            .ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR.into_response())?
            .get("x-application-site-name")
            .and_then(|value| value.to_str().ok())
            .ok_or_else(|| {
                (
                    StatusCode::BAD_REQUEST,
                    "x-application-site-name header missing",
                )
                    .into_response()
            })?;

        // get the database for the site name
        let databases = req
            .extensions()
            .ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR.into_response())?
            .get::<Databases>()
            .ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR.into_response())?;

        let database = databases
            .get_database_for_site(application_site_name)
            .ok_or_else(|| StatusCode::BAD_REQUEST.into_response())?;

        Ok(database)
    }
}

#[derive(Clone)]
struct DatabaseForSite {}

async fn handler(db: DatabaseForSite) -> impl IntoResponse {
    // use the database
}