- add: api service gateway to allow website logins
This commit is contained in:
23
api-service/Cargo.toml
Normal file
23
api-service/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "api-service"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.9"
|
||||
hyper = { version = "1.5.1", features = ["server"] }
|
||||
prost = "0.13.4"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
tokio = { version = "1.42.0", features = ["full"] }
|
||||
tonic = { version = "0.12.3", features = ["transport", "prost"] }
|
||||
tower = "0.5.2"
|
||||
log = "0.4.22"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
utils = { path = "../utils" }
|
||||
dotenv = "0.15"
|
||||
tower-http = { version = "0.6.2", features = ["cors"] }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12.3"
|
||||
6
api-service/build.rs
Normal file
6
api-service/build.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure()
|
||||
.compile_well_known_types(true)
|
||||
.compile_protos(&["../proto/auth.proto"], &["../proto"])?;
|
||||
Ok(())
|
||||
}
|
||||
75
api-service/src/axum_gateway.rs
Normal file
75
api-service/src/axum_gateway.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use axum::extract::State;
|
||||
use axum::{routing::post, Json, Router};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use axum::http::Method;
|
||||
use tonic::transport::Channel;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use auth::auth_service_client::AuthServiceClient;
|
||||
use auth::LoginRequest;
|
||||
use log::error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub mod auth {
|
||||
tonic::include_proto!("auth");
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RestLoginRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RestLoginResponse {
|
||||
token: String,
|
||||
}
|
||||
|
||||
async fn login_handler(
|
||||
State(grpc_client): State<Arc<Mutex<AuthServiceClient<Channel>>>>,
|
||||
Json(payload): Json<RestLoginRequest>,
|
||||
) -> Result<Json<RestLoginResponse>, axum::http::StatusCode> {
|
||||
let request = tonic::Request::new(LoginRequest {
|
||||
username: payload.username.clone(),
|
||||
password: payload.password.clone(),
|
||||
});
|
||||
|
||||
let mut client = grpc_client.lock().await; // Lock the mutex to get mutable access
|
||||
match client.login(request).await {
|
||||
Ok(response) => {
|
||||
let token = response.into_inner().token;
|
||||
Ok(Json(RestLoginResponse { token }))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("gRPC Login call failed: {}", e);
|
||||
Err(axum::http::StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve_rest_api(
|
||||
grpc_client: Arc<Mutex<AuthServiceClient<Channel>>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any) // Allow requests from any origin
|
||||
.allow_methods([Method::GET, Method::POST]) // Allow specific methods
|
||||
.allow_headers(Any); // Allow any headers
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/login", post(login_handler))
|
||||
.with_state(grpc_client)
|
||||
.layer(cors);
|
||||
|
||||
let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||
let port = env::var("API_SERVICE_PORT").unwrap_or_else(|_| "50050".to_string());
|
||||
let listener = tokio::net::TcpListener::bind(format!("{}:{}", addr, port))
|
||||
.await
|
||||
.unwrap();
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
79
api-service/src/main.rs
Normal file
79
api-service/src/main.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use crate::axum_gateway::auth::auth_service_client::AuthServiceClient;
|
||||
use dotenv::dotenv;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::{select, signal};
|
||||
use tracing::{info, Level};
|
||||
use utils::consul_registration;
|
||||
use utils::service_discovery::get_service_address;
|
||||
|
||||
mod axum_gateway;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dotenv().ok();
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(
|
||||
Level::from_str(&env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()))
|
||||
.unwrap_or_else(|_| Level::INFO),
|
||||
)
|
||||
.init();
|
||||
|
||||
// Set the gRPC server address
|
||||
let addr = env::var("API_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||
let port = env::var("API_SERVICE_PORT").unwrap_or_else(|_| "50050".to_string());
|
||||
let health_port = env::var("HEALTH_CHECK_PORT").unwrap_or_else(|_| "8079".to_string());
|
||||
|
||||
let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string());
|
||||
let service_name = env::var("SERVICE_NAME").unwrap_or_else(|_| "api-service".to_string());
|
||||
let service_address = addr.as_str();
|
||||
let service_port = port.clone();
|
||||
let health_check_url = format!("http://{}:{}/health", service_address, health_port);
|
||||
|
||||
// Register service with Consul
|
||||
let service_id = consul_registration::generate_service_id();
|
||||
let tags = vec!["version-1.0".to_string()];
|
||||
let meta = HashMap::new();
|
||||
consul_registration::register_service(
|
||||
&consul_url,
|
||||
service_id.as_str(),
|
||||
service_name.as_str(),
|
||||
service_address,
|
||||
service_port.parse().unwrap_or(50050),
|
||||
tags,
|
||||
meta,
|
||||
&health_check_url,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Start health-check endpoint
|
||||
consul_registration::start_health_check(addr.as_str()).await?;
|
||||
|
||||
let auth_node = get_service_address(&consul_url, "auth-service").await?;
|
||||
let auth_address = auth_node.get(0).unwrap();
|
||||
let auth_service_address = format!(
|
||||
"http://{}:{}",
|
||||
auth_address.ServiceAddress, auth_address.ServicePort
|
||||
);
|
||||
|
||||
// Connect to the gRPC auth-service
|
||||
let grpc_client = AuthServiceClient::connect(auth_service_address.to_string()).await?;
|
||||
let grpc_client = Arc::new(Mutex::new(grpc_client));
|
||||
|
||||
// Start the Axum REST API
|
||||
info!("Starting REST API on 0.0.0.0:8079");
|
||||
axum_gateway::serve_rest_api(grpc_client).await?;
|
||||
|
||||
select! {
|
||||
_ = signal::ctrl_c() => {},
|
||||
}
|
||||
|
||||
consul_registration::deregister_service(&consul_url, service_id.as_str())
|
||||
.await
|
||||
.expect("");
|
||||
info!("service {} deregistered", service_name);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user