feat: run http server to receive commands
This commit is contained in:
		
							
								
								
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1180,6 +1180,9 @@ dependencies = [ | ||||
|  "azalea", | ||||
|  "clap", | ||||
|  "futures", | ||||
|  "http-body-util", | ||||
|  "hyper", | ||||
|  "hyper-util", | ||||
|  "log", | ||||
|  "mlua", | ||||
|  "parking_lot", | ||||
| @@ -1493,6 +1496,12 @@ version = "1.10.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" | ||||
|  | ||||
| [[package]] | ||||
| name = "httpdate" | ||||
| version = "1.0.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | ||||
|  | ||||
| [[package]] | ||||
| name = "hyper" | ||||
| version = "1.6.0" | ||||
| @@ -1505,6 +1514,7 @@ dependencies = [ | ||||
|  "http", | ||||
|  "http-body", | ||||
|  "httparse", | ||||
|  "httpdate", | ||||
|  "itoa", | ||||
|  "pin-project-lite", | ||||
|  "smallvec", | ||||
|   | ||||
| @@ -20,6 +20,9 @@ anyhow = "1" | ||||
| azalea = { git = "https://github.com/azalea-rs/azalea.git" } | ||||
| clap = { version = "4", features = ["derive"] } | ||||
| futures = "0" | ||||
| http-body-util = "0" | ||||
| hyper = { version = "1", features = ["server"] } | ||||
| hyper-util = "0" | ||||
| log = { version = "0" } | ||||
| mlua = { version = "0", features = ["async", "luau", "send"] } | ||||
| parking_lot = { version = "0" } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| use clap::Parser; | ||||
| use std::path::PathBuf; | ||||
| use std::{net::SocketAddr, path::PathBuf}; | ||||
|  | ||||
| /// A Minecraft utility bot | ||||
| #[derive(Parser)] | ||||
| @@ -7,4 +7,8 @@ pub struct Arguments { | ||||
|     /// Path to main Lua file | ||||
|     #[arg(short, long)] | ||||
|     pub script: Option<PathBuf>, | ||||
|  | ||||
|     /// Socket address to bind HTTP server to | ||||
|     #[arg(short, long)] | ||||
|     pub address: Option<SocketAddr>, | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| use crate::{State, commands::CommandSource, scripting}; | ||||
| use crate::{State, commands::CommandSource, http::handle, scripting}; | ||||
| use azalea::prelude::*; | ||||
| use log::info; | ||||
| use hyper::{server::conn::http1, service::service_fn}; | ||||
| use hyper_util::rt::TokioIo; | ||||
| use log::{error, info}; | ||||
| use mlua::Function; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow::Result<()> { | ||||
|     let globals = state.lua.lock().globals(); | ||||
| @@ -40,7 +43,25 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow: | ||||
|                     inner: Some(client), | ||||
|                 }, | ||||
|             )?; | ||||
|             globals.get::<Function>("Init")?.call::<()>(())? | ||||
|             globals.get::<Function>("Init")?.call::<()>(())?; | ||||
|  | ||||
|             if let Some(address) = state.address { | ||||
|                 let listener = TcpListener::bind(address).await?; | ||||
|                 loop { | ||||
|                     let (stream, _) = listener.accept().await?; | ||||
|                     let io = TokioIo::new(stream); | ||||
|  | ||||
|                     let state = state.clone(); | ||||
|                     let service = service_fn(move |request| { | ||||
|                         let state = state.clone(); | ||||
|                         async move { handle(request, state).await } | ||||
|                     }); | ||||
|  | ||||
|                     if let Err(error) = http1::Builder::new().serve_connection(io, service).await { | ||||
|                         error!("failed to serve connection: {error:?}"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         _ => (), | ||||
|     }; | ||||
|   | ||||
							
								
								
									
										90
									
								
								src/http.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/http.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| use crate::State; | ||||
| use http_body_util::{BodyExt, Empty, Full, combinators::BoxBody}; | ||||
| use hyper::{Method, Request, Response, StatusCode, body::Bytes}; | ||||
|  | ||||
| pub async fn handle( | ||||
|     request: Request<hyper::body::Incoming>, | ||||
|     state: State, | ||||
| ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { | ||||
|     let path = request.uri().path().to_owned(); | ||||
|  | ||||
|     match (request.method(), path.as_str()) { | ||||
|         (&Method::POST, "/reload") => { | ||||
|             let lua = state.lua.lock(); | ||||
|             let config_path = match lua.globals().get::<String>("config_path") { | ||||
|                 Ok(path) => path, | ||||
|                 Err(error) => { | ||||
|                     return Ok(status_code_response( | ||||
|                         StatusCode::INTERNAL_SERVER_ERROR, | ||||
|                         Some(full(format!( | ||||
|                             "failed to get config_path from lua globals: {error:?}" | ||||
|                         ))), | ||||
|                     )); | ||||
|                 } | ||||
|             }; | ||||
|             if let Err(error) = match &std::fs::read_to_string(&config_path) { | ||||
|                 Ok(string) => lua.load(string).exec(), | ||||
|                 Err(error) => { | ||||
|                     return Ok(status_code_response( | ||||
|                         StatusCode::INTERNAL_SERVER_ERROR, | ||||
|                         Some(full(format!("failed to read {config_path:?}: {error:?}"))), | ||||
|                     )); | ||||
|                 } | ||||
|             } { | ||||
|                 return Ok(status_code_response( | ||||
|                     StatusCode::INTERNAL_SERVER_ERROR, | ||||
|                     Some(full(format!( | ||||
|                         "failed to execute configuration as lua code: {error:?}" | ||||
|                     ))), | ||||
|                 )); | ||||
|             } | ||||
|  | ||||
|             Ok(Response::new(empty())) | ||||
|         } | ||||
|  | ||||
|         (&Method::POST, "/eval" | "/exec") => { | ||||
|             let bytes = request.into_body().collect().await?.to_bytes(); | ||||
|             let code = match std::str::from_utf8(&bytes) { | ||||
|                 Ok(code) => code, | ||||
|                 Err(error) => { | ||||
|                     return Ok(status_code_response( | ||||
|                         StatusCode::BAD_REQUEST, | ||||
|                         Some(full(format!("invalid utf-8 data received: {error:?}"))), | ||||
|                     )); | ||||
|                 } | ||||
|             }; | ||||
|             let chunk = state.lua.lock().load(code); | ||||
|  | ||||
|             Ok(Response::new(full(match path.as_str() { | ||||
|                 "/eval" => format!("{:?}", chunk.eval::<String>()), | ||||
|                 "/exec" => format!("{:?}", chunk.exec()), | ||||
|                 _ => unreachable!(), | ||||
|             }))) | ||||
|         } | ||||
|  | ||||
|         (&Method::GET, "/ping") => Ok(Response::new(full("pong!"))), | ||||
|  | ||||
|         _ => Ok(status_code_response(StatusCode::NOT_FOUND, None)), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn status_code_response( | ||||
|     status_code: StatusCode, | ||||
|     bytes: Option<BoxBody<Bytes, hyper::Error>>, | ||||
| ) -> Response<BoxBody<Bytes, hyper::Error>> { | ||||
|     let mut response = Response::new(bytes.unwrap_or(empty())); | ||||
|     *response.status_mut() = status_code; | ||||
|     response | ||||
| } | ||||
|  | ||||
| fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> { | ||||
|     Full::new(chunk.into()) | ||||
|         .map_err(|never| match never {}) | ||||
|         .boxed() | ||||
| } | ||||
|  | ||||
| fn empty() -> BoxBody<Bytes, hyper::Error> { | ||||
|     Empty::<Bytes>::new() | ||||
|         .map_err(|never| match never {}) | ||||
|         .boxed() | ||||
| } | ||||
| @@ -3,6 +3,7 @@ | ||||
| mod arguments; | ||||
| mod commands; | ||||
| mod events; | ||||
| mod http; | ||||
| mod scripting; | ||||
|  | ||||
| use azalea::{brigadier::prelude::CommandDispatcher, prelude::*}; | ||||
| @@ -11,12 +12,13 @@ use commands::{CommandSource, register}; | ||||
| use events::handle_event; | ||||
| use mlua::Lua; | ||||
| use parking_lot::Mutex; | ||||
| use std::{path::PathBuf, process::ExitCode, sync::Arc}; | ||||
| use std::{net::SocketAddr, path::PathBuf, process::ExitCode, sync::Arc}; | ||||
|  | ||||
| #[derive(Default, Clone, Component)] | ||||
| pub struct State { | ||||
|     lua: Arc<Mutex<Lua>>, | ||||
|     commands: Arc<CommandDispatcher<Mutex<CommandSource>>>, | ||||
|     lua: Arc<Mutex<Lua>>, | ||||
|     address: Option<SocketAddr>, | ||||
| } | ||||
|  | ||||
| #[tokio::main] | ||||
| @@ -73,8 +75,9 @@ async fn main() -> ExitCode { | ||||
|     let Err(error) = ClientBuilder::new() | ||||
|         .set_handler(handle_event) | ||||
|         .set_state(State { | ||||
|             lua: Arc::new(Mutex::new(lua)), | ||||
|             commands: Arc::new(commands), | ||||
|             lua: Arc::new(Mutex::new(lua)), | ||||
|             address: args.address, | ||||
|         }) | ||||
|         .start(account, server.as_ref()) | ||||
|         .await; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user