322 lines
9.4 KiB
Rust
322 lines
9.4 KiB
Rust
use std::net::SocketAddr;
|
|
use std::path::Path;
|
|
|
|
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 tokio::sync::mpsc::{self, Sender};
|
|
use system::{
|
|
cache::WebCache,
|
|
net::{
|
|
Server, Stream,
|
|
tcp::*,
|
|
tls::*,
|
|
},
|
|
};
|
|
use protocol::QRPacket;
|
|
|
|
#[derive(Clone)]
|
|
struct HttpServiceArgs {
|
|
tx:Sender<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());
|
|
|
|
/*
|
|
** Get client language.
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Upgrade to websocket, if reqeusted.
|
|
*/
|
|
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())
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Otherwise, serve cached assets.
|
|
*/
|
|
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
|
|
}
|
|
|
|
|
|
/*
|
|
** [multi_thread, worker_threads]
|
|
** Workaround for single core computer, where listening tasks blocked
|
|
** client-handling and data-processing tasks.
|
|
*/
|
|
#[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() {
|
|
result
|
|
} else {
|
|
println!("fatal: failed to initialize server.");
|
|
return;
|
|
};
|
|
|
|
|
|
/*
|
|
** Initialize central bus and data serivce.
|
|
*/
|
|
let (data_tx, data_rx) = mpsc::channel::<QRPacket>(1024);
|
|
tokio::spawn(async move {
|
|
manager::thread_system(app, data_rx).await;
|
|
});
|
|
|
|
|
|
/*
|
|
** Load image assets.
|
|
*/
|
|
let mut js_asset_data = String::from("const GAME_ASSET={Image:{");
|
|
let asset_path = Path::new("www/asset/");
|
|
for name in [
|
|
"Promote",
|
|
"Militia",
|
|
"Lance",
|
|
"Knight",
|
|
"Tower",
|
|
"Castle",
|
|
"Dragon",
|
|
"Behemoth",
|
|
"Heart",
|
|
] {
|
|
if let Ok(output) = util::imager::load(asset_path.join(&format!("{}.svg", name))) {
|
|
js_asset_data += &format!("{}:{},", name, output);
|
|
} else {
|
|
println!("error: failed to load asset: {}", name);
|
|
}
|
|
}
|
|
js_asset_data += "}};";
|
|
|
|
/*
|
|
** Cache source files.
|
|
*/
|
|
let cache = WebCache::new();
|
|
cache.cache("text/html", "/.html", &[
|
|
WebCache::file("www/.html"),
|
|
]).ok();
|
|
cache.cache_whitespace_minimize("/.html").ok();
|
|
cache.cache("text/css", "/.css", &[
|
|
WebCache::file("www/css/main.css"),
|
|
WebCache::file("www/css/ui.css"),
|
|
WebCache::file("www/css/form.css"),
|
|
WebCache::file("www/css/game.css"),
|
|
WebCache::file("www/css/text.css"),
|
|
WebCache::file("www/css/profile.css"),
|
|
WebCache::file("www/css/util.css"),
|
|
]).ok();
|
|
cache.cache("text/javascript", "/.js", &[
|
|
WebCache::file("www/js/const.js"),
|
|
WebCache::file("www/js/language.js"),
|
|
WebCache::file("www/js/util.js"),
|
|
WebCache::file("www/js/badge.js"),
|
|
WebCache::file("www/js/game_asset.js"),
|
|
WebCache::string(&js_asset_data),
|
|
WebCache::file("www/js/game.js"),
|
|
WebCache::file("www/js/game_config.js"),
|
|
WebCache::file("www/js/game_config_const.js"),
|
|
WebCache::file("www/js/interface.js"),
|
|
WebCache::file("www/js/ui.js"),
|
|
WebCache::file("www/js/scene.js"),
|
|
WebCache::file("www/js/system.js"),
|
|
WebCache::file("www/js/main.js"),
|
|
]).ok();
|
|
cache.cache("image/png", "/favicon.png", &[
|
|
WebCache::file("www/asset/favicon.png"),
|
|
]).ok();
|
|
cache.cache("image/png", "/favicon_notify.png", &[
|
|
WebCache::file("www/asset/favicon_notify.png"),
|
|
]).ok();
|
|
|
|
let about_path = std::path::Path::new("www/pages/about");
|
|
for doc in [
|
|
"main",
|
|
] {
|
|
if cache.cache("text/html", &format!("/about/{}.html", doc), &[
|
|
WebCache::markdown(about_path.join(format!("{}.md", doc)))
|
|
]).is_err() {
|
|
println!("error: failed to load: {}", doc);
|
|
}
|
|
}
|
|
|
|
let notice_path = std::path::Path::new("www/pages/notice");
|
|
for doc in [
|
|
"main",
|
|
] {
|
|
if cache.cache("text/html", &format!("/notice/{}.html", doc), &[
|
|
WebCache::markdown(notice_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("text/html", &format!("/guide/{}.html", doc), &[
|
|
WebCache::markdown(guide_path.join(format!("{}.md", doc))),
|
|
]).is_err() {
|
|
println!("error: failed to load: {}", doc);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** Initialize network services.
|
|
*/
|
|
let mut tcp_server = TcpServer::new();
|
|
match tcp_server.bind("127.0.0.1:38611").await {
|
|
Ok(_) => {
|
|
let c = cache.clone();
|
|
let data_tx = data_tx.clone();
|
|
tokio::spawn(async move {
|
|
while tcp_server.accept(handle_tcp, HttpServiceArgs {
|
|
tx:data_tx.clone(),
|
|
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 c = cache.clone();
|
|
let data_tx = data_tx.clone();
|
|
tokio::spawn(async move {
|
|
while tls_server.accept(handle_tls, HttpServiceArgs {
|
|
tx:data_tx.clone(),
|
|
cache:c.clone(),
|
|
}).await.is_ok() { }
|
|
});
|
|
}
|
|
Err(_) => {
|
|
println!("error: failed to bind TLS port 38612.");
|
|
}
|
|
}
|
|
|
|
loop { tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; }
|
|
}
|