mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Implement bulk of async APIs
This commit is contained in:
parent
b1b69c7d94
commit
d2ff0783a5
13 changed files with 246 additions and 182 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -757,9 +757,6 @@ dependencies = [
|
||||||
"bstr",
|
"bstr",
|
||||||
"cc",
|
"cc",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
"luau0-src",
|
"luau0-src",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
|
@ -32,7 +32,7 @@ os_str_bytes = "6.4.1"
|
||||||
hyper = { version = "0.14.24", features = ["full"] }
|
hyper = { version = "0.14.24", features = ["full"] }
|
||||||
hyper-tungstenite = { version = "0.9.0" }
|
hyper-tungstenite = { version = "0.9.0" }
|
||||||
tokio-tungstenite = { version = "0.18.0" }
|
tokio-tungstenite = { version = "0.18.0" }
|
||||||
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
|
mlua = { version = "0.8.7", features = ["luau", "serialize"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.69"
|
anyhow = "1.0.69"
|
||||||
|
|
|
@ -16,7 +16,11 @@ use reqwest::Method;
|
||||||
use tokio::{sync::mpsc, task};
|
use tokio::{sync::mpsc, task};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lua::net::{NetClient, NetClientBuilder, NetWebSocketClient, NetWebSocketServer, ServeConfig},
|
lua::{
|
||||||
|
// net::{NetWebSocketClient, NetWebSocketServer},
|
||||||
|
net::{NetClient, NetClientBuilder, ServeConfig},
|
||||||
|
task::TaskScheduler,
|
||||||
|
},
|
||||||
utils::{net::get_request_user_agent_header, table::TableBuilder},
|
utils::{net::get_request_user_agent_header, table::TableBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,13 +145,14 @@ async fn net_request<'a>(lua: &'static Lua, config: LuaValue<'a>) -> LuaResult<L
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_socket<'a>(lua: &'static Lua, url: String) -> LuaResult<LuaAnyUserData> {
|
async fn net_socket<'a>(lua: &'static Lua, url: String) -> LuaResult<LuaTable> {
|
||||||
let (stream, _) = tokio_tungstenite::connect_async(url)
|
let (ws, _) = tokio_tungstenite::connect_async(url)
|
||||||
.await
|
.await
|
||||||
.map_err(LuaError::external)?;
|
.map_err(LuaError::external)?;
|
||||||
let ws_lua = NetWebSocketClient::from(stream);
|
todo!()
|
||||||
let ws_proper = ws_lua.into_proper(lua).await?;
|
// let sock = NetWebSocketClient::from(ws);
|
||||||
Ok(ws_proper)
|
// let table = sock.into_lua_table(lua)?;
|
||||||
|
// Ok(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_serve<'a>(
|
async fn net_serve<'a>(
|
||||||
|
@ -223,13 +228,21 @@ impl Service<Request<Body>> for NetService {
|
||||||
let key = kopt.as_ref().as_ref().unwrap();
|
let key = kopt.as_ref().as_ref().unwrap();
|
||||||
let handler: LuaFunction = lua.registry_value(key).expect("Missing websocket handler");
|
let handler: LuaFunction = lua.registry_value(key).expect("Missing websocket handler");
|
||||||
let (response, ws) = ws_upgrade(&mut req, None).expect("Failed to upgrade websocket");
|
let (response, ws) = ws_upgrade(&mut req, None).expect("Failed to upgrade websocket");
|
||||||
|
// TODO: This should be spawned as part of the scheduler,
|
||||||
|
// the scheduler may exit early and cancel this even though what
|
||||||
|
// we want here is a long-running task that keeps the program alive
|
||||||
task::spawn_local(async move {
|
task::spawn_local(async move {
|
||||||
// Create our new full websocket object
|
// Create our new full websocket object, then
|
||||||
|
// schedule our handler to get called asap
|
||||||
let ws = ws.await.map_err(LuaError::external)?;
|
let ws = ws.await.map_err(LuaError::external)?;
|
||||||
let ws_lua = NetWebSocketServer::from(ws);
|
// let sock = NetWebSocketServer::from(ws);
|
||||||
let ws_proper = ws_lua.into_proper(lua).await?;
|
// let table = sock.into_lua_table(lua)?;
|
||||||
// Call our handler with it
|
// let sched = lua.app_data_mut::<&TaskScheduler>().unwrap();
|
||||||
handler.call_async::<_, ()>(ws_proper).await
|
// sched.schedule_current_resume(
|
||||||
|
// LuaValue::Function(handler),
|
||||||
|
// LuaMultiValue::from_vec(vec![LuaValue::Table(table)]),
|
||||||
|
// )
|
||||||
|
Ok::<_, LuaError>(())
|
||||||
});
|
});
|
||||||
Box::pin(async move { Ok(response) })
|
Box::pin(async move { Ok(response) })
|
||||||
} else {
|
} else {
|
||||||
|
@ -274,7 +287,7 @@ impl Service<Request<Body>> for NetService {
|
||||||
.with_value("headers", header_map)?
|
.with_value("headers", header_map)?
|
||||||
.with_value("body", lua.create_string(&bytes)?)?
|
.with_value("body", lua.create_string(&bytes)?)?
|
||||||
.build_readonly()?;
|
.build_readonly()?;
|
||||||
match handler.call_async(request).await {
|
match handler.call(request) {
|
||||||
// Plain strings from the handler are plaintext responses
|
// Plain strings from the handler are plaintext responses
|
||||||
Ok(LuaValue::String(s)) => Ok(Response::builder()
|
Ok(LuaValue::String(s)) => Ok(Response::builder()
|
||||||
.status(200)
|
.status(200)
|
||||||
|
|
|
@ -100,12 +100,6 @@ impl Lune {
|
||||||
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
|
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
|
||||||
let debug: LuaTable = lua.globals().raw_get("debug")?;
|
let debug: LuaTable = lua.globals().raw_get("debug")?;
|
||||||
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
||||||
// Add in wanted lune globals
|
|
||||||
for global in self.includes.clone() {
|
|
||||||
if !self.excludes.contains(&global) {
|
|
||||||
global.inject(lua)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create our task scheduler and schedule the main thread on it
|
// Create our task scheduler and schedule the main thread on it
|
||||||
let sched = TaskScheduler::new(lua)?.into_static();
|
let sched = TaskScheduler::new(lua)?.into_static();
|
||||||
lua.set_app_data(sched);
|
lua.set_app_data(sched);
|
||||||
|
@ -119,6 +113,13 @@ impl Lune {
|
||||||
),
|
),
|
||||||
LuaValue::Nil.to_lua_multi(lua)?,
|
LuaValue::Nil.to_lua_multi(lua)?,
|
||||||
)?;
|
)?;
|
||||||
|
// Create our wanted lune globals, some of these need
|
||||||
|
// the task scheduler be available during construction
|
||||||
|
for global in self.includes.clone() {
|
||||||
|
if !self.excludes.contains(&global) {
|
||||||
|
global.inject(lua)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Keep running the scheduler until there are either no tasks
|
// Keep running the scheduler until there are either no tasks
|
||||||
// left to run, or until a task requests to exit the process
|
// left to run, or until a task requests to exit the process
|
||||||
let exit_code = LocalSet::new()
|
let exit_code = LocalSet::new()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
mod client;
|
mod client;
|
||||||
mod config;
|
mod config;
|
||||||
mod ws_client;
|
// mod ws_client;
|
||||||
mod ws_server;
|
// mod ws_server;
|
||||||
|
|
||||||
pub use client::{NetClient, NetClientBuilder};
|
pub use client::{NetClient, NetClientBuilder};
|
||||||
pub use config::ServeConfig;
|
pub use config::ServeConfig;
|
||||||
pub use ws_client::NetWebSocketClient;
|
// pub use ws_client::NetWebSocketClient;
|
||||||
pub use ws_server::NetWebSocketServer;
|
// pub use ws_server::NetWebSocketServer;
|
||||||
|
|
|
@ -8,71 +8,59 @@ use futures_util::{SinkExt, StreamExt};
|
||||||
use tokio::{net::TcpStream, sync::Mutex};
|
use tokio::{net::TcpStream, sync::Mutex};
|
||||||
use tokio_tungstenite::MaybeTlsStream;
|
use tokio_tungstenite::MaybeTlsStream;
|
||||||
|
|
||||||
|
use crate::utils::table::TableBuilder;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NetWebSocketClient(Arc<Mutex<WebSocketStream<MaybeTlsStream<TcpStream>>>>);
|
pub struct NetWebSocketClient(Arc<Mutex<WebSocketStream<MaybeTlsStream<TcpStream>>>>);
|
||||||
|
|
||||||
impl NetWebSocketClient {
|
impl NetWebSocketClient {
|
||||||
pub async fn close(&self) -> LuaResult<()> {
|
async fn close(&self) -> LuaResult<()> {
|
||||||
let mut ws = self.0.lock().await;
|
self.0.lock().await.close(None).await;
|
||||||
ws.close(None).await.map_err(LuaError::external)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&self, msg: WsMessage) -> LuaResult<()> {
|
async fn send(&self, msg: String) -> LuaResult<()> {
|
||||||
let mut ws = self.0.lock().await;
|
self.0
|
||||||
ws.send(msg).await.map_err(LuaError::external)?;
|
.lock()
|
||||||
Ok(())
|
.await
|
||||||
|
.send(WsMessage::Text(msg))
|
||||||
|
.await
|
||||||
|
.map_err(LuaError::external)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&self) -> LuaResult<Option<WsMessage>> {
|
async fn next<'a>(&self, lua: &'static Lua) -> LuaResult<LuaValue<'a>> {
|
||||||
let mut ws = self.0.lock().await;
|
let item = self
|
||||||
let item = ws.next().await.transpose();
|
.0
|
||||||
item.map_err(LuaError::external)
|
.lock()
|
||||||
}
|
.await
|
||||||
|
.next()
|
||||||
pub async fn into_proper(self, lua: &'static Lua) -> LuaResult<LuaAnyUserData> {
|
.await
|
||||||
// HACK: This creates a new userdata that consumes and proxies this one,
|
.transpose()
|
||||||
// since there's no great way to implement this in pure async Rust
|
.map_err(LuaError::external)?;
|
||||||
// and as a plain table without tons of strange lifetime issues
|
Ok(match item {
|
||||||
let chunk = r#"
|
None => LuaValue::Nil,
|
||||||
local ws = ...
|
Some(msg) => match msg {
|
||||||
local proxy = newproxy(true)
|
|
||||||
local meta = getmetatable(proxy)
|
|
||||||
meta.__index = {
|
|
||||||
close = function() return ws:close() end,
|
|
||||||
send = function(...) return ws:send(...) end,
|
|
||||||
next = function() return ws:next() end,
|
|
||||||
}
|
|
||||||
meta.__iter = function()
|
|
||||||
return function()
|
|
||||||
return ws:next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return proxy
|
|
||||||
"#;
|
|
||||||
lua.load(chunk).call_async(self).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaUserData for NetWebSocketClient {
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
||||||
methods.add_async_method("close", |_, this, _: ()| async move { this.close().await });
|
|
||||||
methods.add_async_method("send", |_, this, msg: String| async move {
|
|
||||||
this.send(WsMessage::Text(msg)).await
|
|
||||||
});
|
|
||||||
methods.add_async_method("next", |lua, this, _: ()| async move {
|
|
||||||
match this.next().await? {
|
|
||||||
Some(msg) => Ok(match msg {
|
|
||||||
WsMessage::Binary(bin) => LuaValue::String(lua.create_string(&bin)?),
|
WsMessage::Binary(bin) => LuaValue::String(lua.create_string(&bin)?),
|
||||||
WsMessage::Text(txt) => LuaValue::String(lua.create_string(&txt)?),
|
WsMessage::Text(txt) => LuaValue::String(lua.create_string(&txt)?),
|
||||||
_ => LuaValue::Nil,
|
_ => LuaValue::Nil,
|
||||||
}),
|
},
|
||||||
None => Ok(LuaValue::Nil),
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
|
||||||
|
let inner_close = self.clone();
|
||||||
|
let inner_send = self.clone();
|
||||||
|
let inner_next = self.clone();
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_async_function("close", move |_, _: ()| inner_close.close())?
|
||||||
|
.with_async_function("send", move |_, msg: String| inner_send.send(msg))?
|
||||||
|
.with_async_function("next", move |lua, _: ()| inner_next.next(lua))?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for NetWebSocketClient {}
|
||||||
|
|
||||||
impl From<WebSocketStream<MaybeTlsStream<TcpStream>>> for NetWebSocketClient {
|
impl From<WebSocketStream<MaybeTlsStream<TcpStream>>> for NetWebSocketClient {
|
||||||
fn from(value: WebSocketStream<MaybeTlsStream<TcpStream>>) -> Self {
|
fn from(value: WebSocketStream<MaybeTlsStream<TcpStream>>) -> Self {
|
||||||
Self(Arc::new(Mutex::new(value)))
|
Self(Arc::new(Mutex::new(value)))
|
||||||
|
|
|
@ -8,68 +8,56 @@ use hyper_tungstenite::{tungstenite::Message as WsMessage, WebSocketStream};
|
||||||
use futures_util::{SinkExt, StreamExt};
|
use futures_util::{SinkExt, StreamExt};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::utils::table::TableBuilder;
|
||||||
|
|
||||||
|
type Inner = Arc<Mutex<WebSocketStream<Upgraded>>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NetWebSocketServer(Arc<Mutex<WebSocketStream<Upgraded>>>);
|
pub struct NetWebSocketServer(Inner);
|
||||||
|
|
||||||
impl NetWebSocketServer {
|
impl NetWebSocketServer {
|
||||||
pub async fn close(&self) -> LuaResult<()> {
|
async fn close(&self) -> LuaResult<()> {
|
||||||
let mut ws = self.0.lock().await;
|
self.0.lock().await.close(None).await;
|
||||||
ws.close(None).await.map_err(LuaError::external)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&self, msg: WsMessage) -> LuaResult<()> {
|
async fn send(&self, msg: String) -> LuaResult<()> {
|
||||||
let mut ws = self.0.lock().await;
|
self.0
|
||||||
ws.send(msg).await.map_err(LuaError::external)?;
|
.lock()
|
||||||
Ok(())
|
.await
|
||||||
|
.send(WsMessage::Text(msg))
|
||||||
|
.await
|
||||||
|
.map_err(LuaError::external)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&self) -> LuaResult<Option<WsMessage>> {
|
async fn next<'a>(&self, lua: &'static Lua) -> LuaResult<LuaValue<'a>> {
|
||||||
let mut ws = self.0.lock().await;
|
let item = self
|
||||||
let item = ws.next().await.transpose();
|
.0
|
||||||
item.map_err(LuaError::external)
|
.lock()
|
||||||
}
|
.await
|
||||||
|
.next()
|
||||||
pub async fn into_proper(self, lua: &'static Lua) -> LuaResult<LuaAnyUserData> {
|
.await
|
||||||
// HACK: This creates a new userdata that consumes and proxies this one,
|
.transpose()
|
||||||
// since there's no great way to implement this in pure async Rust
|
.map_err(LuaError::external)?;
|
||||||
// and as a plain table without tons of strange lifetime issues
|
Ok(match item {
|
||||||
let chunk = r#"
|
None => LuaValue::Nil,
|
||||||
local ws = ...
|
Some(msg) => match msg {
|
||||||
local proxy = newproxy(true)
|
|
||||||
local meta = getmetatable(proxy)
|
|
||||||
meta.__index = {
|
|
||||||
close = function() return ws:close() end,
|
|
||||||
send = function(...) return ws:send(...) end,
|
|
||||||
next = function() return ws:next() end,
|
|
||||||
}
|
|
||||||
meta.__iter = function()
|
|
||||||
return function()
|
|
||||||
return ws:next()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return proxy
|
|
||||||
"#;
|
|
||||||
lua.load(chunk).call_async(self).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaUserData for NetWebSocketServer {
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
||||||
methods.add_async_method("close", |_, this, _: ()| async move { this.close().await });
|
|
||||||
methods.add_async_method("send", |_, this, msg: String| async move {
|
|
||||||
this.send(WsMessage::Text(msg)).await
|
|
||||||
});
|
|
||||||
methods.add_async_method("next", |lua, this, _: ()| async move {
|
|
||||||
match this.next().await? {
|
|
||||||
Some(msg) => Ok(match msg {
|
|
||||||
WsMessage::Binary(bin) => LuaValue::String(lua.create_string(&bin)?),
|
WsMessage::Binary(bin) => LuaValue::String(lua.create_string(&bin)?),
|
||||||
WsMessage::Text(txt) => LuaValue::String(lua.create_string(&txt)?),
|
WsMessage::Text(txt) => LuaValue::String(lua.create_string(&txt)?),
|
||||||
_ => LuaValue::Nil,
|
_ => LuaValue::Nil,
|
||||||
}),
|
},
|
||||||
None => Ok(LuaValue::Nil),
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
|
||||||
|
let inner_close = self.clone();
|
||||||
|
let inner_send = self.clone();
|
||||||
|
let inner_next = self.clone();
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_async_function("close", move |_, _: ()| inner_close.close())?
|
||||||
|
.with_async_function("send", move |_, msg: String| inner_send.send(msg))?
|
||||||
|
.with_async_function("next", move |lua, _: ()| inner_next.next(lua))?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,19 @@ use tokio::{
|
||||||
time::{sleep, Instant},
|
time::{sleep, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::utils::table::TableBuilder;
|
||||||
|
|
||||||
type TaskSchedulerQueue = Arc<Mutex<VecDeque<TaskReference>>>;
|
type TaskSchedulerQueue = Arc<Mutex<VecDeque<TaskReference>>>;
|
||||||
|
|
||||||
type TaskFutureArgsOverride<'fut> = Option<Vec<LuaValue<'fut>>>;
|
type TaskFutureArgsOverride<'fut> = Option<Vec<LuaValue<'fut>>>;
|
||||||
type TaskFutureReturns<'fut> = LuaResult<TaskFutureArgsOverride<'fut>>;
|
type TaskFutureReturns<'fut> = LuaResult<TaskFutureArgsOverride<'fut>>;
|
||||||
type TaskFuture<'fut> = LocalBoxFuture<'fut, (TaskReference, TaskFutureReturns<'fut>)>;
|
type TaskFuture<'fut> = LocalBoxFuture<'fut, (TaskReference, TaskFutureReturns<'fut>)>;
|
||||||
|
|
||||||
|
const TASK_ASYNC_IMPL_LUA: &str = r#"
|
||||||
|
resume_async(thread(), ...)
|
||||||
|
return yield()
|
||||||
|
"#;
|
||||||
|
|
||||||
/// An enum representing different kinds of tasks
|
/// An enum representing different kinds of tasks
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum TaskKind {
|
pub enum TaskKind {
|
||||||
|
@ -441,7 +448,6 @@ impl<'fut> TaskScheduler<'fut> {
|
||||||
The given lua thread or function will be resumed
|
The given lua thread or function will be resumed
|
||||||
using the optional arguments returned by the future.
|
using the optional arguments returned by the future.
|
||||||
*/
|
*/
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn schedule_async(
|
pub fn schedule_async(
|
||||||
&self,
|
&self,
|
||||||
thread_or_function: LuaValue<'_>,
|
thread_or_function: LuaValue<'_>,
|
||||||
|
@ -450,6 +456,42 @@ impl<'fut> TaskScheduler<'fut> {
|
||||||
self.queue_async(thread_or_function, None, None, fut)
|
self.queue_async(thread_or_function, None, None, fut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a function callable from Lua that runs an async
|
||||||
|
closure and returns the results of it to the call site.
|
||||||
|
*/
|
||||||
|
pub fn make_scheduled_async_fn<A, R, F, FR>(&self, func: F) -> LuaResult<LuaFunction>
|
||||||
|
where
|
||||||
|
A: FromLuaMulti<'static>,
|
||||||
|
R: ToLuaMulti<'static>,
|
||||||
|
F: 'static + Fn(&'static Lua, A) -> FR,
|
||||||
|
FR: 'static + Future<Output = LuaResult<R>>,
|
||||||
|
{
|
||||||
|
let async_env_thread: LuaFunction = self.lua.named_registry_value("co.thread")?;
|
||||||
|
let async_env_yield: LuaFunction = self.lua.named_registry_value("co.yield")?;
|
||||||
|
self.lua
|
||||||
|
.load(TASK_ASYNC_IMPL_LUA)
|
||||||
|
.set_environment(
|
||||||
|
TableBuilder::new(self.lua)?
|
||||||
|
.with_value("thread", async_env_thread)?
|
||||||
|
.with_value("yield", async_env_yield)?
|
||||||
|
.with_function(
|
||||||
|
"resume_async",
|
||||||
|
move |lua: &Lua, (thread, args): (LuaThread, A)| {
|
||||||
|
let fut = func(lua, args);
|
||||||
|
let sched = lua.app_data_mut::<&TaskScheduler>().unwrap();
|
||||||
|
sched.schedule_async(LuaValue::Thread(thread), async {
|
||||||
|
let rets = fut.await?;
|
||||||
|
let mult = rets.to_lua_multi(lua)?;
|
||||||
|
Ok(Some(mult.into_vec()))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
.build_readonly()?,
|
||||||
|
)?
|
||||||
|
.into_function()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if a task still exists in the scheduler.
|
Checks if a task still exists in the scheduler.
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,8 @@ create_tests! {
|
||||||
net_request_redirect: "net/request/redirect",
|
net_request_redirect: "net/request/redirect",
|
||||||
net_json_decode: "net/json/decode",
|
net_json_decode: "net/json/decode",
|
||||||
net_json_encode: "net/json/encode",
|
net_json_encode: "net/json/encode",
|
||||||
net_serve: "net/serve",
|
net_serve_requests: "net/serve/requests",
|
||||||
|
net_serve_websockets: "net/serve/websockets",
|
||||||
process_args: "process/args",
|
process_args: "process/args",
|
||||||
process_cwd: "process/cwd",
|
process_cwd: "process/cwd",
|
||||||
process_env: "process/env",
|
process_env: "process/env",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::process::ExitStatus;
|
use std::process::ExitStatus;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use tokio::{io, process::Child, task::spawn};
|
use tokio::{io, process::Child, task};
|
||||||
|
|
||||||
use crate::utils::futures::AsyncTeeWriter;
|
use crate::utils::futures::AsyncTeeWriter;
|
||||||
|
|
||||||
|
@ -11,7 +11,15 @@ pub async fn pipe_and_inherit_child_process_stdio(
|
||||||
let mut child_stdout = child.stdout.take().unwrap();
|
let mut child_stdout = child.stdout.take().unwrap();
|
||||||
let mut child_stderr = child.stderr.take().unwrap();
|
let mut child_stderr = child.stderr.take().unwrap();
|
||||||
|
|
||||||
let stdout_thread = spawn(async move {
|
/*
|
||||||
|
NOTE: We do not need to register these
|
||||||
|
independent tasks spawning in the scheduler
|
||||||
|
|
||||||
|
This function is only used by `process.spawn` which in
|
||||||
|
turn registers a task with the scheduler that awaits this
|
||||||
|
*/
|
||||||
|
|
||||||
|
let stdout_thread = task::spawn(async move {
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stdout);
|
let mut tee = AsyncTeeWriter::new(&mut stdout);
|
||||||
|
|
||||||
|
@ -22,7 +30,7 @@ pub async fn pipe_and_inherit_child_process_stdio(
|
||||||
Ok::<_, LuaError>(tee.into_vec())
|
Ok::<_, LuaError>(tee.into_vec())
|
||||||
});
|
});
|
||||||
|
|
||||||
let stderr_thread = spawn(async move {
|
let stderr_thread = task::spawn(async move {
|
||||||
let mut stderr = io::stderr();
|
let mut stderr = io::stderr();
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stderr);
|
let mut tee = AsyncTeeWriter::new(&mut stderr);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::future::Future;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::lua::task::TaskScheduler;
|
||||||
|
|
||||||
pub struct TableBuilder {
|
pub struct TableBuilder {
|
||||||
lua: &'static Lua,
|
lua: &'static Lua,
|
||||||
tab: LuaTable<'static>,
|
tab: LuaTable<'static>,
|
||||||
|
@ -76,8 +78,9 @@ impl TableBuilder {
|
||||||
F: 'static + Fn(&'static Lua, A) -> FR,
|
F: 'static + Fn(&'static Lua, A) -> FR,
|
||||||
FR: 'static + Future<Output = LuaResult<R>>,
|
FR: 'static + Future<Output = LuaResult<R>>,
|
||||||
{
|
{
|
||||||
let f = self.lua.create_async_function(func)?;
|
let sched = self.lua.app_data_mut::<&TaskScheduler>().unwrap();
|
||||||
self.with_value(key, LuaValue::Function(f))
|
let func = sched.make_scheduled_async_fn(func)?;
|
||||||
|
self.with_value(key, LuaValue::Function(func))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_readonly(self) -> LuaResult<LuaTable<'static>> {
|
pub fn build_readonly(self) -> LuaResult<LuaTable<'static>> {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
local PORT = 8080
|
local PORT = 8080
|
||||||
local URL = `http://127.0.0.1:{PORT}`
|
local URL = `http://127.0.0.1:{PORT}`
|
||||||
local WS_URL = `ws://127.0.0.1:{PORT}`
|
|
||||||
local REQUEST = "Hello from client!"
|
|
||||||
local RESPONSE = "Hello, lune!"
|
local RESPONSE = "Hello, lune!"
|
||||||
|
|
||||||
|
local thread = task.delay(0.2, function()
|
||||||
|
task.spawn(error, "Serve must not block the current thread")
|
||||||
|
process.exit(1)
|
||||||
|
end)
|
||||||
|
|
||||||
local handle = net.serve(PORT, function(request)
|
local handle = net.serve(PORT, function(request)
|
||||||
-- info("Request:", request)
|
-- info("Request:", request)
|
||||||
-- info("Responding with", RESPONSE)
|
-- info("Responding with", RESPONSE)
|
||||||
|
@ -16,6 +19,7 @@ end)
|
||||||
local response = net.request(URL .. "/some/path?key=param1&key=param2&key2=param3").body
|
local response = net.request(URL .. "/some/path?key=param1&key=param2&key2=param3").body
|
||||||
assert(response == RESPONSE, "Invalid response from server")
|
assert(response == RESPONSE, "Invalid response from server")
|
||||||
|
|
||||||
|
task.cancel(thread)
|
||||||
handle.stop()
|
handle.stop()
|
||||||
|
|
||||||
-- Stopping is not guaranteed to happen instantly since it is async, but
|
-- Stopping is not guaranteed to happen instantly since it is async, but
|
||||||
|
@ -54,48 +58,3 @@ assert(
|
||||||
or string.find(message, "shut down"),
|
or string.find(message, "shut down"),
|
||||||
"The error message for calling stop twice on the net serve handle should be descriptive"
|
"The error message for calling stop twice on the net serve handle should be descriptive"
|
||||||
)
|
)
|
||||||
|
|
||||||
--[[
|
|
||||||
Serve should also take a full config with handler functions
|
|
||||||
|
|
||||||
A server should also be able to start on the previously closed port
|
|
||||||
]]
|
|
||||||
local handle2 = net.serve(PORT, {
|
|
||||||
handleRequest = function()
|
|
||||||
return RESPONSE
|
|
||||||
end,
|
|
||||||
handleWebSocket = function(socket)
|
|
||||||
local socketMessage = socket.next()
|
|
||||||
assert(socketMessage == REQUEST, "Invalid web socket request from client")
|
|
||||||
socket.send(RESPONSE)
|
|
||||||
socket.close()
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
local response3 = net.request(URL).body
|
|
||||||
assert(response3 == RESPONSE, "Invalid response from server")
|
|
||||||
|
|
||||||
-- Web socket client should work
|
|
||||||
local socket = net.socket(WS_URL)
|
|
||||||
|
|
||||||
socket.send(REQUEST)
|
|
||||||
|
|
||||||
local socketMessage = socket.next()
|
|
||||||
assert(socketMessage ~= nil, "Got no web socket response from server")
|
|
||||||
assert(socketMessage == RESPONSE, "Invalid web socket response from server")
|
|
||||||
|
|
||||||
socket.close()
|
|
||||||
|
|
||||||
-- Wait for the socket to close and make sure we can't send messages afterwards
|
|
||||||
task.wait()
|
|
||||||
local success3, err2 = (pcall :: any)(socket.send, "")
|
|
||||||
assert(not success3, "Sending messages after the socket has been closed should error")
|
|
||||||
local message2 = tostring(err2)
|
|
||||||
assert(
|
|
||||||
string.find(message2, "close") or string.find(message2, "closing"),
|
|
||||||
"The error message for sending messages on a closed web socket should be descriptive"
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Stop the server to end the test
|
|
||||||
|
|
||||||
handle2.stop()
|
|
64
tests/net/serve/websockets.luau
Normal file
64
tests/net/serve/websockets.luau
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
local PORT = 8080
|
||||||
|
local URL = `http://127.0.0.1:{PORT}`
|
||||||
|
local WS_URL = `ws://127.0.0.1:{PORT}`
|
||||||
|
local REQUEST = "Hello from client!"
|
||||||
|
local RESPONSE = "Hello, lune!"
|
||||||
|
|
||||||
|
local thread = task.delay(0.2, function()
|
||||||
|
task.spawn(error, "Serve must not block the current thread")
|
||||||
|
process.exit(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Serve should also take a full config with handler functions
|
||||||
|
|
||||||
|
A server should also be able to start on a previously closed port
|
||||||
|
]]
|
||||||
|
|
||||||
|
local handle = net.serve(PORT, function(request)
|
||||||
|
return RESPONSE
|
||||||
|
end)
|
||||||
|
|
||||||
|
task.cancel(thread)
|
||||||
|
handle.stop()
|
||||||
|
task.wait()
|
||||||
|
|
||||||
|
local handle2 = net.serve(PORT, {
|
||||||
|
handleRequest = function()
|
||||||
|
return RESPONSE
|
||||||
|
end,
|
||||||
|
handleWebSocket = function(socket)
|
||||||
|
local socketMessage = socket.next()
|
||||||
|
assert(socketMessage == REQUEST, "Invalid web socket request from client")
|
||||||
|
socket.send(RESPONSE)
|
||||||
|
socket.close()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = net.request(URL).body
|
||||||
|
assert(response == RESPONSE, "Invalid response from server")
|
||||||
|
|
||||||
|
-- Web socket client should work
|
||||||
|
local socket = net.socket(WS_URL)
|
||||||
|
|
||||||
|
socket.send(REQUEST)
|
||||||
|
|
||||||
|
local socketMessage = socket.next()
|
||||||
|
assert(socketMessage ~= nil, "Got no web socket response from server")
|
||||||
|
assert(socketMessage == RESPONSE, "Invalid web socket response from server")
|
||||||
|
|
||||||
|
socket.close()
|
||||||
|
|
||||||
|
-- Wait for the socket to close and make sure we can't send messages afterwards
|
||||||
|
task.wait()
|
||||||
|
local success3, err2 = (pcall :: any)(socket.send, "")
|
||||||
|
assert(not success3, "Sending messages after the socket has been closed should error")
|
||||||
|
local message2 = tostring(err2)
|
||||||
|
assert(
|
||||||
|
string.find(message2, "close") or string.find(message2, "closing"),
|
||||||
|
"The error message for sending messages on a closed web socket should be descriptive"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Stop the server to end the test
|
||||||
|
|
||||||
|
handle2.stop()
|
Loading…
Reference in a new issue