From aefd57d2badf71d2997c5f3827a80d5eea1c5eea Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 3 Feb 2023 19:27:56 -0500 Subject: [PATCH] Implement HTTP server, update Luau version --- CHANGELOG.md | 28 ++++++ Cargo.lock | 146 +++++++++++-------------------- Cargo.toml | 2 +- lune.yml | 4 + luneDocs.json | 22 +++++ luneTypes.d.luau | 23 +++++ src/lib/globals/net.rs | 180 ++++++++++++++++++++++++++++++++++++++- src/tests/net/serve.luau | 21 +++++ 8 files changed, 328 insertions(+), 98 deletions(-) create mode 100644 src/tests/net/serve.luau diff --git a/CHANGELOG.md b/CHANGELOG.md index ed561cb..94564ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for string interpolation syntax (update to Luau 0.561) +- Added network server functionality using `net.serve` + + Example usage: + + ```lua + net.serve(8080, function(request) + print(`Got a {request.method} request at {request.path}!`) + + local data = net.jsonDecode(request.body) + + -- For simple text responses with a 200 status + return "OK" + + -- For anything else + return { + status = 203, + headers = { ["Content-Type"] = "application/json" }, + body = net.jsonEncode({ + message = "echo", + data = data, + }) + } + end) + ``` + ### Changed - Improved type definitions file for Selene, now including constants like `process.env` + tags such as `readonly` and `mustuse` wherever applicable diff --git a/Cargo.lock b/Cargo.lock index e544532..c750eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "0.7.20" @@ -23,19 +17,6 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" -[[package]] -name = "async-compression" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" -dependencies = [ - "flate2", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -89,9 +70,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -142,15 +123,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -203,16 +175,6 @@ dependencies = [ "libc", ] -[[package]] -name = "flate2" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -258,24 +220,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -290,15 +252,15 @@ checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-macro", @@ -335,9 +297,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -348,6 +310,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01" + [[package]] name = "http" version = "0.2.8" @@ -441,12 +409,12 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -457,14 +425,14 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.0", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -475,9 +443,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -538,9 +506,9 @@ dependencies = [ [[package]] name = "luau0-src" -version = "0.5.1+luau558" +version = "0.5.2+luau561" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0869ba31f9b152c28bb604ecee6deaa2103a97365bb219781ac137bd487626" +checksum = "746135e327565d137d2cddabf9c66554eb00610c455a047f03dba87847a745cc" dependencies = [ "cc", ] @@ -552,6 +520,7 @@ dependencies = [ "anyhow", "clap", "full_moon", + "hyper", "mlua", "os_str_bytes", "regex", @@ -573,15 +542,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "mio" version = "0.8.5" @@ -629,7 +589,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -794,7 +754,6 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "async-compression", "base64", "bytes", "encoding_rs", @@ -819,7 +778,6 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-rustls", - "tokio-util", "tower-service", "url", "wasm-bindgen", @@ -861,16 +819,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.7" +version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1051,9 +1009,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" @@ -1205,9 +1163,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1215,9 +1173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -1230,9 +1188,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -1242,9 +1200,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1252,9 +1210,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -1265,15 +1223,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index daf40a1..be9d06e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,11 @@ serde_json = "1.0.91" clap = { version = "4.1.1", features = ["derive"] } full_moon = { version = "0.17.0", features = ["roblox"] } +hyper = { version = "0.14.24", features = ["full"] } mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] } serde = { version = "1.0.152", features = ["derive"] } tokio = { version = "1.24.2", features = ["full"] } reqwest = { version = "0.11.14", default-features = false, features = [ "rustls-tls", - "gzip", ] } diff --git a/lune.yml b/lune.yml index ad6b72f..06ca480 100644 --- a/lune.yml +++ b/lune.yml @@ -72,6 +72,10 @@ globals: net.request: args: - type: any + net.serve: + args: + - type: number + - type: function # Processs process.args: property: read-only diff --git a/luneDocs.json b/luneDocs.json index c2e4832..9f06f86 100644 --- a/luneDocs.json +++ b/luneDocs.json @@ -361,6 +361,28 @@ "@roblox/global/net.request/return/0": { "documentation": "A dictionary representing the response for the request" }, + "@roblox/global/net.serve": { + "code_sample": "", + "documentation": "Creates an HTTP server that listens on the given `port`.\n\nThe call to this function will block indefinitely and if\nput inside `task.spawn` will not be cancellable using `task.cancel`, to\nstop the script from running it must be terminated manually or using `process.exit`.", + "learn_more_link": "", + "params": [ + { + "documentation": "@roblox/global/net.serve/param/0", + "name": "port" + }, + { + "documentation": "@roblox/global/net.serve/param/1", + "name": "handler" + } + ], + "returns": [] + }, + "@roblox/global/net.serve/param/0": { + "documentation": "The port to use for the server" + }, + "@roblox/global/net.serve/param/1": { + "documentation": "The handler function to use for the server" + }, "@roblox/global/process": { "code_sample": "", "documentation": "Current process & child processes", diff --git a/luneTypes.d.luau b/luneTypes.d.luau index 9e3681b..18f59e3 100644 --- a/luneTypes.d.luau +++ b/luneTypes.d.luau @@ -241,6 +241,29 @@ declare net: { --[=[ @within net + Creates an HTTP server that listens on the given `port`. + + The call to this function will block indefinitely and if + put inside `task.spawn` will not be cancellable using `task.cancel`, to + stop the script from running it must be terminated manually or using `process.exit`. + + @param port The port to use for the server + @param handler The handler function to use for the server + ]=] + serve: (port: number, handler: (request: { + path: string, + query: string, + method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH", + headers: { [string]: string }?, + body: string?, + }) -> (string | { + status: number?, + headers: { [string]: string }?, + body: string?, + })) -> (), + --[=[ + @within net + Encodes the given value as JSON. @param value The value to encode as JSON diff --git a/src/lib/globals/net.rs b/src/lib/globals/net.rs index a81b0bf..7076dbd 100644 --- a/src/lib/globals/net.rs +++ b/src/lib/globals/net.rs @@ -1,9 +1,22 @@ use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::{Arc, Weak}; +use std::task::{Context, Poll}; +use hyper::body::to_bytes; +use hyper::http::HeaderValue; +use hyper::server::conn::AddrStream; use mlua::prelude::*; -use reqwest::Method; + +use hyper::service::Service; +use hyper::{Body, HeaderMap, Request, Response, Server}; +use reqwest::{ClientBuilder, Method}; +use tokio::sync::mpsc::Sender; +use tokio::task; use crate::utils::{net::get_request_user_agent_header, table::TableBuilder}; +use crate::LuneMessage; pub fn create(lua: &Lua) -> LuaResult<()> { lua.globals().raw_set( @@ -12,6 +25,7 @@ pub fn create(lua: &Lua) -> LuaResult<()> { .with_function("jsonEncode", net_json_encode)? .with_function("jsonDecode", net_json_decode)? .with_async_function("request", net_request)? + .with_async_function("serve", net_serve)? .build_readonly()?, ) } @@ -90,7 +104,13 @@ async fn net_request<'lua>(lua: &'lua Lua, config: LuaValue<'lua>) -> LuaResult< ))), }?; // TODO: Figure out how to reuse this client - let client = reqwest::ClientBuilder::new() + let mut default_headers = HeaderMap::new(); + default_headers.insert( + "User-Agent", + HeaderValue::from_str(&get_request_user_agent_header()).map_err(LuaError::external)?, + ); + let client = ClientBuilder::new() + .default_headers(default_headers) .build() .map_err(LuaError::external)?; // Create and send the request @@ -99,7 +119,6 @@ async fn net_request<'lua>(lua: &'lua Lua, config: LuaValue<'lua>) -> LuaResult< request = request.header(header.to_str()?, value.to_str()?); } let res = request - .header("User-Agent", &get_request_user_agent_header()) // Always force user agent .body(body.unwrap_or_default()) .send() .await @@ -123,3 +142,158 @@ async fn net_request<'lua>(lua: &'lua Lua, config: LuaValue<'lua>) -> LuaResult< .with_value("body", lua.create_string(&res_bytes)?)? .build_readonly() } + +async fn net_serve<'lua>( + lua: &'lua Lua, + (port, callback): (u16, LuaFunction<'lua>), +) -> LuaResult<()> { + let server_lua = lua.app_data_ref::>().unwrap().upgrade().unwrap(); + let server_sender = lua + .app_data_ref::>>() + .unwrap() + .upgrade() + .unwrap(); + let server_callback = server_lua.create_registry_value(callback)?; + let server = Server::bind(&([127, 0, 0, 1], port).into()) + .executor(LocalExec) + .serve(MakeNetService(server_lua, server_callback.into())); + if let Err(err) = server.await.map_err(LuaError::external) { + server_sender + .send(LuneMessage::LuaError(err)) + .await + .map_err(LuaError::external)?; + } + Ok(()) +} + +// Hyper service implementation for net, lots of boilerplate here +// but make_svc and make_svc_function do not work for what we need + +pub struct NetService(Arc, Arc); + +impl Service> for NetService { + type Response = Response; + type Error = LuaError; + type Future = Pin>>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + let lua = self.0.clone(); + let key = self.1.clone(); + let (parts, body) = req.into_parts(); + Box::pin(async move { + // Convert request body into bytes, extract handler + // function & lune message sender to use later + let bytes = to_bytes(body).await.map_err(LuaError::external)?; + let handler: LuaFunction = lua.registry_value(&key)?; + let sender = lua + .app_data_ref::>>() + .unwrap() + .upgrade() + .unwrap(); + // Create a readonly table with request info to pass to the handler + let request = TableBuilder::new(&lua)? + .with_value("path", parts.uri.path())? + .with_value("query", parts.uri.query().unwrap_or_default())? + .with_value("method", parts.method.as_str())? + .with_value( + "headers", + parts + .headers + .iter() + .map(|(name, value)| { + (name.to_string(), value.to_str().unwrap().to_string()) + }) + .collect::>(), + )? + .with_value("body", lua.create_string(&bytes)?)? + .build_readonly()?; + match handler.call_async(request).await { + // Plain strings from the handler are plaintext responses + Ok(LuaValue::String(s)) => Ok(Response::builder() + .status(200) + .header("Content-Type", "text/plain") + .body(Body::from(s.as_bytes().to_vec())) + .unwrap()), + // Tables are more detailed responses with potential status, headers, body + Ok(LuaValue::Table(t)) => { + let status = t.get::<_, Option>("status")?.unwrap_or(200); + let mut resp = Response::builder().status(status); + + if let Some(headers) = t.get::<_, Option>("headers")? { + for pair in headers.pairs::() { + let (h, v) = pair?; + resp = resp.header(&h, v.as_bytes()); + } + } + + let body = t + .get::<_, Option>("body")? + .map(|b| Body::from(b.as_bytes().to_vec())) + .unwrap_or_else(Body::empty); + + Ok(resp.body(body).unwrap()) + } + // If the handler returns an error, generate a 5xx response + Err(err) => { + sender + .send(LuneMessage::LuaError(err.to_lua_err())) + .await + .map_err(LuaError::external)?; + Ok(Response::builder() + .status(500) + .body(Body::from("Internal Server Error")) + .unwrap()) + } + // If the handler returns a value that is of an invalid type, + // this should also be an error, so generate a 5xx response + Ok(value) => { + sender + .send(LuneMessage::LuaError(LuaError::RuntimeError(format!( + "Expected net serve handler to return a value of type 'string' or 'table', got '{}'", + value.type_name() + )))) + .await + .map_err(LuaError::external)?; + Ok(Response::builder() + .status(500) + .body(Body::from("Internal Server Error")) + .unwrap()) + } + } + }) + } +} + +struct MakeNetService(Arc, Arc); + +impl Service<&AddrStream> for MakeNetService { + type Response = NetService; + type Error = hyper::Error; + type Future = Pin>>>; + + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _: &AddrStream) -> Self::Future { + let lua = self.0.clone(); + let key = self.1.clone(); + Box::pin(async move { Ok(NetService(lua, key)) }) + } +} + +#[derive(Clone, Copy, Debug)] +struct LocalExec; + +impl hyper::rt::Executor for LocalExec +where + F: std::future::Future + 'static, // not requiring `Send` +{ + fn execute(&self, fut: F) { + task::spawn_local(fut); + } +} diff --git a/src/tests/net/serve.luau b/src/tests/net/serve.luau new file mode 100644 index 0000000..7fa4e21 --- /dev/null +++ b/src/tests/net/serve.luau @@ -0,0 +1,21 @@ +local RESPONSE = "Hello, lune!" + +task.spawn(function() + net.serve(8080, function(request) + console.info("Request:", request) + console.info("Responding with", RESPONSE) + return RESPONSE + end) +end) + +local response = net.request("http://127.0.0.1:8080").body +assert(response == RESPONSE, "Invalid response from server") + +task.delay(1, function() + process.exit(0) +end) + +task.delay(2, function() + console.error("Process did not exit") + process.exit(1) +end)