Migrate away from tokio & reqwest, use smol & ureq

This commit is contained in:
Filip Tibell 2023-01-22 21:14:13 -05:00
parent e5e96dfd54
commit 06339a2699
No known key found for this signature in database
10 changed files with 394 additions and 717 deletions

811
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -23,12 +23,12 @@ lto = true # Enable link-time optimization
panic = "abort" # Remove extra panic info panic = "abort" # Remove extra panic info
[dependencies] [dependencies]
anyhow = { version = "1.0.68" } anyhow = "1.0.68"
clap = { version = "4.1.1", features = ["derive"] } clap = { version = "4.1.1", features = ["derive"] }
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] } mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
once_cell = { version = "1.17.0" } once_cell = "1.17.0"
os_str_bytes = { version = "6.4.1" } os_str_bytes = "6.4.1"
reqwest = { version = "0.11.13", features = ["gzip", "deflate"] }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.91" } serde_json = "1.0.91"
tokio = { version = "1.24.2", features = ["full"] } smol = "1.3.0"
ureq = "2.6.2"

View file

@ -57,7 +57,7 @@ impl Cli {
// Download definition files, if wanted // Download definition files, if wanted
let download_types_requested = self.download_selene_types || self.download_luau_types; let download_types_requested = self.download_selene_types || self.download_luau_types;
if download_types_requested { if download_types_requested {
let client = GithubClient::new().map_err(mlua::Error::external)?; let client = GithubClient::new();
let release = client let release = client
.fetch_release_for_this_version() .fetch_release_for_this_version()
.await .await

View file

@ -10,9 +10,10 @@ mod utils;
use cli::Cli; use cli::Cli;
#[tokio::main] fn main() -> Result<()> {
async fn main() -> Result<()> { smol::block_on(async {
let cli = Cli::parse(); let cli = Cli::parse();
cli.run().await?; cli.run().await?;
Ok(()) Ok(())
})
} }

View file

@ -1,10 +1,10 @@
use std::env::current_dir; use std::env::current_dir;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use lune::utils::net::{get_github_owner_and_repo, get_request_user_agent_header}; use lune::utils::net::{get_github_owner_and_repo, get_request_user_agent_header};
use smol::unblock;
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
pub struct ReleaseAsset { pub struct ReleaseAsset {
@ -29,35 +29,28 @@ pub struct Release {
} }
pub struct Client { pub struct Client {
client: reqwest::Client,
github_owner: String, github_owner: String,
github_repo: String, github_repo: String,
} }
impl Client { impl Client {
pub fn new() -> Result<Self> { pub fn new() -> Self {
let (github_owner, github_repo) = get_github_owner_and_repo(); let (github_owner, github_repo) = get_github_owner_and_repo();
let mut headers = HeaderMap::new(); Self {
headers.insert(
"User-Agent",
HeaderValue::from_str(&get_request_user_agent_header())?,
);
headers.insert(
"Accept",
HeaderValue::from_static("application/vnd.github+json"),
);
headers.insert(
"X-GitHub-Api-Version",
HeaderValue::from_static("2022-11-28"),
);
let client = reqwest::Client::builder()
.default_headers(headers)
.build()?;
Ok(Self {
client,
github_owner, github_owner,
github_repo, github_repo,
}) }
}
async fn get(&self, url: &str, headers: &[(&str, &str)]) -> Result<String> {
let mut request = ureq::get(url)
.set("User-Agent", &get_request_user_agent_header())
.set("Accept", "application/vnd.github+json")
.set("X-GitHub-Api-Version", "2022-11-28");
for (header, value) in headers {
request = request.set(header, value);
}
unblock(|| Ok(request.send_string("")?.into_string()?)).await
} }
pub async fn fetch_releases(&self) -> Result<Vec<Release>> { pub async fn fetch_releases(&self) -> Result<Vec<Release>> {
@ -65,17 +58,8 @@ impl Client {
"https://api.github.com/repos/{}/{}/releases", "https://api.github.com/repos/{}/{}/releases",
&self.github_owner, &self.github_repo &self.github_owner, &self.github_repo
); );
let response_bytes = self let response_string = self.get(&release_api_url, &[]).await?;
.client Ok(serde_json::from_str(&response_string)?)
.get(release_api_url)
.send()
.await
.context("Failed to send releases request")?
.bytes()
.await
.context("Failed to get releases response bytes")?;
let response_body: Vec<Release> = serde_json::from_slice(&response_bytes)?;
Ok(response_body)
} }
pub async fn fetch_release_for_this_version(&self) -> Result<Release> { pub async fn fetch_release_for_this_version(&self) -> Result<Release> {
@ -95,17 +79,8 @@ impl Client {
.find(|asset| matches!(&asset.name, Some(name) if name == asset_name)) .find(|asset| matches!(&asset.name, Some(name) if name == asset_name))
{ {
let file_path = current_dir()?.join(asset_name); let file_path = current_dir()?.join(asset_name);
let file_bytes = self let file_string = self.get(&asset.url, &[]).await?;
.client smol::fs::write(&file_path, &file_string)
.get(&asset.url)
.header("Accept", "application/octet-stream")
.send()
.await
.context("Failed to send asset download request")?
.bytes()
.await
.context("Failed to get asset download response bytes")?;
tokio::fs::write(&file_path, &file_bytes)
.await .await
.with_context(|| { .with_context(|| {
format!("Failed to write file at path '{}'", &file_path.display()) format!("Failed to write file at path '{}'", &file_path.display())

View file

@ -1,7 +1,7 @@
use std::path::{PathBuf, MAIN_SEPARATOR}; use std::path::{PathBuf, MAIN_SEPARATOR};
use mlua::{Lua, Result}; use mlua::{Lua, Result};
use tokio::fs; use smol::{fs, prelude::*};
use crate::utils::table_builder::TableBuilder; use crate::utils::table_builder::TableBuilder;
@ -30,7 +30,7 @@ async fn fs_read_file(_: &Lua, path: String) -> Result<String> {
async fn fs_read_dir(_: &Lua, path: String) -> Result<Vec<String>> { async fn fs_read_dir(_: &Lua, path: String) -> Result<Vec<String>> {
let mut dir_strings = Vec::new(); let mut dir_strings = Vec::new();
let mut dir = fs::read_dir(&path).await.map_err(mlua::Error::external)?; let mut dir = fs::read_dir(&path).await.map_err(mlua::Error::external)?;
while let Some(dir_entry) = dir.next_entry().await.map_err(mlua::Error::external)? { while let Some(dir_entry) = dir.try_next().await.map_err(mlua::Error::external)? {
if let Some(dir_path_str) = dir_entry.path().to_str() { if let Some(dir_path_str) = dir_entry.path().to_str() {
dir_strings.push(dir_path_str.to_owned()); dir_strings.push(dir_path_str.to_owned());
} else { } else {

View file

@ -1,10 +1,6 @@
use std::{collections::HashMap, str::FromStr}; use std::collections::HashMap;
use mlua::{Error, Lua, LuaSerdeExt, Result, Table, Value}; use mlua::{Error, Lua, LuaSerdeExt, Result, Table, Value};
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Method,
};
use crate::utils::{net::get_request_user_agent_header, table_builder::TableBuilder}; use crate::utils::{net::get_request_user_agent_header, table_builder::TableBuilder};
@ -38,7 +34,7 @@ async fn net_request<'lua>(lua: &'lua Lua, config: Value<'lua>) -> Result<Table<
Value::String(s) => { Value::String(s) => {
let url = s.to_string_lossy().to_string(); let url = s.to_string_lossy().to_string();
let method = "GET".to_string(); let method = "GET".to_string();
(url, method, None, None) (url, method, HashMap::new(), None)
} }
Value::Table(tab) => { Value::Table(tab) => {
// Extract url // Extract url
@ -58,17 +54,14 @@ async fn net_request<'lua>(lua: &'lua Lua, config: Value<'lua>) -> Result<Table<
// Extract headers // Extract headers
let headers = match tab.raw_get::<&str, mlua::Table>("headers") { let headers = match tab.raw_get::<&str, mlua::Table>("headers") {
Ok(config_headers) => { Ok(config_headers) => {
let mut lua_headers = HeaderMap::new(); let mut lua_headers = HashMap::new();
for pair in config_headers.pairs::<mlua::String, mlua::String>() { for pair in config_headers.pairs::<mlua::String, mlua::String>() {
let (key, value) = pair?; let (key, value) = pair?.to_owned();
lua_headers.insert( lua_headers.insert(key, value);
HeaderName::from_str(key.to_str()?).map_err(Error::external)?,
HeaderValue::from_str(value.to_str()?).map_err(Error::external)?,
);
} }
Some(lua_headers) lua_headers
} }
Err(_) => None, Err(_) => HashMap::new(),
}; };
// Extract body // Extract body
let body = match tab.raw_get::<&str, mlua::String>("body") { let body = match tab.raw_get::<&str, mlua::String>("body") {
@ -85,58 +78,46 @@ async fn net_request<'lua>(lua: &'lua Lua, config: Value<'lua>) -> Result<Table<
} }
}; };
// Convert method string into proper enum // Convert method string into proper enum
let method = match Method::from_str(&method) { let method = method.trim().to_ascii_uppercase();
Ok(meth) => meth, let method = match method.as_ref() {
Err(_) => { "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" => &method,
_ => {
return Err(Error::RuntimeError(format!( return Err(Error::RuntimeError(format!(
"Invalid request config method '{}'", "Invalid request config method '{}'",
&method &method
))) )))
} }
}; };
// Extract headers from config, force user agent
let mut header_map = if let Some(headers) = headers {
headers
} else {
HeaderMap::new()
};
header_map.insert(
"User-Agent",
HeaderValue::from_str(&get_request_user_agent_header()).map_err(Error::external)?,
);
// Create a client to send a request with
// FUTURE: Try to reuse this client
let client = reqwest::Client::builder()
.build()
.map_err(Error::external)?;
// Create and send the request // Create and send the request
let mut request = client.request(method, url).headers(header_map); let mut request = ureq::request(method, &url);
if let Some(body) = body { for (header, value) in headers {
request = request.body(body); request = request.set(header.to_str()?, value.to_str()?);
} }
let response = request.send().await.map_err(Error::external)?; let response = request
// Extract status, headers, body .set("User-Agent", &get_request_user_agent_header()) // Always force user agent
let res_status = response.status(); .send_bytes(&body.unwrap_or_default());
let res_headers = response.headers().clone(); match response {
let res_bytes = response.bytes().await.map_err(Error::external)?; Ok(res) | Err(ureq::Error::Status(_, res)) => {
// Construct and return a readonly lua table with results // Extract status, headers
TableBuilder::new(lua)? let res_status = res.status();
.with_value("ok", res_status.is_success())? let res_status_text = res.status_text().to_owned();
.with_value("statusCode", res_status.as_u16())? let res_header_names = &res.headers_names();
.with_value( let res_headers = res_header_names
"statusMessage",
res_status.canonical_reason().unwrap_or("?"),
)?
.with_value(
"headers",
res_headers
.iter() .iter()
.filter_map(|(key, value)| match value.to_str() { .map(|name| (name.to_owned(), res.header(name).unwrap().to_owned()))
Ok(value) => Some((key.as_str(), value)), .collect::<HashMap<String, String>>();
Err(_) => None, // Read response bytes
}) let mut res_bytes = Vec::new();
.collect::<HashMap<_, _>>(), res.into_reader().read_to_end(&mut res_bytes)?;
)? // Construct and return a readonly lua table with results
.with_value("body", lua.create_string(&res_bytes)?)? TableBuilder::new(lua)?
.build_readonly() .with_value("ok", (200..300).contains(&res_status))?
.with_value("statusCode", res_status)?
.with_value("statusMessage", res_status_text)?
.with_value("headers", res_headers)?
.with_value("body", lua.create_string(&res_bytes)?)?
.build_readonly()
}
Err(e) => Err(Error::external(e)),
}
} }

View file

@ -5,7 +5,7 @@ use std::{
use mlua::{Error, Function, Lua, MetaMethod, Result, Table, Value}; use mlua::{Error, Function, Lua, MetaMethod, Result, Table, Value};
use os_str_bytes::RawOsString; use os_str_bytes::RawOsString;
use tokio::process::Command; use smol::process::Command;
use crate::utils::table_builder::TableBuilder; use crate::utils::table_builder::TableBuilder;
@ -113,10 +113,7 @@ async fn process_spawn(lua: &Lua, (program, args): (String, Option<Vec<String>>)
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.map_err(mlua::Error::external)?; .map_err(mlua::Error::external)?;
let output = child let output = child.output().await.map_err(mlua::Error::external)?;
.wait_with_output()
.await
.map_err(mlua::Error::external)?;
// NOTE: If an exit code was not given by the child process, // NOTE: If an exit code was not given by the child process,
// we default to 1 if it yielded any error output, otherwise 0 // we default to 1 if it yielded any error output, otherwise 0
let code = output let code = output

View file

@ -1,7 +1,7 @@
use std::time::Duration; use std::time::{Duration, Instant};
use mlua::{Error, Function, Lua, Result, Table, Thread, Value, Variadic}; use mlua::{Error, Function, Lua, Result, Table, Thread, Value, Variadic};
use tokio::time::{self, Instant}; use smol::Timer;
use crate::utils::table_builder::TableBuilder; use crate::utils::table_builder::TableBuilder;
@ -78,7 +78,7 @@ async fn task_spawn<'a>(lua: &'a Lua, (tof, args): (Value<'a>, Vararg<'a>)) -> R
// the async wait function inside of a coroutine // the async wait function inside of a coroutine
async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> { async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
let start = Instant::now(); let start = Instant::now();
time::sleep( Timer::after(
duration duration
.map(Duration::from_secs_f32) .map(Duration::from_secs_f32)
.unwrap_or(Duration::ZERO), .unwrap_or(Duration::ZERO),

View file

@ -1,8 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use mlua::Lua; use mlua::prelude::*;
use tokio::task;
pub mod globals; pub mod globals;
pub mod utils; pub mod utils;
@ -62,46 +61,33 @@ impl Lune {
} }
pub async fn run(&self, name: &str, chunk: &str) -> Result<()> { pub async fn run(&self, name: &str, chunk: &str) -> Result<()> {
let run_name = name.to_owned(); smol::block_on(async {
let run_chunk = chunk.to_owned(); let lua = Lua::new();
let run_globals = self.globals.to_owned(); for global in &self.globals {
let run_args = self.args.to_owned(); match &global {
// Spawn a thread-local task so that we can then spawn LuneGlobal::Console => create_console(&lua).await?,
// more tasks in our globals without the Send requirement LuneGlobal::Fs => create_fs(&lua).await?,
let local = task::LocalSet::new(); LuneGlobal::Net => create_net(&lua).await?,
local LuneGlobal::Process => create_process(&lua, self.args.clone()).await?,
.run_until(async move { LuneGlobal::Task => create_task(&lua).await?,
task::spawn_local(async move { }
let lua = Lua::new(); }
for global in &run_globals { let result = lua.load(chunk).set_name(name)?.exec_async().await;
match &global { match result {
LuneGlobal::Console => create_console(&lua).await?, Ok(_) => Ok(()),
LuneGlobal::Fs => create_fs(&lua).await?, Err(e) => {
LuneGlobal::Net => create_net(&lua).await?, if cfg!(test) {
LuneGlobal::Process => create_process(&lua, run_args.clone()).await?, bail!(pretty_format_luau_error(&e))
LuneGlobal::Task => create_task(&lua).await?, } else {
} bail!(
"\n{}\n{}",
format_label("ERROR"),
pretty_format_luau_error(&e)
)
} }
let result = lua.load(&run_chunk).set_name(&run_name)?.exec_async().await; }
match result { }
Ok(_) => Ok(()), })
Err(e) => {
if cfg!(test) {
bail!(pretty_format_luau_error(&e))
} else {
bail!(
"\n{}\n{}",
format_label("ERROR"),
pretty_format_luau_error(&e)
)
}
}
}
})
.await
.unwrap()
})
.await
} }
} }
@ -109,26 +95,28 @@ impl Lune {
mod tests { mod tests {
use crate::Lune; use crate::Lune;
use anyhow::Result; use anyhow::Result;
use smol::fs::read_to_string;
use std::env::current_dir; use std::env::current_dir;
use tokio::fs::read_to_string;
const ARGS: &[&str] = &["Foo", "Bar"]; const ARGS: &[&str] = &["Foo", "Bar"];
macro_rules! run_tests { macro_rules! run_tests {
($($name:ident: $value:expr,)*) => { ($($name:ident: $value:expr,)*) => {
$( $(
#[tokio::test] #[test]
async fn $name() -> Result<()> { fn $name() -> Result<()> {
let path = current_dir() smol::block_on(async {
.unwrap() let path = current_dir()
.join(format!("src/tests/{}.luau", $value)); .unwrap()
let script = read_to_string(&path) .join(format!("src/tests/{}.luau", $value));
.await let script = read_to_string(&path)
.unwrap(); .await
let lune = Lune::new() .unwrap();
.with_args(ARGS.clone().iter().map(ToString::to_string).collect()) let lune = Lune::new()
.with_all_globals(); .with_args(ARGS.clone().iter().map(ToString::to_string).collect())
lune.run($value, &script).await .with_all_globals();
lune.run($value, &script).await
})
} }
)* )*
} }