puniyu_server 0.7.0

puniyu 的服务器模块
Documentation
use crate::BaseResponse;
use crate::{api, info, middleware};
use actix_web::middleware::{NormalizePath, TrailingSlash};
use actix_web::{App, HttpResponse, HttpServer, http::StatusCode, web};
use puniyu_common::APP_NAME;
use puniyu_common::path::RESOURCE_DIR;
use puniyu_registry::server::ServerRegistry;
use puniyu_types::server::{
	SERVER_COMMAND_TX, ServerCommand, get_server_config, save_server_config,
};
use std::net::IpAddr;
use std::sync::LazyLock;

fn get_host_from_env() -> IpAddr {
	std::env::var("HTTP_HOST").ok().and_then(|s| s.parse().ok()).unwrap()
}

fn get_port_from_env() -> u16 {
	std::env::var("HTTP_PORT").ok().and_then(|s| s.parse().ok()).unwrap()
}

async fn logo() -> HttpResponse {
	static LOGO: LazyLock<Option<Vec<u8>>> =
		LazyLock::new(|| std::fs::read(RESOURCE_DIR.join("logo.png")).ok());

	let logo_data = crate::LOGO
		.get()
		.and_then(|v| if v.is_empty() { None } else { Some(v.clone()) })
		.or_else(|| LOGO.as_ref().cloned());

	match logo_data {
		Some(data) => HttpResponse::Ok().content_type("image/png").body(data),
		None => BaseResponse::<()> {
			code: StatusCode::NOT_FOUND.as_u16(),
			data: None,
			message: "Logo not found".to_string(),
		}
		.send_json(),
	}
}

pub async fn run_server_with_control(
	host: Option<IpAddr>,
	port: Option<u16>,
) -> std::io::Result<()> {
	let (tx, rx) = flume::bounded::<ServerCommand>(16);
	let _ = SERVER_COMMAND_TX.set(tx);
	let init_host = host.unwrap_or_else(get_host_from_env);
	let init_port = port.unwrap_or_else(get_port_from_env);
	save_server_config(init_host, init_port);

	loop {
		let config = get_server_config();
		let host = config.as_ref().map(|c| c.host).unwrap_or_else(get_host_from_env);
		let port = config.as_ref().map(|c| c.port).unwrap_or_else(get_port_from_env);
		let addr = format!("{}:{}", host, port);

		info!("服务器在 {} 运行", addr);

		let server = HttpServer::new(|| {
			let app = App::new()
				.wrap(middleware::AccessLog)
				.wrap(NormalizePath::new(TrailingSlash::Trim))
				.service(
					web::resource("/")
						.to(|| async { format!("welcome {}", APP_NAME.get().unwrap()) }),
				)
				.service(web::resource("/logo").route(web::get().to(logo)))
				.service(web::resource("/logo.png").route(web::get().to(logo)))
				.configure(|cfg| {
					cfg.service(web::scope("/api/v1").configure(api::register_routes));
				});

			ServerRegistry::get_all()
				.into_iter()
				.fold(app, |app, service_cfg| app.configure(|cfg| service_cfg(cfg)))
		})
		.bind(&addr)?;

		let running_server = server.run();
		let handle = running_server.handle();

		tokio::select! {
			result = running_server => {
				return result;
			}
			cmd = rx.recv_async() => {
				match cmd {
					Ok(ServerCommand::Start) => {
						continue;
					}
					Ok(ServerCommand::Stop) => {
						info!("收到停止命令,正在关闭服务器...");
						handle.stop(true).await;
						return Ok(());
					}
					Ok(ServerCommand::Restart) => {
						info!("收到重启命令,正在重启服务器...");
						handle.stop(true).await;
						save_server_config(get_host_from_env(), get_port_from_env());
						continue;
					}
					Err(_) => {
						handle.stop(true).await;
						return Ok(());
					}
				}
			}
		}
	}
}

pub fn run_server_spawn(host: Option<IpAddr>, port: Option<u16>) {
	tokio::spawn(async move { run_server_with_control(host, port).await });
}