Initial working request function in net

This commit is contained in:
Filip Tibell 2025-04-26 15:56:10 +02:00
parent f9fc1c6de1
commit 07744d0079
8 changed files with 169 additions and 8 deletions

22
Cargo.lock generated
View file

@ -1336,6 +1336,19 @@ dependencies = [
"http 1.3.1",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http 1.3.1",
"http-body 1.0.1",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
@ -1379,9 +1392,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
@ -1839,6 +1860,7 @@ dependencies = [
"bstr",
"futures-lite",
"futures-rustls",
"http-body-util",
"hyper 1.6.0",
"lune-std-serde",
"lune-utils",

View file

@ -24,7 +24,8 @@ blocking = "1.6"
bstr = "1.9"
futures-lite = "2.6"
futures-rustls = "0.26"
hyper = "1.6"
http-body-util = "0.1"
hyper = { version = "1.6", features = ["http1", "client", "server"] }
pin-project-lite = "0.2"
rustls = "0.23"
rustls-pki-types = "1.11"

View file

@ -1,2 +1,4 @@
mod hyper;
mod request;
mod stream;
pub use self::request::{Request, Response};

View file

@ -0,0 +1,115 @@
use bstr::BString;
use futures_lite::prelude::*;
use http_body_util::{BodyStream, Full};
use hyper::{
body::{Bytes, Incoming},
client::conn::http1::handshake,
Method, Request as HyperRequest, Response as HyperResponse,
};
use mlua::prelude::*;
use crate::{
client::stream::HttpRequestStream,
shared::hyper::{HyperExecutor, HyperIo},
};
#[derive(Debug, Clone)]
pub struct Request {
inner: HyperRequest<Full<Bytes>>,
}
impl Request {
pub async fn send(self, lua: Lua) -> LuaResult<Response> {
let stream = HttpRequestStream::connect(self.inner.uri()).await?;
let (mut sender, conn) = handshake(HyperIo::from(stream))
.await
.map_err(LuaError::external)?;
HyperExecutor::execute(lua, conn);
let incoming = sender
.send_request(self.inner)
.await
.map_err(LuaError::external)?;
Response::from_incoming(incoming).await
}
}
impl FromLua for Request {
fn from_lua(value: LuaValue, _lua: &Lua) -> LuaResult<Self> {
if let LuaValue::String(s) = value {
// We got a string, assume it's a URL + GET method
let uri = s.to_str()?;
Ok(Self {
inner: HyperRequest::builder()
.uri(uri.as_ref())
.body(Full::new(Bytes::new()))
.into_lua_err()?,
})
} else if let LuaValue::Table(t) = value {
// URL is always required with table options
let url = t.get::<String>("url")?;
let builder = HyperRequest::builder().uri(url);
// Add method, if provided
let builder = match t.get::<Option<String>>("method") {
Ok(Some(method)) => builder.method(method.as_str()),
Ok(None) => builder.method(Method::GET),
Err(e) => return Err(e),
};
// Add body, if provided
let builder = match t.get::<Option<BString>>("body") {
Ok(Some(body)) => builder.body(Full::new(body.to_vec().into())),
Ok(None) => builder.body(Full::new(Bytes::new())),
Err(e) => return Err(e),
};
Ok(Self {
inner: builder.into_lua_err()?,
})
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: String::from("HttpRequest"),
message: Some(String::from("HttpRequest must be a string or table")),
})
}
}
}
#[derive(Debug, Clone)]
pub struct Response {
inner: HyperResponse<Full<Bytes>>,
}
impl Response {
pub async fn from_incoming(incoming: HyperResponse<Incoming>) -> LuaResult<Self> {
let (parts, body) = incoming.into_parts();
let body = BodyStream::new(body)
.try_fold(Vec::<u8>::new(), |mut body, chunk| {
if let Some(chunk) = chunk.data_ref() {
body.extend_from_slice(chunk);
}
Ok(body)
})
.await
.into_lua_err()?;
let bytes = Full::new(Bytes::from(body));
let inner = HyperResponse::from_parts(parts, bytes);
Ok(Self { inner })
}
}
impl LuaUserData for Response {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("ok", |_, this| Ok(this.inner.status().is_success()));
fields.add_field_method_get("status", |_, this| Ok(this.inner.status().as_u16()));
}
}

View file

@ -27,7 +27,7 @@ pub enum HttpRequestStream {
}
impl HttpRequestStream {
pub async fn connect(url: Uri) -> Result<Self, io::Error> {
pub async fn connect(url: &Uri) -> Result<Self, io::Error> {
let Some(host) = url.host() else {
return Err(make_err("unknown or missing host"));
};

View file

@ -8,6 +8,10 @@ mod client;
mod server;
mod url;
use self::client::{Request, Response};
pub(crate) mod shared;
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
/**
@ -27,10 +31,14 @@ pub fn typedefs() -> String {
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
// .with_async_function("request", net_request)?
.with_async_function("request", net_request)?
// .with_async_function("socket", net_socket)?
// .with_async_function("serve", net_serve)?
// .with_function("urlEncode", net_url_encode)?
// .with_function("urlDecode", net_url_decode)?
.build_readonly()
}
async fn net_request(lua: Lua, req: Request) -> LuaResult<Response> {
req.send(lua).await
}

View file

@ -9,7 +9,7 @@ use std::{
use async_io::Timer;
use futures_lite::prelude::*;
use hyper::rt::{self, ReadBufCursor};
use hyper::rt::{self, Executor, ReadBufCursor};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
@ -20,9 +20,21 @@ pub struct HyperExecutor {
lua: Lua,
}
impl From<Lua> for HyperExecutor {
fn from(lua: Lua) -> Self {
Self { lua }
#[allow(dead_code)]
impl HyperExecutor {
pub fn execute<Fut>(lua: Lua, fut: Fut)
where
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
let exec = if let Some(exec) = lua.app_data_ref::<Self>() {
exec
} else {
lua.set_app_data(Self { lua: lua.clone() });
lua.app_data_ref::<Self>().unwrap()
};
exec.execute(fut);
}
}

View file

@ -0,0 +1 @@
pub mod hyper;