mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
Initial working http server
This commit is contained in:
parent
9157ed9d33
commit
4725497f42
4 changed files with 206 additions and 4 deletions
|
@ -8,10 +8,9 @@ pub(crate) mod server;
|
|||
pub(crate) mod shared;
|
||||
pub(crate) mod url;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use self::{
|
||||
client::config::RequestConfig,
|
||||
server::config::ResponseConfig,
|
||||
server::config::ServeConfig,
|
||||
shared::{request::Request, response::Response},
|
||||
};
|
||||
|
||||
|
@ -36,7 +35,7 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
|||
TableBuilder::new(lua)?
|
||||
.with_async_function("request", net_request)?
|
||||
// .with_async_function("socket", net_socket)?
|
||||
// .with_async_function("serve", net_serve)?
|
||||
.with_async_function("serve", net_serve)?
|
||||
.with_function("urlEncode", net_url_encode)?
|
||||
.with_function("urlDecode", net_url_decode)?
|
||||
.build_readonly()
|
||||
|
@ -46,6 +45,10 @@ async fn net_request(lua: Lua, config: RequestConfig) -> LuaResult<Response> {
|
|||
self::client::send_request(Request::try_from(config)?, lua).await
|
||||
}
|
||||
|
||||
async fn net_serve(lua: Lua, (port, config): (u16, ServeConfig)) -> LuaResult<()> {
|
||||
self::server::serve(lua, port, config).await
|
||||
}
|
||||
|
||||
fn net_url_encode(
|
||||
lua: &Lua,
|
||||
(lua_string, as_binary): (LuaString, Option<bool>),
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||
|
@ -6,6 +9,18 @@ use mlua::prelude::*;
|
|||
|
||||
use crate::shared::headers::table_to_hash_map;
|
||||
|
||||
const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||
|
||||
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
|
||||
return {
|
||||
status = 426,
|
||||
body = "Upgrade Required",
|
||||
headers = {
|
||||
Upgrade = "websocket",
|
||||
},
|
||||
}
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResponseConfig {
|
||||
pub status: StatusCode,
|
||||
|
@ -63,3 +78,75 @@ impl FromLua for ResponseConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServeConfig {
|
||||
pub address: IpAddr,
|
||||
pub handle_request: LuaFunction,
|
||||
pub handle_web_socket: Option<LuaFunction>,
|
||||
}
|
||||
|
||||
impl FromLua for ServeConfig {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::Function(f) = &value {
|
||||
// Single function = request handler, rest is default
|
||||
Ok(ServeConfig {
|
||||
handle_request: f.clone(),
|
||||
handle_web_socket: None,
|
||||
address: DEFAULT_IP_ADDRESS,
|
||||
})
|
||||
} else if let LuaValue::Table(t) = &value {
|
||||
// Table means custom options
|
||||
let address: Option<LuaString> = t.get("address")?;
|
||||
let handle_request: Option<LuaFunction> = t.get("handleRequest")?;
|
||||
let handle_web_socket: Option<LuaFunction> = t.get("handleWebSocket")?;
|
||||
if handle_request.is_some() || handle_web_socket.is_some() {
|
||||
let address: IpAddr = match &address {
|
||||
Some(addr) => {
|
||||
let addr_str = addr.to_str()?;
|
||||
|
||||
addr_str
|
||||
.trim_start_matches("http://")
|
||||
.trim_start_matches("https://")
|
||||
.parse()
|
||||
.map_err(|_e| LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "ServeConfig".to_string(),
|
||||
message: Some(format!(
|
||||
"IP address format is incorrect - \
|
||||
expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \
|
||||
got '{addr_str}'"
|
||||
)),
|
||||
})?
|
||||
}
|
||||
None => DEFAULT_IP_ADDRESS,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
address,
|
||||
handle_request: handle_request.unwrap_or_else(|| {
|
||||
lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER)
|
||||
.into_function()
|
||||
.expect("Failed to create default http responder function")
|
||||
}),
|
||||
handle_web_socket,
|
||||
})
|
||||
} else {
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "ServeConfig".to_string(),
|
||||
message: Some(String::from(
|
||||
"Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function",
|
||||
)),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "ServeConfig".to_string(),
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,61 @@
|
|||
use std::net::SocketAddr;
|
||||
|
||||
use async_net::TcpListener;
|
||||
use hyper::server::conn::http1::Builder as Http1Builder;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::LuaSpawnExt;
|
||||
|
||||
use crate::{
|
||||
server::{config::ServeConfig, service::Service},
|
||||
shared::hyper::{HyperIo, HyperTimer},
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
pub mod service;
|
||||
|
||||
/**
|
||||
Starts an HTTP server using the given port and configuration.
|
||||
*/
|
||||
pub async fn serve(lua: Lua, port: u16, config: ServeConfig) -> LuaResult<()> {
|
||||
let address = SocketAddr::from((config.address, port));
|
||||
let service = Service {
|
||||
lua: lua.clone(),
|
||||
address,
|
||||
config,
|
||||
};
|
||||
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
|
||||
lua.spawn_local({
|
||||
let lua = lua.clone();
|
||||
async move {
|
||||
loop {
|
||||
let (connection, _addr) = match listener.accept().await {
|
||||
Ok((connection, addr)) => (connection, addr),
|
||||
Err(err) => {
|
||||
eprintln!("Error while accepting connection: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
lua.spawn_local({
|
||||
let service = service.clone();
|
||||
async move {
|
||||
let result = Http1Builder::new()
|
||||
.timer(HyperTimer)
|
||||
.keep_alive(true) // Needed for websockets
|
||||
.serve_connection(HyperIo::from(connection), service)
|
||||
.with_upgrades()
|
||||
.await;
|
||||
if let Err(err) = result {
|
||||
eprintln!("Error while responding to request: {err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
52
crates/lune-std-net/src/server/service.rs
Normal file
52
crates/lune-std-net/src/server/service.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use std::{future::Future, net::SocketAddr, pin::Pin};
|
||||
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
service::Service as HyperService,
|
||||
Request as HyperRequest, Response as HyperResponse,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::LuaSchedulerExt;
|
||||
|
||||
use crate::{
|
||||
server::config::{ResponseConfig, ServeConfig},
|
||||
shared::{request::Request, response::Response},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct Service {
|
||||
pub(super) lua: Lua,
|
||||
pub(super) address: SocketAddr,
|
||||
pub(super) config: ServeConfig,
|
||||
}
|
||||
|
||||
impl HyperService<HyperRequest<Incoming>> for Service {
|
||||
type Response = HyperResponse<Full<Bytes>>;
|
||||
type Error = LuaError;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
fn call(&self, req: HyperRequest<Incoming>) -> Self::Future {
|
||||
let lua = self.lua.clone();
|
||||
let config = self.config.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let handler = config.handle_request.clone();
|
||||
let request = Request::from_incoming(req, true).await?;
|
||||
|
||||
let thread_id = lua.push_thread_back(handler, request)?;
|
||||
lua.track_thread(thread_id);
|
||||
lua.wait_for_thread(thread_id).await;
|
||||
|
||||
let thread_res = lua
|
||||
.get_thread_result(thread_id)
|
||||
.expect("Missing handler thread result")?;
|
||||
|
||||
let config = ResponseConfig::from_lua_multi(thread_res, &lua)?;
|
||||
let response = Response::try_from(config)?;
|
||||
|
||||
Ok(response.as_full())
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue