mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 12:19:09 +00:00
Improve error handling when net.serve callback errors
This commit is contained in:
parent
a7fab69838
commit
8fd39fcf9d
6 changed files with 156 additions and 49 deletions
|
@ -1,9 +1,11 @@
|
||||||
mod client;
|
mod client;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod response;
|
||||||
mod server;
|
mod server;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
pub use client::{NetClient, NetClientBuilder};
|
pub use client::{NetClient, NetClientBuilder};
|
||||||
pub use config::{RequestConfig, ServeConfig};
|
pub use config::{RequestConfig, ServeConfig};
|
||||||
|
pub use response::{NetServeResponse, NetServeResponseKind};
|
||||||
pub use server::{NetLocalExec, NetService};
|
pub use server::{NetLocalExec, NetService};
|
||||||
pub use websocket::NetWebSocket;
|
pub use websocket::NetWebSocket;
|
||||||
|
|
106
packages/lib/src/lua/net/response.rs
Normal file
106
packages/lib/src/lua/net/response.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use hyper::{Body, Response};
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum NetServeResponseKind {
|
||||||
|
PlainText,
|
||||||
|
Table,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NetServeResponse {
|
||||||
|
kind: NetServeResponseKind,
|
||||||
|
status: u16,
|
||||||
|
headers: HashMap<String, Vec<u8>>,
|
||||||
|
body: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetServeResponse {
|
||||||
|
pub fn into_response(self) -> LuaResult<Response<Body>> {
|
||||||
|
Ok(match self.kind {
|
||||||
|
NetServeResponseKind::PlainText => Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("Content-Type", "text/plain")
|
||||||
|
.body(Body::from(self.body.unwrap()))
|
||||||
|
.map_err(LuaError::external)?,
|
||||||
|
NetServeResponseKind::Table => {
|
||||||
|
let mut response = Response::builder();
|
||||||
|
for (key, value) in self.headers {
|
||||||
|
response = response.header(&key, value);
|
||||||
|
}
|
||||||
|
response
|
||||||
|
.status(self.status)
|
||||||
|
.body(Body::from(self.body.unwrap_or_default()))
|
||||||
|
.map_err(LuaError::external)?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> FromLua<'lua> for NetServeResponse {
|
||||||
|
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
// Plain strings from the handler are plaintext responses
|
||||||
|
LuaValue::String(s) => Ok(Self {
|
||||||
|
kind: NetServeResponseKind::PlainText,
|
||||||
|
status: 200,
|
||||||
|
headers: HashMap::new(),
|
||||||
|
body: Some(s.as_bytes().to_vec()),
|
||||||
|
}),
|
||||||
|
// Tables are more detailed responses with potential status, headers, body
|
||||||
|
LuaValue::Table(t) => {
|
||||||
|
let status: Option<u16> = t.get("status")?;
|
||||||
|
let headers: Option<LuaTable> = t.get("headers")?;
|
||||||
|
let body: Option<LuaString> = t.get("body")?;
|
||||||
|
|
||||||
|
let mut headers_map = HashMap::new();
|
||||||
|
if let Some(headers) = headers {
|
||||||
|
for pair in headers.pairs::<String, LuaString>() {
|
||||||
|
let (h, v) = pair?;
|
||||||
|
headers_map.insert(h, v.as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_bytes = body.map(|s| s.as_bytes().to_vec());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
kind: NetServeResponseKind::Table,
|
||||||
|
status: status.unwrap_or(200),
|
||||||
|
headers: headers_map,
|
||||||
|
body: body_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Anything else is an error
|
||||||
|
value => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "NetServeResponse",
|
||||||
|
message: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> ToLua<'lua> for NetServeResponse {
|
||||||
|
fn to_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
||||||
|
if self.headers.len() > i32::MAX as usize {
|
||||||
|
return Err(LuaError::ToLuaConversionError {
|
||||||
|
from: "NetServeResponse",
|
||||||
|
to: "table",
|
||||||
|
message: Some("Too many header values".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let body = self.body.map(|b| lua.create_string(&b)).transpose()?;
|
||||||
|
let headers = lua.create_table_with_capacity(0, self.headers.len() as i32)?;
|
||||||
|
for (key, value) in self.headers {
|
||||||
|
headers.set(key, lua.create_string(&value)?)?;
|
||||||
|
}
|
||||||
|
let table = lua.create_table_with_capacity(0, 3)?;
|
||||||
|
table.set("status", self.status)?;
|
||||||
|
table.set("headers", headers)?;
|
||||||
|
table.set("body", body)?;
|
||||||
|
table.set_readonly(true);
|
||||||
|
Ok(LuaValue::Table(table))
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
utils::table::TableBuilder,
|
utils::table::TableBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::NetWebSocket;
|
use super::{NetServeResponse, NetWebSocket};
|
||||||
|
|
||||||
// Hyper service implementation for net, lots of boilerplate here
|
// Hyper service implementation for net, lots of boilerplate here
|
||||||
// but make_svc and make_svc_function do not work for what we need
|
// but make_svc and make_svc_function do not work for what we need
|
||||||
|
@ -77,7 +77,6 @@ impl Service<Request<Body>> for NetServiceInner {
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// Convert request body into bytes, extract handler
|
// 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 bytes = to_bytes(body).await.map_err(LuaError::external)?;
|
||||||
let handler: LuaFunction = lua.registry_value(&key)?;
|
let handler: LuaFunction = lua.registry_value(&key)?;
|
||||||
// Create a readonly table for the request query params
|
// Create a readonly table for the request query params
|
||||||
|
@ -112,53 +111,22 @@ impl Service<Request<Body>> for NetServiceInner {
|
||||||
.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()?;
|
||||||
// TODO: Make some kind of NetServeResponse type with a
|
let response: LuaResult<NetServeResponse> = handler.call(request);
|
||||||
// FromLua implementation instead, this is a bit messy
|
// Send below errors to task scheduler so that they can emit properly
|
||||||
// and does not send errors to the scheduler properly
|
let lua_error = match response {
|
||||||
match handler.call(request) {
|
Ok(r) => match r.into_response() {
|
||||||
// Plain strings from the handler are plaintext responses
|
Ok(res) => return Ok(res),
|
||||||
Ok(LuaValue::String(s)) => Ok(Response::builder()
|
Err(err) => err,
|
||||||
.status(200)
|
},
|
||||||
.header("Content-Type", "text/plain")
|
Err(err) => err,
|
||||||
.body(Body::from(s.as_bytes().to_vec()))
|
};
|
||||||
.unwrap()),
|
lua.app_data_ref::<&TaskScheduler>()
|
||||||
// Tables are more detailed responses with potential status, headers, body
|
.expect("Missing task scheduler")
|
||||||
Ok(LuaValue::Table(t)) => {
|
.forward_lua_error(lua_error);
|
||||||
let status = t.get::<_, Option<u16>>("status")?.unwrap_or(200);
|
Ok(Response::builder()
|
||||||
let mut resp = Response::builder().status(status);
|
.status(500)
|
||||||
|
.body(Body::from("Internal Server Error"))
|
||||||
if let Some(headers) = t.get::<_, Option<LuaTable>>("headers")? {
|
.unwrap())
|
||||||
for pair in headers.pairs::<String, LuaString>() {
|
|
||||||
let (h, v) = pair?;
|
|
||||||
resp = resp.header(&h, v.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = t
|
|
||||||
.get::<_, Option<LuaString>>("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(_) => {
|
|
||||||
// TODO: Send above error to task scheduler so that it can emit properly
|
|
||||||
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(_) => {
|
|
||||||
// TODO: Implement the type in the above todo
|
|
||||||
Ok(Response::builder()
|
|
||||||
.status(500)
|
|
||||||
.body(Body::from("Internal Server Error"))
|
|
||||||
.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ async fn receive_next_message(scheduler: &TaskScheduler<'_>) -> TaskSchedulerSta
|
||||||
if let Some(message) = message_opt {
|
if let Some(message) = message_opt {
|
||||||
match message {
|
match message {
|
||||||
TaskSchedulerMessage::NewBlockingTaskReady => TaskSchedulerState::new(scheduler),
|
TaskSchedulerMessage::NewBlockingTaskReady => TaskSchedulerState::new(scheduler),
|
||||||
|
TaskSchedulerMessage::NewLuaErrorReady(err) => TaskSchedulerState::err(scheduler, err),
|
||||||
TaskSchedulerMessage::Spawned => {
|
TaskSchedulerMessage::Spawned => {
|
||||||
let prev = scheduler.futures_background_count.get();
|
let prev = scheduler.futures_background_count.get();
|
||||||
scheduler.futures_background_count.set(prev + 1);
|
scheduler.futures_background_count.set(prev + 1);
|
||||||
|
|
|
@ -5,6 +5,7 @@ use mlua::prelude::*;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TaskSchedulerMessage {
|
pub enum TaskSchedulerMessage {
|
||||||
NewBlockingTaskReady,
|
NewBlockingTaskReady,
|
||||||
|
NewLuaErrorReady(LuaError),
|
||||||
Spawned,
|
Spawned,
|
||||||
Terminated(LuaResult<()>),
|
Terminated(LuaResult<()>),
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,35 @@ impl<'fut> TaskScheduler<'fut> {
|
||||||
self.exit_code.set(Some(code));
|
self.exit_code.set(Some(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Forwards a lua error to be emitted as soon as possible,
|
||||||
|
after any current blocking / queued tasks have been resumed.
|
||||||
|
|
||||||
|
Useful when an async function may call into Lua and get a
|
||||||
|
result back, without erroring out of the entire async block.
|
||||||
|
*/
|
||||||
|
pub fn forward_lua_error(&self, err: LuaError) {
|
||||||
|
let sender = self.futures_tx.clone();
|
||||||
|
sender
|
||||||
|
.send(TaskSchedulerMessage::NewLuaErrorReady(err))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"\
|
||||||
|
\nFailed to forward lua error - this is an internal error! \
|
||||||
|
\nPlease report it at {} \
|
||||||
|
\nDetails: {e} \
|
||||||
|
",
|
||||||
|
env!("CARGO_PKG_REPOSITORY")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Forces the current task to be set to the given reference.
|
||||||
|
|
||||||
|
Useful if a task is to be resumed externally but full
|
||||||
|
compatibility with the task scheduler is still necessary.
|
||||||
|
*/
|
||||||
pub(crate) fn force_set_current_task(&self, reference: Option<TaskReference>) {
|
pub(crate) fn force_set_current_task(&self, reference: Option<TaskReference>) {
|
||||||
self.tasks_current.set(reference);
|
self.tasks_current.set(reference);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue