From 81068759e5166c106c2f5a38c51a90915713be20055577ed67d893f9e6142143 Mon Sep 17 00:00:00 2001 From: RavenX8 <7156279+RavenX8@users.noreply.github.com> Date: Mon, 10 Mar 2025 06:09:26 -0400 Subject: [PATCH 1/3] - update: docker compose to add custom networks - add: get service endpoints by using consul dns --- auth-service/src/main.rs | 12 ++++++-- docker-compose.yml | 47 +++++++++++++++++++++++++++++++- packet-service/src/main.rs | 2 +- utils/Cargo.toml | 1 + utils/src/consul_registration.rs | 6 ++++ utils/src/service_discovery.rs | 33 ++++++++++++++++++++-- world-service/Cargo.toml | 1 + world-service/src/main.rs | 16 +++++++---- 8 files changed, 105 insertions(+), 13 deletions(-) diff --git a/auth-service/src/main.rs b/auth-service/src/main.rs index 8e0ad14..741bac4 100644 --- a/auth-service/src/main.rs +++ b/auth-service/src/main.rs @@ -9,9 +9,9 @@ use std::env; use std::str::FromStr; use std::sync::Arc; use tonic::transport::Server; -use tracing::{info, Level}; +use tracing::{debug, info, Level}; use utils::consul_registration; -use utils::service_discovery::get_service_address; +use utils::service_discovery::{get_service_address, get_service_endpoints_by_dns}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -29,12 +29,18 @@ async fn main() -> Result<(), Box> { let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string()); let port = env::var("AUTH_SERVICE_PORT").unwrap_or_else(|_| "50051".to_string()); - let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string()); + let consul_address = env::var("CONSUL_ADDRESS").unwrap_or_else(|_| "127.0.0.1".to_string()); + let consul_port = env::var("CONSUL_PORT").unwrap_or_else(|_| "8500".to_string()); + let consul_dns_port = env::var("CONSUL_DNS_PORT").unwrap_or_else(|_| "8600".to_string()); + let consul_url = format!("http://{}:{}", consul_address, consul_port); + // 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(|_| "auth-service".to_string()); let service_address = env::var("AUTH_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string()); let service_port = port.clone(); let db_nodes = get_service_address(&consul_url, "database-service").await?; let session_nodes = get_service_address(&consul_url, "session-service").await?; + let temp_session_nodes = get_service_endpoints_by_dns(format!("{}:{}", consul_address, consul_dns_port).as_str(), "grpc", "session-service").await?; + debug!("{:?}", temp_session_nodes); // Register service with Consul let service_id = consul_registration::get_or_generate_service_id(env!("CARGO_PKG_NAME")); diff --git a/docker-compose.yml b/docker-compose.yml index 0231588..242dbd5 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,8 @@ dockerfile: Dockerfile restart: on-failure container_name: frontend + networks: + - frontend ports: - "3000:80" env_file: @@ -18,6 +20,8 @@ context: ./ dockerfile: ./auth-service/Dockerfile restart: on-failure + networks: + - backend ports: - "50051:50051" env_file: @@ -37,6 +41,9 @@ context: ./ dockerfile: ./api-service/Dockerfile restart: on-failure + networks: + - frontend + - backend ports: - "8080:8080" - "8081:8081" @@ -54,6 +61,8 @@ context: ./ dockerfile: ./database-service/Dockerfile restart: on-failure + networks: + - backend ports: - "50052:50052" env_file: @@ -70,6 +79,8 @@ context: ./ dockerfile: ./character-service/Dockerfile restart: on-failure + networks: + - backend ports: - "50053:50053" env_file: @@ -86,6 +97,8 @@ context: ./ dockerfile: ./world-service/Dockerfile restart: on-failure + networks: + - backend ports: - "50054:50054" env_file: @@ -102,9 +115,12 @@ context: ./ dockerfile: ./packet-service/Dockerfile restart: on-failure + networks: + - backend ports: - "29000:29000" - "4001:4001" + - "8082:8082" env_file: - ./packet-service/.env - .env @@ -119,6 +135,8 @@ context: ./ dockerfile: ./session-service/Dockerfile restart: on-failure + networks: + - backend ports: - "50055:50055" env_file: @@ -134,6 +152,9 @@ env_file: - .env restart: on-failure + networks: + backend: + ipv4_address: 172.16.238.4 ports: - "6379:6379" volumes: @@ -144,6 +165,9 @@ env_file: - .env restart: on-failure + networks: + backend: + ipv4_address: 172.16.238.3 ports: - "5432:5432" volumes: @@ -152,10 +176,21 @@ consul: image: consul:1.15.4 - command: agent -dev -client=0.0.0.0 + command: [ + "agent", + "-dev", + "-bootstrap-expect=1", + "-client=0.0.0.0", + "-bind=0.0.0.0", + ] restart: on-failure + networks: + backend: + ipv4_address: 172.16.238.2 ports: - "8500:8500" + - "8600:8600/udp" + - "8600:8600/tcp" volumes: - ./scripts/consul.json:/consul/config/cors.json @@ -163,3 +198,13 @@ volumes: db_data: cache_data: service_ids: + +networks: + backend: + ipam: + driver: default + config: + - subnet: "172.16.238.0/24" + - subnet: "2001:3984:3989::/64" + frontend: + driver: bridge \ No newline at end of file diff --git a/packet-service/src/main.rs b/packet-service/src/main.rs index 44b41e7..f33d816 100644 --- a/packet-service/src/main.rs +++ b/packet-service/src/main.rs @@ -83,7 +83,7 @@ async fn main() -> Result<(), Box> { // Register service with Consul let service_id = consul_registration::get_or_generate_service_id(env!("CARGO_PKG_NAME")); let version = env!("CARGO_PKG_VERSION").to_string(); - let tags = vec![version]; + let tags = vec![version, "tcp".to_string()]; let mut meta = HashMap::new(); consul_registration::register_service( &consul_url, diff --git a/utils/Cargo.toml b/utils/Cargo.toml index a72ab04..90d6284 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -15,3 +15,4 @@ redis = "0.29.1" deadpool-redis = "0.20.0" async-trait = "0.1.87" serde_json = "1.0.140" +hickory-resolver = "0.24.4" diff --git a/utils/src/consul_registration.rs b/utils/src/consul_registration.rs index 7edaba9..9bbb729 100644 --- a/utils/src/consul_registration.rs +++ b/utils/src/consul_registration.rs @@ -64,6 +64,12 @@ pub async fn register_service( "port": service_port, "tags": tags, "meta": meta, + "tagged_addresses": { + "lan": { + "address": service_address.to_string(), + "port": service_port, + } + }, "check": { check_protocol: check_address, "interval": "10s", diff --git a/utils/src/service_discovery.rs b/utils/src/service_discovery.rs index 308a514..fe44d48 100644 --- a/utils/src/service_discovery.rs +++ b/utils/src/service_discovery.rs @@ -1,5 +1,32 @@ -use serde::Deserialize; +use hickory_resolver::config::*; +use hickory_resolver::{Resolver, TokioAsyncResolver}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::net::SocketAddr; +use tokio::runtime::Runtime; +use tracing::log::debug; + +pub async fn get_service_endpoints_by_dns(consul_url: &str, service_protocol: &str, service_name: &str) -> Result, Box> { + let mut rc = ResolverConfig::new(); + let url = consul_url.parse()?; + rc.add_name_server(NameServerConfig::new(url, Protocol::Tcp)); + + let resolver = TokioAsyncResolver::tokio(rc, ResolverOpts::default()); + + let srv_name = format!("_{}._{}.service.consul", service_name, service_protocol); + let srv_record = resolver.srv_lookup(&srv_name).await?; + + let mut endpoints = Vec::new(); + debug!("service records: {:?}", srv_record); + for record in srv_record { + let hostname = record.target(); + debug!("hostname: {:?}", hostname); + + // endpoints.push(SocketAddr::new(, record.port())); + } + + Ok(endpoints) +} #[derive(Debug, Deserialize)] pub struct ServiceNode { @@ -24,7 +51,7 @@ pub async fn get_service_address( service_name, response.status() ) - .into()); + .into()); } // Deserialize the response into a Vec @@ -52,7 +79,7 @@ async fn get_services_with_tag( service_name, response.status() ) - .into()); + .into()); } // Deserialize the response into a Vec diff --git a/world-service/Cargo.toml b/world-service/Cargo.toml index 2a6a176..ae131d1 100644 --- a/world-service/Cargo.toml +++ b/world-service/Cargo.toml @@ -13,6 +13,7 @@ tracing-subscriber = "0.3.18" tonic = "0.12.3" prost = "0.13.4" warp = "0.3.7" +tonic-health = "0.12.3" [build-dependencies] tonic-build = "0.12.3" \ No newline at end of file diff --git a/world-service/src/main.rs b/world-service/src/main.rs index fc6bbb5..abb26e5 100644 --- a/world-service/src/main.rs +++ b/world-service/src/main.rs @@ -2,9 +2,9 @@ use dotenv::dotenv; use std::collections::HashMap; use std::env; use std::str::FromStr; -use tracing::Level; +use tracing::{debug, Level}; use utils::consul_registration; -use utils::service_discovery::get_service_address; +use utils::service_discovery::{get_service_address, get_service_endpoints_by_dns}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -21,15 +21,21 @@ async fn main() -> Result<(), Box> { let port = env::var("WORLD_SERVICE_PORT").unwrap_or_else(|_| "50054".to_string()); let health_port = env::var("HEALTH_CHECK_PORT").unwrap_or_else(|_| "8084".to_string()); - let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string()); + // let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string()); + let consul_address = env::var("CONSUL_ADDRESS").unwrap_or_else(|_| "127.0.0.1".to_string()); + let consul_port = env::var("CONSUL_PORT").unwrap_or_else(|_| "8500".to_string()); + let consul_dns_port = env::var("CONSUL_DNS_PORT").unwrap_or_else(|_| "8600".to_string()); + let consul_url = format!("http://{}:{}", consul_address, consul_port); let service_name = env::var("SERVICE_NAME").unwrap_or_else(|_| "world-service".to_string()); let service_address = env::var("WORLD_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string()); let service_port = port.clone(); let health_check_url = format!("http://{}:{}/health", service_address, health_port); - let health_check_endpoint_addr = format!("{}:{}", service_address, health_port); let db_nodes = get_service_address(&consul_url, "database-service").await?; + let temp_db_nodes = get_service_endpoints_by_dns(format!("{}:{}", consul_address, consul_dns_port).as_str(), "grpc", "database-service").await?; + debug!("{:?}", temp_db_nodes); + // Register service with Consul let service_id = consul_registration::get_or_generate_service_id(env!("CARGO_PKG_NAME")); let version = env!("CARGO_PKG_VERSION").to_string(); @@ -47,7 +53,7 @@ async fn main() -> Result<(), Box> { Some("http"), Some(&health_check_url), ) - .await?; + .await?; // Start health-check endpoint consul_registration::start_health_check(addr.as_str()).await?; -- 2.49.1 From cdf7bb3f159b5fedcaf8c572096e88d92b0c421f7c16b052fcee4452475a0978 Mon Sep 17 00:00:00 2001 From: raven <7156279+RavenX8@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:39:45 -0400 Subject: [PATCH 2/3] - update: tell consul to use docker dns to resolve CNAME addresses - add: load balancer for consul services - update: dns lookup to now return the service address - update: docker consul to the latest version --- auth-service/src/main.rs | 46 ++++---- docker-compose.yml | 2 +- scripts/consul.json | 3 + utils/Cargo.toml | 1 + utils/src/lib.rs | 1 + utils/src/multi_service_load_balancer.rs | 135 +++++++++++++++++++++++ utils/src/service_discovery.rs | 9 +- 7 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 utils/src/multi_service_load_balancer.rs diff --git a/auth-service/src/main.rs b/auth-service/src/main.rs index 741bac4..16cbb5e 100644 --- a/auth-service/src/main.rs +++ b/auth-service/src/main.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use tonic::transport::Server; use tracing::{debug, info, Level}; use utils::consul_registration; +use utils::multi_service_load_balancer::{LoadBalancingStrategy, MultiServiceLoadBalancer}; use utils::service_discovery::{get_service_address, get_service_endpoints_by_dns}; #[tokio::main] @@ -33,14 +34,35 @@ async fn main() -> Result<(), Box> { let consul_port = env::var("CONSUL_PORT").unwrap_or_else(|_| "8500".to_string()); let consul_dns_port = env::var("CONSUL_DNS_PORT").unwrap_or_else(|_| "8600".to_string()); let consul_url = format!("http://{}:{}", consul_address, consul_port); - // let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string()); + let consul_dns_url = format!("{}:{}", consul_address, consul_dns_port); let service_name = env::var("SERVICE_NAME").unwrap_or_else(|_| "auth-service".to_string()); let service_address = env::var("AUTH_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string()); let service_port = port.clone(); - let db_nodes = get_service_address(&consul_url, "database-service").await?; - let session_nodes = get_service_address(&consul_url, "session-service").await?; - let temp_session_nodes = get_service_endpoints_by_dns(format!("{}:{}", consul_address, consul_dns_port).as_str(), "grpc", "session-service").await?; - debug!("{:?}", temp_session_nodes); + + let lb = MultiServiceLoadBalancer::new(&consul_dns_url, LoadBalancingStrategy::RoundRobin); + + let mut db_url = "".to_string(); + match lb.get_endpoint("database-service", "grpc").await? { + Some(endpoint) => { + db_url = format!("http://{}", endpoint); + }, + None => { + println!("No endpoints available for database-service"); + } + } + + let mut session_service_address = "".to_string(); + match lb.get_endpoint("session-service", "grpc").await? { + Some(endpoint) => { + session_service_address = format!("http://{}", endpoint); + }, + None => { + println!("No endpoints available for session-service"); + } + } + + let db_client = Arc::new(DatabaseClient::connect(&db_url).await?); + let session_client = Arc::new(SessionServiceClient::connect(session_service_address).await?); // Register service with Consul let service_id = consul_registration::get_or_generate_service_id(env!("CARGO_PKG_NAME")); @@ -60,20 +82,6 @@ async fn main() -> Result<(), Box> { ) .await?; - let db_address = db_nodes.get(0).unwrap(); - let db_url = format!( - "http://{}:{}", - db_address.ServiceAddress, db_address.ServicePort - ); - let db_client = Arc::new(DatabaseClient::connect(&db_url).await?); - - let session_address = session_nodes.get(0).unwrap(); - let session_address = format!( - "http://{}:{}", - session_address.ServiceAddress, session_address.ServicePort - ); - let session_client = Arc::new(SessionServiceClient::connect(session_address).await?); - let full_addr = format!("{}:{}", &addr, port); let address = full_addr.parse().expect("Invalid address"); let auth_service = MyAuthService { diff --git a/docker-compose.yml b/docker-compose.yml index 242dbd5..2b395b4 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -175,7 +175,7 @@ - ./sql/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro consul: - image: consul:1.15.4 + image: hashicorp/consul:latest command: [ "agent", "-dev", diff --git a/scripts/consul.json b/scripts/consul.json index c36b4d0..f01ca0a 100755 --- a/scripts/consul.json +++ b/scripts/consul.json @@ -1,4 +1,7 @@ { + "recursors": [ + "127.0.0.11" + ], "http_config": { "response_headers": { "Access-Control-Allow-Origin": "*" diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 90d6284..72dbab9 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -16,3 +16,4 @@ deadpool-redis = "0.20.0" async-trait = "0.1.87" serde_json = "1.0.140" hickory-resolver = "0.24.4" +rand = "0.8.5" diff --git a/utils/src/lib.rs b/utils/src/lib.rs index ff75744..06e1115 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -3,3 +3,4 @@ pub mod null_string; pub mod redis_cache; pub mod service_discovery; pub mod signal_handler; +pub mod multi_service_load_balancer; diff --git a/utils/src/multi_service_load_balancer.rs b/utils/src/multi_service_load_balancer.rs new file mode 100644 index 0000000..81d49aa --- /dev/null +++ b/utils/src/multi_service_load_balancer.rs @@ -0,0 +1,135 @@ +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use rand::seq::SliceRandom; +use crate::service_discovery::get_service_endpoints_by_dns; + +pub enum LoadBalancingStrategy { + Random, + RoundRobin, +} + +// Service identifier +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ServiceId { + pub name: String, + pub protocol: String, +} + +impl ServiceId { + pub fn new(name: &str, protocol: &str) -> Self { + ServiceId { + name: name.to_string(), + protocol: protocol.to_string(), + } + } +} + +// Per-service state +struct ServiceState { + endpoints: Vec, + current_index: usize, +} + +impl ServiceState { + fn new(endpoints: Vec) -> Self { + ServiceState { + endpoints, + current_index: 0, + } + } + + fn get_endpoint(&mut self, strategy: &LoadBalancingStrategy) -> Option { + if self.endpoints.is_empty() { + return None; + } + + match strategy { + LoadBalancingStrategy::Random => { + let mut rng = rand::thread_rng(); + self.endpoints.choose(&mut rng).copied() + } + LoadBalancingStrategy::RoundRobin => { + let endpoint = self.endpoints[self.current_index].clone(); + self.current_index = (self.current_index + 1) % self.endpoints.len(); + Some(endpoint) + } + } + } +} + +pub struct MultiServiceLoadBalancer { + consul_url: String, + strategy: LoadBalancingStrategy, + services: Arc>>, +} + +impl MultiServiceLoadBalancer { + pub fn new(consul_url: &str, strategy: LoadBalancingStrategy) -> Self { + MultiServiceLoadBalancer { + consul_url: consul_url.to_string(), + strategy, + services: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub async fn get_endpoint( + &self, + service_name: &str, + service_protocol: &str, + ) -> Result, Box> { + let service_id = ServiceId::new(service_name, service_protocol); + + // Try to get an endpoint from the cache first + { + let mut services = self.services.lock().unwrap(); + if let Some(service_state) = services.get_mut(&service_id) { + if let Some(endpoint) = service_state.get_endpoint(&self.strategy) { + return Ok(Some(endpoint)); + } + } + } + + // If we don't have endpoints or they're all unavailable, refresh them + self.refresh_service_endpoints(service_name, service_protocol).await?; + + // Try again after refresh + let mut services = self.services.lock().unwrap(); + if let Some(service_state) = services.get_mut(&service_id) { + return Ok(service_state.get_endpoint(&self.strategy)); + } + + Ok(None) + } + + pub async fn refresh_service_endpoints( + &self, + service_name: &str, + service_protocol: &str, + ) -> Result<(), Box> { + let endpoints = get_service_endpoints_by_dns( + &self.consul_url, + service_protocol, + service_name, + ).await?; + + let service_id = ServiceId::new(service_name, service_protocol); + let mut services = self.services.lock().unwrap(); + + services.insert(service_id, ServiceState::new(endpoints)); + Ok(()) + } + + pub async fn refresh_all_services(&self) -> Result<(), Box> { + let service_ids: Vec = { + let services = self.services.lock().unwrap(); + services.keys().cloned().collect() + }; + + for service_id in service_ids { + self.refresh_service_endpoints(&service_id.name, &service_id.protocol).await?; + } + + Ok(()) + } +} \ No newline at end of file diff --git a/utils/src/service_discovery.rs b/utils/src/service_discovery.rs index fe44d48..82471e2 100644 --- a/utils/src/service_discovery.rs +++ b/utils/src/service_discovery.rs @@ -3,6 +3,7 @@ use hickory_resolver::{Resolver, TokioAsyncResolver}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::SocketAddr; +use std::str::FromStr; use tokio::runtime::Runtime; use tracing::log::debug; @@ -17,12 +18,12 @@ pub async fn get_service_endpoints_by_dns(consul_url: &str, service_protocol: &s let srv_record = resolver.srv_lookup(&srv_name).await?; let mut endpoints = Vec::new(); - debug!("service records: {:?}", srv_record); for record in srv_record { let hostname = record.target(); - debug!("hostname: {:?}", hostname); - - // endpoints.push(SocketAddr::new(, record.port())); + let lookup_responses = resolver.lookup_ip(hostname.to_string()).await?; + for response in lookup_responses { + endpoints.push(SocketAddr::from_str(&format!("{}:{}", &response.to_string(), record.port()))?); + } } Ok(endpoints) -- 2.49.1 From cbd71d1ab1bb0773636916da5acd6f584d2dcc0f03f2ce6a492059375bf85364 Mon Sep 17 00:00:00 2001 From: RavenX8 <7156279+RavenX8@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:16:57 -0400 Subject: [PATCH 3/3] - add: logout route to api service --- api-service/src/axum_gateway.rs | 40 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/api-service/src/axum_gateway.rs b/api-service/src/axum_gateway.rs index 2d74c48..9452483 100644 --- a/api-service/src/axum_gateway.rs +++ b/api-service/src/axum_gateway.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use tonic::transport::Channel; use tower_http::cors::{Any, CorsLayer}; +use crate::axum_gateway::auth::LogoutRequest; use auth::auth_service_client::AuthServiceClient; use auth::{LoginRequest, RegisterRequest}; use log::{error, info}; @@ -32,6 +33,16 @@ struct RestLoginResponse { session_id: String, } +#[derive(Serialize, Deserialize)] +struct RestLogoutRequest { + session_id: String, +} + +#[derive(Serialize, Deserialize)] +struct RestLogoutResponse { + message: String, +} + #[derive(Serialize, Deserialize)] struct RestRegisterRequest { username: String, @@ -52,8 +63,6 @@ async fn login_handler( ) -> Result, axum::http::StatusCode> { let ip_address = addr.ip().to_string(); - info!("Client IP Address: {}", ip_address); - let request = tonic::Request::new(LoginRequest { username: payload.username.clone(), password: payload.password.clone(), @@ -75,6 +84,28 @@ async fn login_handler( } } +async fn logout_handler( + ConnectInfo(addr): ConnectInfo, + State(grpc_client): State>>>, + Json(payload): Json, +) -> Result { + let ip_address = addr.ip().to_string(); + + let request = tonic::Request::new(LogoutRequest { + session_id: payload.session_id + }); + + let mut client = grpc_client.lock().await; // Lock the mutex to get mutable access + match client.logout(request).await { + Ok(_response) => { + Ok(axum::http::StatusCode::OK) + } + Err(_e) => { + Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR) + } + } +} + async fn register_handler( ConnectInfo(addr): ConnectInfo, State(grpc_client): State>>>, @@ -110,6 +141,7 @@ pub async fn serve_rest_api( let app = Router::new() .route("/api/login", post(login_handler)) + .route("/api/logout", post(logout_handler)) .route("/api/register", post(register_handler)) .with_state(grpc_client) .layer(cors); @@ -123,8 +155,8 @@ pub async fn serve_rest_api( listener, app.into_make_service_with_connect_info::(), ) - .await - .unwrap(); + .await + .unwrap(); Ok(()) } -- 2.49.1