mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
Make request and response implement FromLua directly instead of separate config
This commit is contained in:
parent
1915441ee6
commit
9f6a1532a6
11 changed files with 252 additions and 349 deletions
|
@ -3,7 +3,6 @@
|
|||
|
||||
local net = require("@lune/net")
|
||||
local process = require("@lune/process")
|
||||
local task = require("@lune/task")
|
||||
|
||||
local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
|
||||
then assert(tonumber(process.env.PORT), "Failed to parse port from env")
|
||||
|
@ -11,6 +10,10 @@ local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
|
|||
|
||||
-- Create our responder functions
|
||||
|
||||
local function root(_request: net.ServeRequest): string
|
||||
return `Hello from Lune server!`
|
||||
end
|
||||
|
||||
local function pong(request: net.ServeRequest): string
|
||||
return `Pong!\n{request.path}\n{request.body}`
|
||||
end
|
||||
|
@ -29,10 +32,12 @@ local function notFound(_request: net.ServeRequest): net.ServeResponse
|
|||
}
|
||||
end
|
||||
|
||||
-- Run the server on port 8080
|
||||
-- Run the server on the port forever
|
||||
|
||||
local handle = net.serve(PORT, function(request)
|
||||
if string.sub(request.path, 1, 5) == "/ping" then
|
||||
net.serve(PORT, function(request)
|
||||
if request.path == "/" then
|
||||
return root(request)
|
||||
elseif string.sub(request.path, 1, 5) == "/ping" then
|
||||
return pong(request)
|
||||
elseif string.sub(request.path, 1, 7) == "/teapot" then
|
||||
return teapot(request)
|
||||
|
@ -42,12 +47,4 @@ local handle = net.serve(PORT, function(request)
|
|||
end)
|
||||
|
||||
print(`Listening on port {PORT} 🚀`)
|
||||
|
||||
-- Exit our example after a small delay, if you copy this
|
||||
-- example just remove this part to keep the server running
|
||||
|
||||
task.delay(2, function()
|
||||
print("Shutting down...")
|
||||
task.wait(1)
|
||||
handle:stop()
|
||||
end)
|
||||
print("Press Ctrl+C to stop")
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use hyper::{header::USER_AGENT, Method};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::shared::headers::{create_user_agent_header, table_to_hash_map};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestConfigOptions {
|
||||
pub decompress: bool,
|
||||
}
|
||||
|
||||
impl Default for RequestConfigOptions {
|
||||
fn default() -> Self {
|
||||
Self { decompress: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for RequestConfigOptions {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::Nil = value {
|
||||
// Nil means default options
|
||||
Ok(Self::default())
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// Table means custom options
|
||||
let decompress = match tab.get::<Option<bool>>("decompress") {
|
||||
Ok(decomp) => Ok(decomp.unwrap_or(true)),
|
||||
Err(_) => Err(LuaError::RuntimeError(
|
||||
"Invalid option value for 'decompress' in request config options".to_string(),
|
||||
)),
|
||||
}?;
|
||||
Ok(Self { decompress })
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "RequestConfigOptions".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid request config options - expected table or nil, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestConfig {
|
||||
pub url: String,
|
||||
pub method: Method,
|
||||
pub query: HashMap<String, Vec<String>>,
|
||||
pub headers: HashMap<String, Vec<String>>,
|
||||
pub body: Option<Vec<u8>>,
|
||||
pub options: RequestConfigOptions,
|
||||
}
|
||||
|
||||
impl FromLua for RequestConfig {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||
// If we just got a string we assume its a GET request to a given url
|
||||
if let LuaValue::String(s) = value {
|
||||
Ok(Self {
|
||||
url: s.to_string_lossy().to_string(),
|
||||
method: Method::GET,
|
||||
query: HashMap::new(),
|
||||
headers: HashMap::new(),
|
||||
body: None,
|
||||
options: RequestConfigOptions::default(),
|
||||
})
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// If we got a table we are able to configure the entire request
|
||||
|
||||
// Extract url
|
||||
let url = match tab.get::<LuaString>("url") {
|
||||
Ok(config_url) => Ok(config_url.to_string_lossy().to_string()),
|
||||
Err(_) => Err(LuaError::runtime("Missing 'url' in request config")),
|
||||
}?;
|
||||
// Extract method
|
||||
let method = match tab.get::<LuaString>("method") {
|
||||
Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(),
|
||||
Err(_) => "GET".to_string(),
|
||||
};
|
||||
// Extract query
|
||||
let query = match tab.get::<LuaTable>("query") {
|
||||
Ok(tab) => table_to_hash_map(tab, "query")?,
|
||||
Err(_) => HashMap::new(),
|
||||
};
|
||||
// Extract headers
|
||||
let mut headers = match tab.get::<LuaTable>("headers") {
|
||||
Ok(tab) => table_to_hash_map(tab, "headers")?,
|
||||
Err(_) => HashMap::new(),
|
||||
};
|
||||
// Extract body
|
||||
let body = match tab.get::<BString>("body") {
|
||||
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Convert method string into proper enum
|
||||
let method = method.trim().to_ascii_uppercase();
|
||||
let method = match method.as_ref() {
|
||||
"GET" => Ok(Method::GET),
|
||||
"POST" => Ok(Method::POST),
|
||||
"PUT" => Ok(Method::PUT),
|
||||
"DELETE" => Ok(Method::DELETE),
|
||||
"HEAD" => Ok(Method::HEAD),
|
||||
"OPTIONS" => Ok(Method::OPTIONS),
|
||||
"PATCH" => Ok(Method::PATCH),
|
||||
_ => Err(LuaError::RuntimeError(format!(
|
||||
"Invalid request config method '{}'",
|
||||
&method
|
||||
))),
|
||||
}?;
|
||||
|
||||
// Parse any extra options given
|
||||
let options = match tab.get::<LuaValue>("options") {
|
||||
Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?,
|
||||
Err(_) => RequestConfigOptions::default(),
|
||||
};
|
||||
|
||||
// Finally, add any default headers, if applicable
|
||||
add_default_headers(lua, &mut headers)?;
|
||||
|
||||
// All good, validated and we got what we need
|
||||
Ok(Self {
|
||||
url,
|
||||
method,
|
||||
query,
|
||||
headers,
|
||||
body,
|
||||
options,
|
||||
})
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "RequestConfig".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid request config - expected string or table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_default_headers(lua: &Lua, headers: &mut HashMap<String, Vec<String>>) -> LuaResult<()> {
|
||||
if !headers.contains_key(USER_AGENT.as_str()) {
|
||||
let ua = create_user_agent_header(lua)?;
|
||||
headers.insert(USER_AGENT.to_string(), vec![ua]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -18,7 +18,6 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
pub mod http_stream;
|
||||
pub mod rustls;
|
||||
pub mod ws_stream;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use hyper::header::{HeaderValue, USER_AGENT};
|
||||
use lune_utils::TableBuilder;
|
||||
use mlua::prelude::*;
|
||||
|
||||
|
@ -9,9 +10,12 @@ pub(crate) mod shared;
|
|||
pub(crate) mod url;
|
||||
|
||||
use self::{
|
||||
client::{config::RequestConfig, ws_stream::WsStream},
|
||||
client::ws_stream::WsStream,
|
||||
server::{config::ServeConfig, handle::ServeHandle},
|
||||
shared::{request::Request, response::Response, websocket::Websocket},
|
||||
shared::{
|
||||
headers::create_user_agent_header, request::Request, response::Response,
|
||||
websocket::Websocket,
|
||||
},
|
||||
};
|
||||
|
||||
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
|
||||
|
@ -41,8 +45,13 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
|||
.build_readonly()
|
||||
}
|
||||
|
||||
async fn net_request(lua: Lua, config: RequestConfig) -> LuaResult<Response> {
|
||||
self::client::send_request(Request::try_from(config)?, lua).await
|
||||
async fn net_request(lua: Lua, mut req: Request) -> LuaResult<Response> {
|
||||
if !req.headers().contains_key(USER_AGENT.as_str()) {
|
||||
let ua = create_user_agent_header(&lua)?;
|
||||
let ua = HeaderValue::from_str(&ua).into_lua_err()?;
|
||||
req.inner.headers_mut().insert(USER_AGENT, ua);
|
||||
}
|
||||
self::client::send_request(req, lua).await
|
||||
}
|
||||
|
||||
async fn net_socket(_: Lua, url: String) -> LuaResult<Websocket<WsStream>> {
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||
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#"
|
||||
|
@ -21,64 +14,6 @@ return {
|
|||
}
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResponseConfig {
|
||||
pub status: StatusCode,
|
||||
pub headers: HashMap<String, Vec<String>>,
|
||||
pub body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl FromLua for ResponseConfig {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
// If we just got a string we assume its a plaintext 200 response
|
||||
if let LuaValue::String(s) = value {
|
||||
Ok(Self {
|
||||
status: StatusCode::OK,
|
||||
headers: HashMap::from([(
|
||||
CONTENT_TYPE.to_string(),
|
||||
vec!["text/plain".to_string()],
|
||||
)]),
|
||||
body: Some(s.as_bytes().to_owned()),
|
||||
})
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// If we got a table we are able to configure the entire response
|
||||
|
||||
// Extract url
|
||||
let status = match tab.get::<u16>("status") {
|
||||
Ok(status) => Ok(StatusCode::from_u16(status).into_lua_err()?),
|
||||
Err(_) => Err(LuaError::runtime("Missing 'status' in response config")),
|
||||
}?;
|
||||
// Extract headers
|
||||
let headers = match tab.get::<LuaTable>("headers") {
|
||||
Ok(tab) => table_to_hash_map(tab, "headers")?,
|
||||
Err(_) => HashMap::new(),
|
||||
};
|
||||
// Extract body
|
||||
let body = match tab.get::<BString>("body") {
|
||||
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// All good, validated and we got what we need
|
||||
Ok(Self {
|
||||
status,
|
||||
headers,
|
||||
body,
|
||||
})
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "ResponseConfig".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid response config - expected string or table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServeConfig {
|
||||
pub address: IpAddr,
|
||||
|
|
|
@ -13,7 +13,7 @@ use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
|
|||
|
||||
use crate::{
|
||||
server::{
|
||||
config::{ResponseConfig, ServeConfig},
|
||||
config::ServeConfig,
|
||||
upgrade::{is_upgrade_request, make_upgrade_response},
|
||||
},
|
||||
shared::{hyper::HyperIo, request::Request, response::Response, websocket::Websocket},
|
||||
|
@ -96,8 +96,7 @@ async fn handle_request(
|
|||
.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)?;
|
||||
let response = Response::from_lua_multi(thread_res, &lua)?;
|
||||
Ok(response.into_full())
|
||||
}
|
||||
|
||||
|
|
|
@ -86,33 +86,3 @@ pub fn hash_map_to_table(
|
|||
|
||||
builder.build_readonly()
|
||||
}
|
||||
|
||||
pub fn table_to_hash_map(
|
||||
tab: LuaTable,
|
||||
tab_origin_key: &'static str,
|
||||
) -> LuaResult<HashMap<String, Vec<String>>> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for pair in tab.pairs::<String, LuaValue>() {
|
||||
let (key, value) = pair?;
|
||||
match value {
|
||||
LuaValue::String(s) => {
|
||||
map.insert(key, vec![s.to_str()?.to_owned()]);
|
||||
}
|
||||
LuaValue::Table(t) => {
|
||||
let mut values = Vec::new();
|
||||
for value in t.sequence_values::<LuaString>() {
|
||||
values.push(value?.to_str()?.to_owned());
|
||||
}
|
||||
map.insert(key, values);
|
||||
}
|
||||
_ => {
|
||||
return Err(LuaError::runtime(format!(
|
||||
"Value for '{tab_origin_key}' must be a string or array of strings",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
|
57
crates/lune-std-net/src/shared/lua.rs
Normal file
57
crates/lune-std-net/src/shared/lua.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use hyper::{
|
||||
body::Bytes,
|
||||
header::{HeaderName, HeaderValue},
|
||||
HeaderMap, Method,
|
||||
};
|
||||
use mlua::prelude::*;
|
||||
|
||||
pub fn lua_value_to_bytes(value: &LuaValue) -> LuaResult<Bytes> {
|
||||
match value {
|
||||
LuaValue::Nil => Ok(Bytes::new()),
|
||||
LuaValue::Buffer(buf) => Ok(Bytes::from(buf.to_vec())),
|
||||
LuaValue::String(str) => Ok(Bytes::copy_from_slice(&str.as_bytes())),
|
||||
v => Err(LuaError::FromLuaConversionError {
|
||||
from: v.type_name(),
|
||||
to: "Bytes".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid body - expected string or buffer, got {}",
|
||||
v.type_name()
|
||||
)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lua_value_to_method(value: &LuaValue) -> LuaResult<Method> {
|
||||
match value {
|
||||
LuaValue::Nil => Ok(Method::GET),
|
||||
LuaValue::String(str) => {
|
||||
let bytes = str.as_bytes().trim_ascii().to_ascii_uppercase();
|
||||
Method::from_bytes(&bytes).into_lua_err()
|
||||
}
|
||||
LuaValue::Buffer(buf) => {
|
||||
let bytes = buf.to_vec().trim_ascii().to_ascii_uppercase();
|
||||
Method::from_bytes(&bytes).into_lua_err()
|
||||
}
|
||||
v => Err(LuaError::FromLuaConversionError {
|
||||
from: v.type_name(),
|
||||
to: "Method".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid method - expected string or buffer, got {}",
|
||||
v.type_name()
|
||||
)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lua_table_to_header_map(table: &LuaTable) -> LuaResult<HeaderMap> {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
for pair in table.pairs::<LuaString, LuaString>() {
|
||||
let (key, val) = pair?;
|
||||
let key = HeaderName::from_bytes(&key.as_bytes()).into_lua_err()?;
|
||||
let val = HeaderValue::from_bytes(&val.as_bytes()).into_lua_err()?;
|
||||
headers.insert(key, val);
|
||||
}
|
||||
|
||||
Ok(headers)
|
||||
}
|
|
@ -2,6 +2,7 @@ pub mod futures;
|
|||
pub mod headers;
|
||||
pub mod hyper;
|
||||
pub mod incoming;
|
||||
pub mod lua;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod websocket;
|
||||
|
|
|
@ -5,20 +5,56 @@ use url::Url;
|
|||
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
header::{HeaderName, HeaderValue},
|
||||
HeaderMap, Method, Request as HyperRequest,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::{
|
||||
client::config::RequestConfig,
|
||||
shared::{
|
||||
headers::{hash_map_to_table, header_map_to_table},
|
||||
incoming::handle_incoming_body,
|
||||
},
|
||||
use crate::shared::{
|
||||
headers::{hash_map_to_table, header_map_to_table},
|
||||
incoming::handle_incoming_body,
|
||||
lua::{lua_table_to_header_map, lua_value_to_bytes, lua_value_to_method},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestOptions {
|
||||
pub decompress: bool,
|
||||
}
|
||||
|
||||
impl Default for RequestOptions {
|
||||
fn default() -> Self {
|
||||
Self { decompress: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for RequestOptions {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::Nil = value {
|
||||
// Nil means default options
|
||||
Ok(Self::default())
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// Table means custom options
|
||||
let decompress = match tab.get::<Option<bool>>("decompress") {
|
||||
Ok(decomp) => Ok(decomp.unwrap_or(true)),
|
||||
Err(_) => Err(LuaError::RuntimeError(
|
||||
"Invalid option value for 'decompress' in request options".to_string(),
|
||||
)),
|
||||
}?;
|
||||
Ok(Self { decompress })
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "RequestOptions".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid request options - expected table or nil, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Request {
|
||||
// NOTE: We use Bytes instead of Full<Bytes> to avoid
|
||||
|
@ -135,52 +171,83 @@ impl Request {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RequestConfig> for Request {
|
||||
type Error = LuaError;
|
||||
fn try_from(config: RequestConfig) -> Result<Self, Self::Error> {
|
||||
// 1. Parse the URL and make sure it is valid
|
||||
let mut url = Url::parse(&config.url).into_lua_err()?;
|
||||
impl FromLua for Request {
|
||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||
if let LuaValue::String(s) = value {
|
||||
// If we just got a string we assume
|
||||
// its a GET request to a given url
|
||||
let uri = s.to_str()?;
|
||||
let uri = uri.parse().into_lua_err()?;
|
||||
|
||||
// 2. Append any query pairs passed as a table
|
||||
{
|
||||
let mut query = url.query_pairs_mut();
|
||||
for (key, values) in config.query {
|
||||
for value in values {
|
||||
let mut request = HyperRequest::new(Bytes::new());
|
||||
*request.uri_mut() = uri;
|
||||
|
||||
Ok(Self {
|
||||
inner: request,
|
||||
address: None,
|
||||
redirects: None,
|
||||
decompress: RequestOptions::default().decompress,
|
||||
})
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// If we got a table we are able to configure the
|
||||
// entire request, maybe with extra options too
|
||||
let options = match tab.get::<LuaValue>("options") {
|
||||
Ok(opts) => RequestOptions::from_lua(opts, lua)?,
|
||||
Err(_) => RequestOptions::default(),
|
||||
};
|
||||
|
||||
// Extract url (required) + optional structured query params
|
||||
let url = tab.get::<LuaString>("url")?;
|
||||
let mut url = url.to_str()?.parse::<Url>().into_lua_err()?;
|
||||
if let Some(t) = tab.get::<Option<LuaTable>>("query")? {
|
||||
let mut query = url.query_pairs_mut();
|
||||
for pair in t.pairs::<LuaString, LuaString>() {
|
||||
let (key, value) = pair?;
|
||||
let key = key.to_str()?;
|
||||
let value = value.to_str()?;
|
||||
query.append_pair(&key, &value);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract method
|
||||
let method = tab.get::<LuaValue>("method")?;
|
||||
let method = lua_value_to_method(&method)?;
|
||||
|
||||
// Extract headers
|
||||
let headers = tab.get::<Option<LuaTable>>("headers")?;
|
||||
let headers = headers
|
||||
.map(|t| lua_table_to_header_map(&t))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Extract body
|
||||
let body = tab.get::<LuaValue>("body")?;
|
||||
let body = lua_value_to_bytes(&body)?;
|
||||
|
||||
// Build the full request
|
||||
let mut request = HyperRequest::new(body);
|
||||
request.headers_mut().extend(headers);
|
||||
*request.uri_mut() = url.to_string().parse().unwrap();
|
||||
*request.method_mut() = method;
|
||||
|
||||
// All good, validated and we got what we need
|
||||
Ok(Self {
|
||||
inner: request,
|
||||
address: None,
|
||||
redirects: None,
|
||||
decompress: options.decompress,
|
||||
})
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Request".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid request - expected string or table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Create the inner request builder
|
||||
let mut builder = HyperRequest::builder()
|
||||
.method(config.method)
|
||||
.uri(url.as_str());
|
||||
|
||||
// 4. Append any headers passed as a table - builder
|
||||
// headers may be None if builder is already invalid
|
||||
if let Some(headers) = builder.headers_mut() {
|
||||
for (key, values) in config.headers {
|
||||
let key = HeaderName::from_bytes(key.as_bytes()).into_lua_err()?;
|
||||
for value in values {
|
||||
let value = HeaderValue::from_str(&value).into_lua_err()?;
|
||||
headers.insert(key.clone(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Convert request body bytes to the proper Body
|
||||
// type that Hyper expects, if we got any bytes
|
||||
let body = config.body.map(Bytes::from).unwrap_or_default();
|
||||
|
||||
// 6. Finally, attach the body, verifying that the request is valid
|
||||
let inner = builder.body(body).into_lua_err()?;
|
||||
|
||||
Ok(Self {
|
||||
inner,
|
||||
address: None,
|
||||
redirects: None,
|
||||
decompress: config.options.decompress,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,16 @@ use http_body_util::Full;
|
|||
|
||||
use hyper::{
|
||||
body::{Bytes, Incoming},
|
||||
header::{HeaderName, HeaderValue},
|
||||
HeaderMap, Response as HyperResponse,
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
HeaderMap, Response as HyperResponse, StatusCode,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::{
|
||||
server::config::ResponseConfig,
|
||||
shared::{headers::header_map_to_table, incoming::handle_incoming_body},
|
||||
use crate::shared::{
|
||||
headers::header_map_to_table,
|
||||
incoming::handle_incoming_body,
|
||||
lua::{lua_table_to_header_map, lua_value_to_bytes},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -104,33 +105,55 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ResponseConfig> for Response {
|
||||
type Error = LuaError;
|
||||
fn try_from(config: ResponseConfig) -> Result<Self, Self::Error> {
|
||||
// 1. Create the inner response builder
|
||||
let mut builder = HyperResponse::builder().status(config.status);
|
||||
impl FromLua for Response {
|
||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||
if let Ok(body) = lua_value_to_bytes(&value) {
|
||||
// String or buffer is always a 200 text/plain response
|
||||
let mut response = HyperResponse::new(body);
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||
Ok(Self {
|
||||
inner: response,
|
||||
decompressed: false,
|
||||
})
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// Extract status (required)
|
||||
let status = tab.get::<u16>("status")?;
|
||||
let status = StatusCode::from_u16(status).into_lua_err()?;
|
||||
|
||||
// 2. Append any headers passed as a table - builder
|
||||
// headers may be None if builder is already invalid
|
||||
if let Some(headers) = builder.headers_mut() {
|
||||
for (key, values) in config.headers {
|
||||
let key = HeaderName::from_bytes(key.as_bytes()).into_lua_err()?;
|
||||
for value in values {
|
||||
let value = HeaderValue::from_str(&value).into_lua_err()?;
|
||||
headers.insert(key.clone(), value);
|
||||
}
|
||||
}
|
||||
// Extract headers
|
||||
let headers = tab.get::<Option<LuaTable>>("headers")?;
|
||||
let headers = headers
|
||||
.map(|t| lua_table_to_header_map(&t))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Extract body
|
||||
let body = tab.get::<LuaValue>("body")?;
|
||||
let body = lua_value_to_bytes(&body)?;
|
||||
|
||||
// Build the full response
|
||||
let mut response = HyperResponse::new(body);
|
||||
response.headers_mut().extend(headers);
|
||||
*response.status_mut() = status;
|
||||
|
||||
// All good, validated and we got what we need
|
||||
Ok(Self {
|
||||
inner: response,
|
||||
decompressed: false,
|
||||
})
|
||||
} else {
|
||||
// Anything else is invalid
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Response".to_string(),
|
||||
message: Some(format!(
|
||||
"Invalid response - expected table/string/buffer, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
// 3. Convert response body bytes to the proper Body
|
||||
// type that Hyper expects, if we got any bytes
|
||||
let body = config.body.map(Bytes::from).unwrap_or_default();
|
||||
|
||||
// 4. Finally, attach the body, verifying that the response is valid
|
||||
Ok(Self {
|
||||
inner: builder.body(body).into_lua_err()?,
|
||||
decompressed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue