frontend-utils: Switch to reqwest

This commit is contained in:
Nathan Adams 2024-04-13 00:34:53 +02:00
parent b1a8ed455f
commit 7079f38a9f
7 changed files with 513 additions and 274 deletions

652
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,7 @@ thiserror = "1.0"
url = "2.5.0"
wasm-bindgen = "=0.2.92"
walkdir = "2.5.0"
tokio = { version = "1.37.0" }
[workspace.lints.rust]
# Clippy nightly often adds new/buggy lints that we want to ignore.

View File

@ -43,6 +43,7 @@ chrono = { workspace = true }
fluent-templates = "0.9.1"
toml_edit = { version = "0.22.9", features = ["parse"] }
gilrs = "0.10"
tokio = { workspace = true, features = ["rt-multi-thread", "macros"]}
# Deliberately held back to match tracy client used by profiling crate
tracing-tracy = { version = "=0.10.4", optional = true }

View File

@ -146,7 +146,8 @@ fn shutdown() {
}
}
fn main() -> Result<(), Error> {
#[tokio::main]
async fn main() -> Result<(), Error> {
init();
let opt = Opt::parse();

View File

@ -20,12 +20,12 @@ urlencoding = "2.1.3"
ruffle_core = { path = "../core", default-features = false }
async-channel = { workspace = true }
slotmap = { workspace = true }
isahc = { version = "1.7.2", features = ["cookies"] }
futures = { workspace = true }
async-io = "2.3.2"
async-net = "2.0.0"
futures-lite = "2.3.0"
webbrowser = "0.8.14"
reqwest = { version = "0.12.3", features = ["cookies"] }
[dev-dependencies]
tempfile = "3"

View File

@ -9,9 +9,7 @@ use async_net::TcpStream;
use futures::future::select;
use futures::{AsyncReadExt, AsyncWriteExt};
use futures_lite::FutureExt;
use isahc::config::{Configurable, RedirectPolicy};
use isahc::http::{HeaderName, HeaderValue};
use isahc::{HttpClient, Request as IsahcRequest, ResponseExt};
use reqwest::Proxy;
use ruffle_core::backend::navigator::{
async_return, create_fetch_error, ErrorResponse, NavigationMethod, NavigatorBackend,
OpenURLMode, OwnedFuture, Request, SocketMode, SuccessResponse,
@ -25,7 +23,6 @@ use std::io;
use std::io::ErrorKind;
use std::path::Path;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::warn;
@ -53,7 +50,7 @@ pub struct ExternalNavigatorBackend<F: FutureSpawner, I: NavigatorInterface> {
base_url: Url,
// Client to use for network requests
client: Option<Rc<HttpClient>>,
client: Option<Rc<reqwest::Client>>,
socket_allowed: HashSet<String>,
@ -82,11 +79,18 @@ impl<F: FutureSpawner, I: NavigatorInterface> ExternalNavigatorBackend<F, I> {
content: Rc<PlayingContent>,
interface: I,
) -> Self {
let proxy = proxy.and_then(|url| url.as_str().parse().ok());
let builder = HttpClient::builder()
.proxy(proxy)
.cookies()
.redirect_policy(RedirectPolicy::Follow);
let mut builder = reqwest::ClientBuilder::new().cookie_store(true);
if let Some(proxy) = proxy {
match Proxy::all(proxy.clone()) {
Ok(proxy) => {
builder = builder.proxy(proxy);
}
Err(e) => {
tracing::error!("Couldn't configure proxy {proxy}: {e}")
}
}
}
let client = builder.build().ok().map(Rc::new);
@ -221,44 +225,23 @@ impl<F: FutureSpawner, I: NavigatorInterface> NavigatorBackend for ExternalNavig
error: Error::FetchError("Network unavailable".to_string()),
})?;
let mut isahc_request = match request.method() {
NavigationMethod::Get => IsahcRequest::get(processed_url.to_string()),
NavigationMethod::Post => IsahcRequest::post(processed_url.to_string()),
let mut request_builder = match request.method() {
NavigationMethod::Get => client.get(processed_url.clone()),
NavigationMethod::Post => client.post(processed_url.clone()),
};
let (body_data, mime) = request.body().clone().unwrap_or_default();
if let Some(headers) = isahc_request.headers_mut() {
for (name, val) in request.headers().iter() {
headers.insert(
HeaderName::from_str(name).map_err(|e| ErrorResponse {
url: processed_url.to_string(),
error: Error::FetchError(e.to_string()),
})?,
HeaderValue::from_str(val).map_err(|e| ErrorResponse {
url: processed_url.to_string(),
error: Error::FetchError(e.to_string()),
})?,
);
}
headers.insert(
"Content-Type",
HeaderValue::from_str(&mime).map_err(|e| ErrorResponse {
url: processed_url.to_string(),
error: Error::FetchError(e.to_string()),
})?,
);
for (name, val) in request.headers().iter() {
request_builder = request_builder.header(name, val);
}
request_builder = request_builder.header("Content-Type", &mime);
let body = isahc_request.body(body_data).map_err(|e| ErrorResponse {
url: processed_url.to_string(),
error: Error::FetchError(e.to_string()),
})?;
request_builder = request_builder.body(body_data);
let response = client.send_async(body).await.map_err(|e| {
let inner = match e.kind() {
isahc::error::ErrorKind::NameResolution => {
Error::InvalidDomain(processed_url.to_string())
}
_ => Error::FetchError(e.to_string()),
let response = request_builder.send().await.map_err(|e| {
let inner = if e.is_connect() {
Error::InvalidDomain(processed_url.to_string())
} else {
Error::FetchError(e.to_string())
};
ErrorResponse {
url: processed_url.to_string(),
@ -266,27 +249,23 @@ impl<F: FutureSpawner, I: NavigatorInterface> NavigatorBackend for ExternalNavig
}
})?;
let url = if let Some(uri) = response.effective_uri() {
uri.to_string()
} else {
processed_url.into()
};
let url = response.url().to_string();
let status = response.status().as_u16();
let redirected = response.effective_uri().is_some();
let redirected = *response.url() != processed_url;
if !response.status().is_success() {
let error = Error::HttpNotOk(
format!("HTTP status is not ok, got {}", response.status()),
status,
redirected,
response.body().len().unwrap_or(0),
response.content_length().unwrap_or_default(),
);
return Err(ErrorResponse { url, error });
}
let response: Box<dyn SuccessResponse> = Box::new(Response {
url,
response_body: ResponseBody::Network(Arc::new(Mutex::new(response))),
response_body: ResponseBody::Network(Arc::new(Mutex::new(Some(response)))),
status,
redirected,
});
@ -484,6 +463,7 @@ mod tests {
use async_net::TcpListener;
use ruffle_core::socket::SocketAction::{Close, Connect, Data};
use std::net::SocketAddr;
use std::str::FromStr;
use tokio::task;
use super::*;

View File

@ -1,5 +1,4 @@
use futures::AsyncReadExt;
use isahc::{prelude::*, AsyncBody, Response as IsahcResponse};
use reqwest::Response as ReqwestResponse;
use ruffle_core::backend::navigator::{OwnedFuture, SuccessResponse};
use ruffle_core::loader::Error;
use std::sync::{Arc, Mutex};
@ -13,7 +12,7 @@ pub enum ResponseBody {
/// This has to be stored in shared ownership so that we can return
/// owned futures. A synchronous lock is used here as we do not
/// expect contention on this lock.
Network(Arc<Mutex<IsahcResponse<AsyncBody>>>),
Network(Arc<Mutex<Option<ReqwestResponse>>>),
}
pub struct Response {
@ -35,15 +34,15 @@ impl SuccessResponse for Response {
Box::pin(async move { file.map_err(|e| Error::FetchError(e.to_string())) })
}
ResponseBody::Network(response) => Box::pin(async move {
let mut body = vec![];
response
Ok(response
.lock()
.expect("working lock during fetch body read")
.copy_to(&mut body)
.take()
.expect("Body cannot already be consumed")
.bytes()
.await
.map_err(|e| Error::FetchError(e.to_string()))?;
Ok(body)
.map_err(|e| Error::FetchError(e.to_string()))?
.to_vec())
}),
}
}
@ -75,9 +74,7 @@ impl SuccessResponse for Response {
}
ResponseBody::Network(response) => {
let response = response.clone();
Box::pin(async move {
let mut buf = vec![0; 4096];
let lock = response.try_lock();
if matches!(lock, Err(std::sync::TryLockError::WouldBlock)) {
return Err(Error::FetchError(
@ -88,16 +85,14 @@ impl SuccessResponse for Response {
let result = lock
.expect("desktop network lock")
.body_mut()
.read(&mut buf)
.as_mut()
.expect("Body cannot already be consumed")
.chunk()
.await;
match result {
Ok(count) if count > 0 => {
buf.resize(count, 0);
Ok(Some(buf))
}
Ok(_) => Ok(None),
Ok(Some(bytes)) => Ok(Some(bytes.to_vec())),
Ok(None) => Ok(None),
Err(e) => Err(Error::FetchError(e.to_string())),
}
})
@ -109,18 +104,9 @@ impl SuccessResponse for Response {
match &self.response_body {
ResponseBody::File(file) => Ok(file.as_ref().map(|file| file.len() as u64).ok()),
ResponseBody::Network(response) => {
let response = response.lock().expect("no recursive locks");
let content_length = response.headers().get("Content-Length");
if let Some(len) = content_length {
Ok(Some(
len.to_str()
.map_err(|_| Error::InvalidHeaderValue)?
.parse::<u64>()?,
))
} else {
Ok(None)
}
let lock = response.lock().expect("no recursive locks");
let response = lock.as_ref().expect("Body cannot already be consumed");
Ok(response.content_length())
}
}
}