diff --git a/Cargo.lock b/Cargo.lock index e214ba7..7166ce7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "mcstatusface" -version = "1.2.0" +version = "1.0.1" dependencies = [ "chrono", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9c5b648..9ea234c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "mcstatusface" -authors = ["ari melody "] -repository = "https://codeberg.org/arimelody/mcstatusface" +authors = ["ari melody "] +repository = "https://git.arimelody.me/ari/mcstatusface" license = "MIT" keywords = ["minecraft", "server", "query", "web"] -version = "1.2.0" +version = "1.1.0" edition = "2024" [dependencies] diff --git a/src/dns.rs b/src/dns.rs deleted file mode 100644 index 8cfb46c..0000000 --- a/src/dns.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{io::{Error, ErrorKind, Result}, net::UdpSocket}; - -const CLOUDFLARE_DNS: &str = "1.1.1.1"; - -const RECURSION_DESIRED: u16 = 1 << 8; - -const RECORD_TYPE_SRV: u16 = 33; -const RECORD_CLASS_IN: u16 = 1; - -pub fn create_dns_query(qname: &String, qtype: u16, qclass: u16) -> Result> { - let qname = match qname.is_ascii() { - true => qname, - false => return Err(Error::new(ErrorKind::InvalidInput, "domain is not valid ASCII")), - }; - - let txid = 1u16; - let flags = RECURSION_DESIRED; - let qdcount = 1u16; - let ancount = 0u16; - let nscount = 0u16; - let arcount = 0u16; - - let header: [u16; 6] = - [txid, flags, qdcount, ancount, nscount, arcount] - .map(|el| el.to_be()); - - // let mut qname_bytes = bitvec![u8, Msb0;]; - let mut query = Vec::::new(); - for mut label in qname.split(".") { - label = match label.len() > u8::MAX.into() { - true => label.split_at(u8::MAX.into()).0, - false => label - }; - let len: u8 = label.len().try_into().unwrap(); - query.push(len); - query.append(&mut label.as_bytes().to_vec()); - } - query.push(0); - query.append(&mut qtype.to_be_bytes().to_vec()); - query.append(&mut qclass.to_be_bytes().to_vec()); - - let mut request = Vec::::new(); - for el in header { - request.push(el as u8); - request.push((el >> 8) as u8); - } - request.append(&mut query); - - Ok(request) -} - -#[derive(Debug)] -pub struct DnsSrvResponse { - qref: u16, - rtype: u16, - class: u16, - ttl: u32, - priority: u16, - weight: u16, - port: u16, -} - -pub fn parse_srv_response(mut ptr: usize, recv: &[u8]) -> Option { - let mut data = DnsSrvResponse { - qref: 0, - rtype: 0, - class: 0, - ttl: 0, - priority: 0, - weight: 0, - port: 0, - }; - - fn read16(buf: &[u8], offset: usize) -> u16 { - let left = (buf[offset] as u16) << 8; - let right = buf[offset + 1] as u16; - left + right - } - - fn read32(buf: &[u8], offset: usize) -> u32 { - let left = (read16(buf, offset) as u32) << 16; - let right = read16(buf, offset + 2) as u32; - left + right - } - - data.qref = read16(recv, ptr); - ptr += 2; - data.rtype = read16(recv, ptr); - ptr += 2; - if data.rtype != RECORD_TYPE_SRV { - return None - } - data.class = read16(recv, ptr); - ptr += 2; - data.ttl = read32(recv, ptr); - ptr += 4; - let _rdata_len = read16(recv, ptr); - ptr += 2; - data.priority = read16(recv, ptr); - ptr += 2; - data.weight = read16(recv, ptr); - ptr += 2; - data.port = read16(recv, ptr); - // optional: read target? - - // print!("data: {:#?}\n", data); - - Some(data) -} - -pub fn resolve_srv_port(domain: &String) -> Option { - let request = create_dns_query( - &("_minecraft._tcp.".to_string() + domain), - RECORD_TYPE_SRV, - RECORD_CLASS_IN).unwrap(); - - let socket = UdpSocket::bind("0.0.0.0:0").expect("failed to bind to port!"); - socket.connect(CLOUDFLARE_DNS.to_string() + ":53").expect("failed to connect to dns server"); - socket.send(&request).expect("failed to send dns request"); - - let mut recv_buf: [u8; 1024] = [0; 1024]; - let _ = socket.recv_from(&mut recv_buf).unwrap(); - - match parse_srv_response(request.len(), &recv_buf) { - Some(data) => Some(data.port), - None => None, - } -} diff --git a/src/http.rs b/src/http.rs index 90e0d49..131c96e 100644 --- a/src/http.rs +++ b/src/http.rs @@ -8,7 +8,7 @@ use std::{ use chrono::Local; -use crate::thread::ThreadPool; +use crate::ThreadPool; #[derive(Clone, Copy)] pub enum StatusCode { diff --git a/src/leb128.rs b/src/leb128.rs index 60247ad..7f7b091 100644 --- a/src/leb128.rs +++ b/src/leb128.rs @@ -1,34 +1,38 @@ const CONTINUE_BIT: u8 = 0x80; -pub fn write_leb128(buffer: &mut Vec, mut value: u64) -> usize { - let mut size: usize = 0; - loop { - let mut byte: u8 = (value & (std::u8::MAX as u64)).try_into().unwrap(); - value >>= 7; - if value != 0 { - byte |= CONTINUE_BIT; +pub struct LEB128; + +impl LEB128 { + pub fn write_leb128(buffer: &mut Vec, mut value: u64) -> usize { + let mut size: usize = 0; + loop { + let mut byte: u8 = (value & (std::u8::MAX as u64)).try_into().unwrap(); + value >>= 7; + if value != 0 { + byte |= CONTINUE_BIT; + } + + buffer.push(byte); + size += 1; + + if value == 0 { + return size; + } } + } - buffer.push(byte); - size += 1; - - if value == 0 { - return size; + pub fn read_leb128(buffer: &[u8]) -> (u32, usize) { + let mut result: u32 = 0; + let mut shift: usize = 0; + let mut offset: usize = 0; + loop { + let byte: u8 = buffer[offset]; + result |= ((byte & (std::i8::MAX as u8)) as u32) << (shift as u32); + offset += 1; + if byte & CONTINUE_BIT == 0 || offset == 4 { + return (result, offset); + } + shift += 7; } } } - -pub fn read_leb128(buffer: &[u8]) -> (u32, usize) { - let mut result: u32 = 0; - let mut shift: usize = 0; - let mut offset: usize = 0; - loop { - let byte: u8 = buffer[offset]; - result |= ((byte & (std::i8::MAX as u8)) as u32) << (shift as u32); - offset += 1; - if byte & CONTINUE_BIT == 0 || offset == 4 { - return (result, offset); - } - shift += 7; - } -} diff --git a/src/lib.rs b/src/lib.rs index 954b787..8a34bdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,87 @@ +use std::{sync::{mpsc, Arc, Mutex}, thread}; + pub mod leb128; pub mod status; -pub mod thread; pub mod http; -pub mod dns; +pub use status::MinecraftStatus; + +pub struct ThreadPool { + workers: Vec, + sender: Option>, +} + +type Job = Box; + +impl ThreadPool { + // Create a new ThreadPool with `size` available threads. + // + // # Panics + // + // `new` will panic if `size` is zero. + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(ThreadWorker::new(id, Arc::clone(&receiver))); + } + + ThreadPool { + workers, + sender: Some(sender) + } + } + + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static + { + let job = Box::new(f); + + self.sender.as_ref().unwrap().send(job).unwrap(); + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + + for worker in &mut self.workers.drain(..) { + println!("Shutting down worker {}", worker.id); + + worker.thread.join().unwrap(); + } + } +} + +struct ThreadWorker { + id: usize, + thread: thread::JoinHandle<()>, +} + +impl ThreadWorker { + fn new(id: usize, receiver: Arc>>) -> ThreadWorker { + let thread = thread::spawn(move || loop { + let msg = receiver.lock().unwrap().recv(); + + match msg { + Ok(job) => { + // println!("Job received by worker {id}"); + job(); + } + Err(_) => { + // println!("Worker {id} disconnected. Shutting down..."); + break; + } + } + }); + + ThreadWorker { id, thread } + } +} + diff --git a/src/main.rs b/src/main.rs index fddb15f..c9d1ffc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,19 @@ -use std::io::Result; +use std::io::{Result}; use std::net::{IpAddr, ToSocketAddrs}; use std::str::FromStr; -use std::env; +use std::{env}; use mcstatusface::http::{HttpServer, StatusCode}; -use mcstatusface::status::MinecraftStatus; -use mcstatusface::dns::resolve_srv_port; +use mcstatusface::{MinecraftStatus}; #[derive(serde::Serialize)] struct MinecraftStatusResponse<'a> { version: &'a String, players: u32, max_players: u32, - enforces_secure_chat: bool, - favicon: Option<&'a String>, motd: String, } -const DEFAULT_PORT: u16 = 25565; - fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() < 2 { @@ -36,15 +31,7 @@ env!("CARGO_PKG_VERSION")); if args[1] != "serve" { let mut address = String::from(args[1].as_str()); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; - - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } + if !address.contains(":") { address.push_str(":25565"); } let mut addrs_iter = address.to_socket_addrs().unwrap(); let address = addrs_iter.next().unwrap(); @@ -52,10 +39,6 @@ env!("CARGO_PKG_VERSION")); println!("Version: {} ({})", status.version.name, status.version.protocol); println!("Players: {}/{}", status.players.online, status.players.max); - println!( - "Enforces Secure Chat: {}", - if status.enforces_secure_chat() { "true" } else { "false" }, - ); println!("MOTD:"); println!("{}", status.parse_description()); @@ -66,20 +49,20 @@ env!("CARGO_PKG_VERSION")); if args.len() > 2 { address = args[2].to_string() } let trusted_proxies: Vec = match env::var("MCSTATUSFACE_TRUSTED_PROXIES") { - Err(_) => { vec![] } Ok(envar) => { let mut trusted_proxies: Vec = Vec::new(); for addr in envar.split(",") { match IpAddr::from_str(addr) { - Err(_) => {} Ok(addr) => { trusted_proxies.push(addr); } + Err(_) => {} } } trusted_proxies } + Err(_) => { vec![] } }; - HttpServer::new(address, 16, trusted_proxies).start(|request, mut response| { + HttpServer::new(address, 64, trusted_proxies).start(|request, mut response| { response.status(StatusCode::OK); response.set_header("Content-Type", "text/plain".to_string()); response.set_header("x-powered-by", "GIRL FUEL".to_string()); @@ -107,87 +90,56 @@ env!("CARGO_PKG_VERSION")); response.status(StatusCode::OK); let query_response: String; match request.query().get("s") { - None => { - query_response = String::from(""); - } Some(query_address) => { let mut address = query_address.to_string(); - address = address.replace("%3A", ":"); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; - - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } + if !address.contains(":") { address.push_str(":25565"); } match address.to_socket_addrs() { Err(_) => { + response.set_header("Content-Type", "text/html".to_string()); response.status(StatusCode::BadRequest); - query_response = format!( - "
-

Server Details

-
Invalid server address: {}.
", - sanitize_html(&address.to_string()), - ); + response.body("Server address is invalid or unreachable.\n".to_string()); + return response.send(); } Ok(mut addrs_iter) => { let address = addrs_iter.next().unwrap(); - match MinecraftStatus::fetch(address) { - Err(_) => { - response.status(StatusCode::InternalServerError); - query_response = format!( - "
-

Server Details

-
Failed to connect to {}.
", - sanitize_html(&address.to_string()), - ); - } - Ok(status) => { - let minecraft_status = MinecraftStatusResponse{ - version: &status.version.name, - players: status.players.online, - max_players: status.players.max, - enforces_secure_chat: status.enforces_secure_chat(), - favicon: status.favicon.as_ref(), - motd: status.parse_description(), - }; + let status = MinecraftStatus::fetch(address).unwrap(); - query_response = format!( - "
+ let minecraft_status = MinecraftStatusResponse{ + version: &status.version.name, + players: status.players.online, + max_players: status.players.max, + motd: status.parse_description(), + }; + + query_response = format!( + "

Server Details

- Favicon:
-
Version: {}
Players: {}/{}
- Enforces Secure Chat: {}
MOTD:

{}
", - sanitize_html(&minecraft_status.favicon.map_or("", |s| s).to_string()), sanitize_html(minecraft_status.version).to_string(), minecraft_status.players, minecraft_status.max_players, - if minecraft_status.enforces_secure_chat { "true" } else { "false" }, sanitize_html(&minecraft_status.motd).to_string(), - ); - } - } + ); } } } + None => { + query_response = String::from(""); + } } let response_content = content .replace("{{response}}", &query_response) .replace("{{host}}", match request.headers().get("host") { - None => { "mcq.bliss.town" } Some(host) => { host } + None => { "mcq.bliss.town" } }); - response.set_header("Content-Type", "text/html".to_string()); response.body(response_content.to_string()); return response.send(); } @@ -201,56 +153,32 @@ env!("CARGO_PKG_VERSION")); } Some(query_address) => { let mut address = query_address.to_string(); - address = address.replace("%3A", ":"); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; + if !address.contains(":") { address.push_str(":25565"); } + let mut addrs_iter = address.to_socket_addrs().unwrap(); + let address = addrs_iter.next().unwrap(); - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } - match address.to_socket_addrs() { - Err(_) => { - response.status(StatusCode::BadRequest); - response.body("Invalid server address.\n".to_string()); + let status = MinecraftStatus::fetch(address).unwrap(); + + let minecraft_status = MinecraftStatusResponse{ + version: &status.version.name, + players: status.players.online, + max_players: status.players.max, + motd: status.parse_description(), + }; + + match serde_json::to_string(&minecraft_status) { + Ok(json) => { + response.status(StatusCode::OK); + response.set_header("Content-Type", "application/json".to_string()); + response.body(json + "\n"); } - Ok(mut addrs_iter) => { - let address = addrs_iter.next().unwrap(); - - match MinecraftStatus::fetch(address) { - Err(_) => { - response.status(StatusCode::InternalServerError); - response.body(format!("Failed to connect to {address}.\n")); - } - Ok(status) => { - let minecraft_status = MinecraftStatusResponse{ - version: &status.version.name, - players: status.players.online, - max_players: status.players.max, - enforces_secure_chat: status.enforces_secure_chat(), - favicon: status.favicon.as_ref(), - motd: status.parse_description(), - }; - - match serde_json::to_string(&minecraft_status) { - Err(e) => { - eprintln!("Failed to format response from {} to JSON: {}", address, e); - response.status(StatusCode::InternalServerError); - response.body("Internal Server Error\n".to_string()); - } - Ok(json) => { - response.status(StatusCode::OK); - response.set_header("Content-Type", "application/json".to_string()); - response.body(json + "\n"); - } - } - } - } - + Err(e) => { + eprintln!("Request to {address} failed: {e}"); + response.status(StatusCode::InternalServerError); + response.body("Unable to reach the requested server.\n".to_string()); } } + return response.send() } } diff --git a/src/public/style/index.css b/src/public/style/index.css index ef8577a..59c28af 100644 --- a/src/public/style/index.css +++ b/src/public/style/index.css @@ -1,47 +1,10 @@ :root { - --bg-0: #101010; - --bg-1: #181818; - --bg-2: #282828; - --fg-0: #c0c0c0; - --fg-1: #d0d0d0; - --fg-2: #e0e0e0; - --accent: #c0ea76; - --on-accent: #43600f; - - --shadow-md: 0 2px 4px rgba(0,0,0,.5); - --shadow-sm: 0 1px 2px rgba(0,0,0,.3); -} - -@media (prefers-color-scheme: light) { - :root { - --bg-0: #e8e8e8; - --bg-1: #f0f0f0; - --bg-2: #fcfcfc; - --fg-0: #606060; - --fg-1: #303030; - --fg-2: #101010; - --accent: #6a9321; - --on-accent: #f6ffe6; - - --shadow-md: 0 2px 4px rgba(0,0,0,.25);; - --shadow-sm: 0 1px 2px rgba(0,0,0,.15);; - } + --accent: #7ca82f; } body { - width: 720px; - max-width: calc(100vw - 1em); - margin: auto; - - font-size: 20px; - font-family: 'Inter', 'Arial', sans-serif; - - background-color: var(--bg-0); - color: var(--fg-0); -} - -* { - transition: background-color .2s ease-out, color .2s ease-out; + font-size: 16px; + font-family: 'Inter', sans-serif; } a { @@ -54,45 +17,34 @@ a:hover { form { width: fit-content; - padding: 1em; - border-radius: 8px; - background-color: var(--bg-1); - color: var(--fg-1); - box-shadow: var(--shadow-md); + padding: .5em; + border: 1px solid black; + border-radius: 4px; } form input[type="text"] { width: fit-content; min-width: 16em; - padding: .2em .4em; - font-size: inherit; - border: none; - border-radius: 4px; - background-color: var(--bg-2); - color: var(--fg-2); - box-shadow: var(--shadow-sm); } form button { margin-top: .5em; - padding: .2em .4em; + padding: .2em .3em; font-family: inherit; font-size: inherit; - border: none; - border-radius: 8px; - color: var(--on-accent); + border: 1px solid black; + border-radius: 4px; + color: white; background-color: var(--accent); - box-shadow: var(--shadow-sm); } pre code { padding: .5em; - border-radius: 8px; - font-size: 16px; - - background-color: var(--fg-1); - color: var(--bg-1); - box-shadow: var(--shadow-sm); + border: 1px solid black; + border-radius: 4px; + font-size: .8em; + color: #e0e0e0; + background-color: #303030; } pre#motd { @@ -103,7 +55,6 @@ pre#motd code { display: block; width: fit-content; min-width: 440px; - box-shadow: var(--shadow-sm); } footer { diff --git a/src/status.rs b/src/status.rs index 7d28f96..c0363ad 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,7 +1,7 @@ use std::io::{Error, ErrorKind, Read, Write, Result}; use std::net::{SocketAddr, TcpStream}; -use crate::leb128::{read_leb128, write_leb128}; +use crate::leb128::LEB128; #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftVersion { @@ -19,7 +19,7 @@ pub struct MinecraftPlayer { pub struct MinecraftPlayers { pub online: u32, pub max: u32, - pub sample: Option>, + pub sample: Vec, } #[derive(serde::Serialize, serde::Deserialize)] @@ -30,14 +30,7 @@ enum MinecraftDescriptionExtra { } #[derive(serde::Serialize, serde::Deserialize)] -#[serde(untagged)] -pub enum MinecraftDescription { - Rich(MinecraftRichDescription), - Plain(String) -} - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct MinecraftRichDescription { +pub struct MinecraftDescription { text: String, extra: Option>, bold: Option, @@ -58,20 +51,18 @@ impl MinecraftStatus { pub fn fetch(address: SocketAddr) -> Result { // println!("Connecting to {address}..."); - let stream = TcpStream::connect(address.to_string()); - if stream.is_err() { return Err(stream.unwrap_err()); } + let mut stream = TcpStream::connect(address.to_string()).unwrap(); // println!("Connected!"); - let mut stream = stream.unwrap(); let mut send_buffer: Vec = Vec::new(); // println!("Sending payload..."); send_buffer.push(0x00); - write_leb128(&mut send_buffer, 769); // 1.21.4 - write_leb128(&mut send_buffer, address.ip().to_string().len().try_into().unwrap()); + LEB128::write_leb128(&mut send_buffer, 769); // 1.21.4 + LEB128::write_leb128(&mut send_buffer, address.ip().to_string().len().try_into().unwrap()); send_buffer.extend_from_slice(address.ip().to_string().as_bytes()); send_buffer.extend_from_slice(&address.port().to_be_bytes()); - write_leb128(&mut send_buffer, 1); + LEB128::write_leb128(&mut send_buffer, 1); send_packet(&mut stream, &send_buffer).unwrap(); send_packet(&mut stream, &[0x00]).unwrap(); @@ -89,7 +80,7 @@ impl MinecraftStatus { if len > 0 { if msg_len == 0 { let mut val: u32; - (val, offset) = read_leb128(&recv_buffer); + (val, offset) = LEB128::read_leb128(&recv_buffer); msg_len = val as usize; if recv_buffer[offset] != 0x00 { @@ -98,7 +89,7 @@ impl MinecraftStatus { offset += 1; // skip message type bit let offset2: usize; - (val, offset2) = read_leb128(&recv_buffer[offset..]); + (val, offset2) = LEB128::read_leb128(&recv_buffer[offset..]); object_len = val as usize; offset += offset2; } @@ -110,6 +101,7 @@ impl MinecraftStatus { } let msg = std::str::from_utf8(&data[offset..]).unwrap().trim(); let sanitised: String = msg.chars().filter(|&c| c >= '\u{20}' || c == '\n' || c == '\r' || c == '\t').collect(); + // println!("{sanitised}"); let status: MinecraftStatus = serde_json::from_slice(sanitised.as_bytes()).unwrap(); Ok(status) @@ -125,33 +117,26 @@ impl MinecraftStatus { } fn _description(description: &MinecraftDescription) -> String { - match description { - MinecraftDescription::Rich(description) => { - if description.extra.is_some() { - let mut extras = String::new(); - for extra in description.extra.as_ref().unwrap() { - match extra { - MinecraftDescriptionExtra::Extra(description) => { - extras += &_description(&description); - } - MinecraftDescriptionExtra::String(string) => { - extras += &string; - } - } + if description.extra.is_some() { + let mut extras = String::new(); + for extra in description.extra.as_ref().unwrap() { + match extra { + MinecraftDescriptionExtra::Extra(description) => { + extras += &_description(&description); + } + MinecraftDescriptionExtra::String(string) => { + extras += &string; } - return description.text.clone() + &extras; } - description.text.clone() - } - MinecraftDescription::Plain(description) => { - description.clone() } + return description.text.clone() + &extras; } + description.text.clone() } fn send_packet(stream: &mut TcpStream, data: &[u8]) -> Result<()> { let mut packet: Vec = Vec::new(); - write_leb128(&mut packet, data.len() as u64); + LEB128::write_leb128(&mut packet, data.len() as u64); packet.extend_from_slice(&data); stream.write(&packet).unwrap(); diff --git a/src/thread.rs b/src/thread.rs deleted file mode 100644 index a6cae8f..0000000 --- a/src/thread.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::{sync::{mpsc, Arc, Mutex}, thread}; - -pub struct ThreadPool { - workers: Vec, - sender: Option>, -} - -type Job = Box; - -impl ThreadPool { - // Create a new ThreadPool with `size` available threads. - // - // # Panics - // - // `new` will panic if `size` is zero. - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let receiver = Arc::new(Mutex::new(receiver)); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(ThreadWorker::new(id, Arc::clone(&receiver))); - } - - ThreadPool { - workers, - sender: Some(sender) - } - } - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - let job = Box::new(f); - - self.sender.as_ref().unwrap().send(job).unwrap(); - } -} - -impl Drop for ThreadPool { - fn drop(&mut self) { - drop(self.sender.take()); - - for worker in &mut self.workers.drain(..) { - worker.thread.join().expect( - format!("Error in worker {}", worker.id).as_str()); - } - } -} - -struct ThreadWorker { - id: usize, - thread: thread::JoinHandle<()>, -} - -impl ThreadWorker { - fn new(id: usize, receiver: Arc>>) -> ThreadWorker { - let thread = thread::spawn(move || loop { - let msg = receiver.lock().unwrap().recv(); - - match msg { - Ok(job) => { - // println!("Job received by worker {id}"); - job(); - } - Err(_) => { - // println!("Worker {id} disconnected. Shutting down..."); - break; - } - } - }); - - ThreadWorker { id, thread } - } -} - diff --git a/src/views/index.html b/src/views/index.html index fcfc154..8168602 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -3,16 +3,8 @@ - Crafty McStatusFace + Minecraft Server Query - - - - - - - -
@@ -21,7 +13,7 @@

- This website retrieves query information from Minecraft servers! + You can use this website to retrieve query information from a Minecraft server!

For more information, see https://minecraft.wiki/w/Query. @@ -38,13 +30,13 @@ Alternatively, you can cURL this website to get a raw JSON response:

-
curl 'https://{{host}}?s=<server address>' | jq .
+
curl 'https://{{host}}?s=<server address>'
{{response}}