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
[dependencies]
anyhow = { version = "1.0.68" }
anyhow = "1.0.68"
clap = { version = "4.1.1", features = ["derive"] }
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
once_cell = { version = "1.17.0" }
os_str_bytes = { version = "6.4.1" }
reqwest = { version = "0.11.13", features = ["gzip", "deflate"] }
once_cell = "1.17.0"
os_str_bytes = "6.4.1"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.91" }
tokio = { version = "1.24.2", features = ["full"] }
serde_json = "1.0.91"
smol = "1.3.0"
ureq = "2.6.2"

View file

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

View file

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

View file

@ -1,10 +1,10 @@
use std::env::current_dir;
use anyhow::{bail, Context, Result};
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use lune::utils::net::{get_github_owner_and_repo, get_request_user_agent_header};
use smol::unblock;
#[derive(Clone, Deserialize, Serialize)]
pub struct ReleaseAsset {
@ -29,35 +29,28 @@ pub struct Release {
}
pub struct Client {
client: reqwest::Client,
github_owner: String,
github_repo: String,
}
impl Client {
pub fn new() -> Result<Self> {
pub fn new() -> Self {
let (github_owner, github_repo) = get_github_owner_and_repo();
let mut headers = HeaderMap::new();
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,
Self {
github_owner,
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>> {
@ -65,17 +58,8 @@ impl Client {
"https://api.github.com/repos/{}/{}/releases",
&self.github_owner, &self.github_repo
);
let response_bytes = self
.client
.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)
let response_string = self.get(&release_api_url, &[]).await?;
Ok(serde_json::from_str(&response_string)?)
}
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))
{
let file_path = current_dir()?.join(asset_name);
let file_bytes = self
.client
.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)
let file_string = self.get(&asset.url, &[]).await?;
smol::fs::write(&file_path, &file_string)
.await
.with_context(|| {
format!("Failed to write file at path '{}'", &file_path.display())

View file

@ -1,7 +1,7 @@
use std::path::{PathBuf, MAIN_SEPARATOR};
use mlua::{Lua, Result};
use tokio::fs;
use smol::{fs, prelude::*};
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>> {
let mut dir_strings = Vec::new();
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() {
dir_strings.push(dir_path_str.to_owned());
} 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 reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Method,
};
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) => {
let url = s.to_string_lossy().to_string();
let method = "GET".to_string();
(url, method, None, None)
(url, method, HashMap::new(), None)
}
Value::Table(tab) => {
// Extract url
@ -58,17 +54,14 @@ async fn net_request<'lua>(lua: &'lua Lua, config: Value<'lua>) -> Result<Table<
// Extract headers
let headers = match tab.raw_get::<&str, mlua::Table>("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>() {
let (key, value) = pair?;
lua_headers.insert(
HeaderName::from_str(key.to_str()?).map_err(Error::external)?,
HeaderValue::from_str(value.to_str()?).map_err(Error::external)?,
);
let (key, value) = pair?.to_owned();
lua_headers.insert(key, value);
}
Some(lua_headers)
lua_headers
}
Err(_) => None,
Err(_) => HashMap::new(),
};
// Extract 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
let method = match Method::from_str(&method) {
Ok(meth) => meth,
Err(_) => {
let method = method.trim().to_ascii_uppercase();
let method = match method.as_ref() {
"GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH" => &method,
_ => {
return Err(Error::RuntimeError(format!(
"Invalid request config 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
let mut request = client.request(method, url).headers(header_map);
if let Some(body) = body {
request = request.body(body);
let mut request = ureq::request(method, &url);
for (header, value) in headers {
request = request.set(header.to_str()?, value.to_str()?);
}
let response = request.send().await.map_err(Error::external)?;
// Extract status, headers, body
let res_status = response.status();
let res_headers = response.headers().clone();
let res_bytes = response.bytes().await.map_err(Error::external)?;
// Construct and return a readonly lua table with results
TableBuilder::new(lua)?
.with_value("ok", res_status.is_success())?
.with_value("statusCode", res_status.as_u16())?
.with_value(
"statusMessage",
res_status.canonical_reason().unwrap_or("?"),
)?
.with_value(
"headers",
res_headers
let response = request
.set("User-Agent", &get_request_user_agent_header()) // Always force user agent
.send_bytes(&body.unwrap_or_default());
match response {
Ok(res) | Err(ureq::Error::Status(_, res)) => {
// Extract status, headers
let res_status = res.status();
let res_status_text = res.status_text().to_owned();
let res_header_names = &res.headers_names();
let res_headers = res_header_names
.iter()
.filter_map(|(key, value)| match value.to_str() {
Ok(value) => Some((key.as_str(), value)),
Err(_) => None,
})
.collect::<HashMap<_, _>>(),
)?
.with_value("body", lua.create_string(&res_bytes)?)?
.build_readonly()
.map(|name| (name.to_owned(), res.header(name).unwrap().to_owned()))
.collect::<HashMap<String, String>>();
// Read response bytes
let mut res_bytes = Vec::new();
res.into_reader().read_to_end(&mut res_bytes)?;
// Construct and return a readonly lua table with results
TableBuilder::new(lua)?
.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 os_str_bytes::RawOsString;
use tokio::process::Command;
use smol::process::Command;
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())
.spawn()
.map_err(mlua::Error::external)?;
let output = child
.wait_with_output()
.await
.map_err(mlua::Error::external)?;
let output = child.output().await.map_err(mlua::Error::external)?;
// NOTE: If an exit code was not given by the child process,
// we default to 1 if it yielded any error output, otherwise 0
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 tokio::time::{self, Instant};
use smol::Timer;
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
async fn task_wait(_: &Lua, duration: Option<f32>) -> Result<f32> {
let start = Instant::now();
time::sleep(
Timer::after(
duration
.map(Duration::from_secs_f32)
.unwrap_or(Duration::ZERO),

View file

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