Serve from other IPs (#142)

This commit is contained in:
vocksel 2024-01-19 11:39:57 -08:00 committed by GitHub
parent 1b25ed2904
commit 3c68d8e314
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 73 additions and 7 deletions

View file

@ -140,9 +140,11 @@ impl FromLua<'_> for RequestConfig {
// Net serve config // Net serve config
#[derive(Debug)]
pub struct ServeConfig<'a> { pub struct ServeConfig<'a> {
pub handle_request: LuaFunction<'a>, pub handle_request: LuaFunction<'a>,
pub handle_web_socket: Option<LuaFunction<'a>>, pub handle_web_socket: Option<LuaFunction<'a>>,
pub address: Option<LuaString<'a>>,
} }
impl<'lua> FromLua<'lua> for ServeConfig<'lua> { impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
@ -152,11 +154,13 @@ impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
return Ok(ServeConfig { return Ok(ServeConfig {
handle_request: f.clone(), handle_request: f.clone(),
handle_web_socket: None, handle_web_socket: None,
address: None,
}) })
} }
LuaValue::Table(t) => { LuaValue::Table(t) => {
let handle_request: Option<LuaFunction> = t.raw_get("handleRequest")?; let handle_request: Option<LuaFunction> = t.raw_get("handleRequest")?;
let handle_web_socket: Option<LuaFunction> = t.raw_get("handleWebSocket")?; let handle_web_socket: Option<LuaFunction> = t.raw_get("handleWebSocket")?;
let address: Option<LuaString> = t.raw_get("address")?;
if handle_request.is_some() || handle_web_socket.is_some() { if handle_request.is_some() || handle_web_socket.is_some() {
return Ok(ServeConfig { return Ok(ServeConfig {
handle_request: handle_request.unwrap_or_else(|| { handle_request: handle_request.unwrap_or_else(|| {
@ -174,6 +178,7 @@ impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
.expect("Failed to create default http responder function") .expect("Failed to create default http responder function")
}), }),
handle_web_socket, handle_web_socket,
address,
}); });
} else { } else {
Some("Missing handleRequest and / or handleWebSocket".to_string()) Some("Missing handleRequest and / or handleWebSocket".to_string())

View file

@ -1,10 +1,15 @@
use std::net::Ipv4Addr;
use mlua::prelude::*; use mlua::prelude::*;
use hyper::header::CONTENT_ENCODING; use hyper::header::CONTENT_ENCODING;
use crate::lune::{scheduler::Scheduler, util::TableBuilder}; use crate::lune::{scheduler::Scheduler, util::TableBuilder};
use self::{server::create_server, util::header_map_to_table}; use self::{
server::{bind_to_addr, create_server},
util::header_map_to_table,
};
use super::serde::{ use super::serde::{
compress_decompress::{decompress, CompressDecompressFormat}, compress_decompress::{decompress, CompressDecompressFormat},
@ -21,9 +26,10 @@ mod websocket;
use client::{NetClient, NetClientBuilder}; use client::{NetClient, NetClientBuilder};
use config::{RequestConfig, ServeConfig}; use config::{RequestConfig, ServeConfig};
use server::bind_to_localhost;
use websocket::NetWebSocket; use websocket::NetWebSocket;
const DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
NetClientBuilder::new() NetClientBuilder::new()
.headers(&[("User-Agent", create_user_agent_header())])? .headers(&[("User-Agent", create_user_agent_header())])?
@ -137,7 +143,22 @@ where
.app_data_ref::<&Scheduler>() .app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler"); .expect("Lua struct is missing scheduler");
let builder = bind_to_localhost(port)?; let address: Ipv4Addr = match &config.address {
Some(addr) => {
let addr_str = addr.to_str()?;
addr_str
.trim_start_matches("http://")
.trim_start_matches("https://")
.parse()
.map_err(|_e| LuaError::RuntimeError(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,
};
let builder = bind_to_addr(address, port)?;
create_server(lua, &sched, config, builder) create_server(lua, &sched, config, builder)
} }

View file

@ -1,4 +1,9 @@
use std::{collections::HashMap, convert::Infallible, net::SocketAddr, sync::Arc}; use std::{
collections::HashMap,
convert::Infallible,
net::{Ipv4Addr, SocketAddr},
sync::Arc,
};
use hyper::{ use hyper::{
server::{conn::AddrIncoming, Builder}, server::{conn::AddrIncoming, Builder},
@ -20,12 +25,13 @@ use super::{
websocket::NetWebSocket, websocket::NetWebSocket,
}; };
pub(super) fn bind_to_localhost(port: u16) -> LuaResult<Builder<AddrIncoming>> { pub(super) fn bind_to_addr(address: Ipv4Addr, port: u16) -> LuaResult<Builder<AddrIncoming>> {
let addr = SocketAddr::from(([127, 0, 0, 1], port)); let addr = SocketAddr::from((address, port));
match Server::try_bind(&addr) { match Server::try_bind(&addr) {
Ok(b) => Ok(b), Ok(b) => Ok(b),
Err(e) => Err(LuaError::external(format!( Err(e) => Err(LuaError::external(format!(
"Failed to bind to localhost on port {port}\n{}", "Failed to bind to {addr}\n{}",
e.to_string() e.to_string()
.replace("error creating server listener: ", "> ") .replace("error creating server listener: ", "> ")
))), ))),

View file

@ -5,8 +5,13 @@ local task = require("@lune/task")
local PORT = 8080 local PORT = 8080
local URL = `http://127.0.0.1:{PORT}` local URL = `http://127.0.0.1:{PORT}`
local URL_EXTERNAL = `http://0.0.0.0`
local RESPONSE = "Hello, lune!" local RESPONSE = "Hello, lune!"
-- A server should never be running before testing
local isRunning = pcall(net.request, URL)
assert(not isRunning, `a server is already running at {URL}`)
-- Serve should not block the thread from continuing -- Serve should not block the thread from continuing
local thread = task.delay(1, function() local thread = task.delay(1, function()
@ -77,3 +82,32 @@ 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 be able to bind to other IP addresses
local handle2 = net.serve(PORT, {
address = URL_EXTERNAL,
handleRequest = function(request)
return `Response from {URL_EXTERNAL}:{PORT}`
end,
})
-- And any requests to that IP should succeed
local response3 = net.request(`{URL_EXTERNAL}:{PORT}`).body
assert(response3 ~= nil, "Invalid response from server")
handle2.stop()
-- Attempting to serve with a malformed IP address should throw an error
local success3 = pcall(function()
net.serve(8080, {
address = "a.b.c.d",
handleRequest = function()
return RESPONSE
end,
})
end)
assert(not success3, "Server was created with malformed address")
-- We have to manually exit so Windows CI doesn't get stuck forever
process.exit(0)