Make server configurable through environment.

This commit is contained in:
Condorra 2024-09-01 00:13:30 +10:00
parent 528d264b02
commit 8dfd84343d
4 changed files with 123 additions and 13 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
localdata

8
Cargo.lock generated
View File

@ -306,6 +306,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "autocfg"
version = "1.3.0"
@ -1381,9 +1387,11 @@ version = "0.1.0"
dependencies = [
"actix-web",
"actix-ws",
"anyhow",
"env_logger",
"futures-util",
"log",
"serde_json",
"tokio",
]

View File

@ -8,7 +8,9 @@ edition = "2021"
[dependencies]
actix-web = "4.9.0"
actix-ws = "0.3.0"
anyhow = "1.0.86"
env_logger = "0.11.5"
futures-util = "0.3.30"
log = "0.4.22"
serde_json = "1.0.127"
tokio = { version = "1.39.3", features = ["net", "macros", "tokio-macros", "rt-multi-thread"] }

View File

@ -1,21 +1,66 @@
use actix_web::{
self, get,
self,
error::InternalError,
get,
http::StatusCode,
middleware::Logger,
rt::{self, net::TcpStream},
web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
web::{self, Data},
App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
};
use actix_ws::{AggregatedMessage, CloseCode, Closed};
use anyhow::Context;
use futures_util::StreamExt;
use serde_json::json;
use std::{env, fs, num::ParseIntError};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub struct ServerConfig {
pub upstream_mud: String,
pub banner_to_mud: String,
pub listen_port: u16,
pub bind_address: String,
pub startup_script_file: String,
}
#[get("/ws")]
async fn ws(req: HttpRequest, body: web::Payload) -> impl Responder {
async fn ws(
config_data: Data<ServerConfig>,
req: HttpRequest,
body: web::Payload,
) -> impl Responder {
let (response, mut session, stream) = actix_ws::handle(&req, body)?;
let mut stream = stream.aggregate_continuations().max_continuation_size(1024);
let mut tcp_stream: TcpStream = TcpStream::connect("localhost:4000").await?;
let mut tcp_stream: TcpStream = TcpStream::connect(&config_data.upstream_mud).await?;
let subst_banner = config_data
.banner_to_mud
.replace(
"%i",
&req.peer_addr()
.map(|a| a.to_string())
.unwrap_or_else(|| "unknown".to_owned()),
)
.replace("%n", "\r\n");
tcp_stream.write_all(subst_banner.as_bytes()).await?;
let script = fetch_startup_script(&config_data.startup_script_file)
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?;
rt::spawn(async move {
if session
.text(
json!({
"RunLua": script
})
.to_string(),
)
.await
.is_err()
{
let _ = session.close(Some(CloseCode::Normal.into())).await;
return;
}
let mut readbuf: [u8; 1024] = [0; 1024];
loop {
tokio::select! {
@ -56,14 +101,68 @@ async fn ws(req: HttpRequest, body: web::Payload) -> impl Responder {
Ok::<HttpResponse, Error>(response)
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
HttpServer::new(|| {
let logger = Logger::default();
App::new().wrap(logger).service(ws)
fn extract_server_config_from_environment() -> anyhow::Result<ServerConfig> {
Ok(ServerConfig {
upstream_mud: env::var("UPSTREAM_MUD").map_err(|_| {
anyhow::Error::msg(
"Expected UPSTREAM_MUD environment variable specifying where to connect.",
)
})?,
banner_to_mud: env::var("BANNER_TO_MUD").map_err(|_| {
anyhow::Error::msg(
"Expected BANNER_TO_MUD environment variable specifying message to send to MUD.",
)
})?,
listen_port: env::var("LISTEN_PORT")
.map_err(|_| {
anyhow::Error::msg(
"Expected LISTEN_PORT environment variable specifying port to listen on.",
)
})
.and_then(|v| {
v.parse::<u16>().map_err(|_e: ParseIntError| {
anyhow::Error::msg("LISTEN_PORT should be a decimal port number")
})
})?,
bind_address: env::var("BIND_ADDRESS").unwrap_or_else(|_| "::".to_owned()),
startup_script_file: env::var("STARTUP_SCRIPT_FILE")
.map_err(|_| {
anyhow::Error::msg(
"Expected STARTUP_SCRIPT_FILE environment variable containing filename of script to send to client.",
)
})?
,
})
.bind(("127.0.0.1", 8124))?
.run()
.await
}
// We load this on each connection so it can change.
fn fetch_startup_script(filename: &str) -> anyhow::Result<String> {
Ok(fs::read_to_string(filename)?)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
let config = extract_server_config_from_environment()?;
let data = Data::new(config);
let config = data.get_ref();
// Do this early so we fail fast if config is wrong.
fetch_startup_script(&config.startup_script_file)
.context("While checking STARTUP_SCRIPT_FILE can be read")?;
let server_data = data.clone();
HttpServer::new(move || {
let logger = Logger::default();
App::new()
.wrap(logger)
.app_data(server_data.clone())
.service(ws)
})
.bind((config.bind_address.clone(), config.listen_port))?
.run()
.await?;
Ok(())
}