dzura/server/src/main.rs
2024-10-02 12:29:13 -07:00

288 lines
9.3 KiB
Rust

use std::net::SocketAddr;
use bus::Bus;
mod config;
mod util;
mod app;
mod system;
mod protocol;
mod manager;
use app::App;
use hyper::body::Bytes;
use hyper_util::rt::TokioIo;
use system::{
cache::WebCache,
net::{
Server, Stream,
tcp::*,
tls::*,
},
};
#[derive(Clone)]
struct HttpServiceArgs {
bus:Bus<protocol::QRPacket>,
cache:WebCache,
}
async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<http_body_util::Full<Bytes>>, std::convert::Infallible>
// Serve cached files and upgrade websocket connections.
//
{
use hyper::{Response, body::Bytes, header::{CONTENT_TYPE, CACHE_CONTROL, ACCEPT_LANGUAGE}};
use http_body_util::Full;
println!("Serving: {}", request.uri().path());
let mut language_code = 0;
if let Some(languages) = request.headers().get(ACCEPT_LANGUAGE) {
if let Ok(languages) = languages.to_str() {
for decl in languages.split(",") {
let decl = decl.trim().to_lowercase();
if decl.len() >= 2 {
let code = &decl[..2];
language_code = match code {
"en" => 0,
"ja" => 1,
_ => { continue; }
}; break;
}
}
}
}
if hyper_tungstenite::is_upgrade_request(&request) {
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
tokio::task::spawn(async move {
match websocket.await {
Ok(websocket) => manager::handle_ws(websocket, args).await,
Err(_) => Err(()),
}.ok()
});
Ok(response)
} else {
Ok(Response::builder()
.status(401)
.body(Full::new(Bytes::new()))
.unwrap())
}
} else {
match args.cache.fetch(request.uri().path()) {
Some(data) => {
let mut output = data.data;
match request.uri().path() {
"/.js" => {
output = [
format!("let CONFIG_VERSION = \"{}\";", config::VERSION).as_bytes().to_vec(),
format!("let CONFIG_LANGUAGE = {};", language_code).as_bytes().to_vec(),
output,
].concat();
}
_ => { }
}
Ok(Response::builder()
.header(CONTENT_TYPE, &data.mime)
.header(CACHE_CONTROL, "no-cache")
.body(Full::new(Bytes::from(output))).unwrap())
}
None => Ok(Response::builder()
.header(CONTENT_TYPE, "text/html")
.header(CACHE_CONTROL, "no-cache")
.body(Full::new(Bytes::from(args.cache.fetch("/.html").unwrap().data))).unwrap()),
}
}
}
async fn handle_http<S>(stream:S, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()>
where S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
// Hand off socket connection to Hyper server.
//
{
use hyper::server::conn::http1;
use hyper::service::service_fn;
println!("Connection from {}", addr.to_string());
let io = TokioIo::new(stream);
let conn = http1::Builder::new()
.serve_connection(io, service_fn(move |req| {
service_http(req, args.clone())
}));
conn.with_upgrades().await.ok();
Ok(())
}
async fn handle_tcp(stream:TcpStream, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()>
{
handle_http(stream.to_stream(), addr, args).await
}
async fn handle_tls(stream:TlsStream, addr:SocketAddr, args:HttpServiceArgs) -> Result<(),()>
{
handle_http(stream.to_stream(), addr, args).await
}
#[tokio::main(flavor = "multi_thread", worker_threads = 12)]
async fn main()
{
println!("Server Version: {}", config::VERSION);
// Initialize application data.
let app;
if let Ok(result) = App::init() {
app = result;
} else {
println!("fatal: failed to initialize server.");
return;
}
// Initialize central bus and data serivce.
let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
match b_main.connect() {
Ok(bus) => {
tokio::spawn(async move {
manager::thread_system(app, bus).await;
});
}
Err(_) => {
println!("fatal: failed to initialize bus.");
return;
}
}
// Initialize HTTPS service.
match b_main.connect() {
Ok(bus) => {
let cache = WebCache::new();
cache.cache_file("text/html", "/.html", "www/.html").ok();
cache.cache_whitespace_minimize("/.html").ok();
cache.cache_file_group("text/css", "/.css", &[
"www/css/main.css",
"www/css/ui.css",
"www/css/form.css",
"www/css/game.css",
"www/css/util.css",
]).ok();
cache.cache_file_group("text/javascript", "/.js", &[
"www/js/const.js",
"www/js/language.js",
"www/js/util.js",
"www/js/badge.js",
"www/js/game_asset.js",
"www/js/game.js",
"www/js/interface.js",
"www/js/ui.js",
"www/js/scene.js",
"www/js/system.js",
"www/js/main.js",
]).ok();
cache.cache_file("image/png", "/favicon.png", "www/asset/favicon.png").ok();
cache.cache_file("image/png", "/favicon_notify.png", "www/asset/favicon_notify.png").ok();
let asset_path = std::path::Path::new("www/asset");
for asset in [
"promote.svg",
"heart_dawn.svg",
"behemoth_dawn.svg",
"dragon_dawn.svg",
"castle_dawn.svg",
"tower_dawn.svg",
"lance_dawn.svg",
"knight_dawn.svg",
"militia_dawn.svg",
"heart_dusk.svg",
"behemoth_dusk.svg",
"dragon_dusk.svg",
"castle_dusk.svg",
"tower_dusk.svg",
"lance_dusk.svg",
"knight_dusk.svg",
"militia_dusk.svg",
] {
if cache.cache_file("image/svg+xml", &format!("/asset/{}", asset), asset_path.join(asset)).is_err() {
println!("error: failed to load: {}", asset);
}
}
let about_path = std::path::Path::new("www/pages/about");
for doc in [
"main",
] {
if cache.cache_md(&format!("/about/{}.html", doc), about_path.join(format!("{}.md", doc))).is_err() {
println!("error: failed to load: {}", doc);
}
}
let guide_path = std::path::Path::new("www/pages/guide");
for doc in [
"game",
"pieces",
"interface",
] {
if cache.cache_md(&format!("/guide/{}.html", doc), guide_path.join(format!("{}.md", doc))).is_err() {
println!("error: failed to load: {}", doc);
}
}
let mut tcp_server = TcpServer::new();
match tcp_server.bind("127.0.0.1:38611").await {
Ok(_) => {
let b = bus.connect().unwrap();
let c = cache.clone();
tokio::spawn(async move {
while tcp_server.accept(handle_tcp, HttpServiceArgs {
bus:b.connect().unwrap(),
cache:c.clone(),
}).await.is_ok() { }
});
}
Err(_) => {
println!("error: failed to bind TCP port 38611.");
}
}
let mut tls_server = TlsServer::new();
for domain in [
"omen.kirisame.com",
"dzura.com",
] {
if tls_server.add_cert(domain, &format!("cert/{}/fullchain.pem", domain), &format!("cert/{}/privkey.pem", domain)).await.is_err() {
println!("error: failed to load TLS certificates for {}.", domain);
}
}
match tls_server.bind("0.0.0.0:38612").await {
Ok(_) => {
let b = bus.connect().unwrap();
let c = cache.clone();
tokio::spawn(async move {
while tls_server.accept(handle_tls, HttpServiceArgs {
bus:b.connect().unwrap(),
cache:c.clone(),
}).await.is_ok() { }
});
}
Err(_) => {
println!("error: failed to bind TLS port 38612.");
}
}
}
Err(_) => {
println!("error: failed to initialize HTTPS service.");
}
}
loop { tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; }
}