obscura serve --user-agent <ua> only logged the value via tracing::info! and never passed it to the CDP server, so every navigation kept using the default Chrome UA string and httpbin.org/user-agent reported the default rather than the override. Plumb the option end-to-end: - BrowserContext::with_full_options(id, proxy, stealth, user_agent) initializes both BrowserContext::user_agent and the underlying ObscuraHttpClient::user_agent RwLock at construction time so the first navigation already sees the override. - CdpContext::new_with_full_options forwards the UA to the default BrowserContext. - obscura_cdp::start_with_full_options accepts the UA and passes it through cdp_processor. - main.rs Serve arm calls start_with_full_options with the parsed UA. - run_multi_worker_serve forwards --user-agent <ua> when spawning worker subprocesses so multi-worker mode honors the flag too. Existing two-arg start_with_options and three-arg with_options stay as thin shims (call the full variant with None) so older callers and the public crate API remain source-compatible. Three new unit tests in obscura-browser cover the propagation, the Chrome default fallback, and the legacy with_options path. Generated by Ora Studio Vibe coded by ousamabenyounes Co-authored-by: Ora Studio <noreply@oratelecom.net>
This commit is contained in:
@@ -30,6 +30,15 @@ impl BrowserContext {
|
||||
}
|
||||
|
||||
pub fn with_options(id: String, proxy_url: Option<String>, stealth: bool) -> Self {
|
||||
Self::with_full_options(id, proxy_url, stealth, None)
|
||||
}
|
||||
|
||||
pub fn with_full_options(
|
||||
id: String,
|
||||
proxy_url: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) -> Self {
|
||||
let cookie_jar = Arc::new(CookieJar::new());
|
||||
let mut client = ObscuraHttpClient::with_options(
|
||||
cookie_jar.clone(),
|
||||
@@ -38,12 +47,21 @@ impl BrowserContext {
|
||||
if stealth {
|
||||
client.block_trackers = true;
|
||||
}
|
||||
let resolved_ua = user_agent.unwrap_or_else(|| {
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36".to_string()
|
||||
});
|
||||
// Sync the http client's UA at construction so navigation requests pick it
|
||||
// up before any async setup runs. The lock has no other holders here, so
|
||||
// try_write always succeeds; we fall back silently if it ever fails.
|
||||
if let Ok(mut guard) = client.user_agent.try_write() {
|
||||
*guard = resolved_ua.clone();
|
||||
}
|
||||
let http_client = Arc::new(client);
|
||||
BrowserContext {
|
||||
id,
|
||||
cookie_jar,
|
||||
http_client,
|
||||
user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36".to_string(),
|
||||
user_agent: resolved_ua,
|
||||
proxy_url,
|
||||
robots_cache: Arc::new(RobotsCache::new()),
|
||||
obey_robots: false,
|
||||
@@ -55,3 +73,41 @@ impl BrowserContext {
|
||||
Self::with_options(id, proxy_url, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn with_full_options_propagates_user_agent_to_http_client() {
|
||||
let ctx = BrowserContext::with_full_options(
|
||||
"test".to_string(),
|
||||
None,
|
||||
false,
|
||||
Some("Custom-UA/1.0".to_string()),
|
||||
);
|
||||
assert_eq!(ctx.user_agent, "Custom-UA/1.0");
|
||||
let client_ua = ctx.http_client.user_agent.read().await.clone();
|
||||
assert_eq!(client_ua, "Custom-UA/1.0");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn with_full_options_falls_back_to_chrome_default() {
|
||||
let ctx = BrowserContext::with_full_options(
|
||||
"test".to_string(),
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
assert!(ctx.user_agent.contains("Chrome"));
|
||||
let client_ua = ctx.http_client.user_agent.read().await.clone();
|
||||
assert!(client_ua.contains("Chrome"));
|
||||
assert_eq!(ctx.user_agent, client_ua);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn with_options_keeps_default_user_agent() {
|
||||
let ctx = BrowserContext::with_options("test".to_string(), None, false);
|
||||
assert!(ctx.user_agent.contains("Chrome"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,19 @@ impl CdpContext {
|
||||
}
|
||||
|
||||
pub fn new_with_options(proxy: Option<String>, stealth: bool) -> Self {
|
||||
let default_context = Arc::new(BrowserContext::with_options(
|
||||
Self::new_with_full_options(proxy, stealth, None)
|
||||
}
|
||||
|
||||
pub fn new_with_full_options(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) -> Self {
|
||||
let default_context = Arc::new(BrowserContext::with_full_options(
|
||||
"default".to_string(),
|
||||
proxy,
|
||||
stealth,
|
||||
user_agent,
|
||||
));
|
||||
CdpContext {
|
||||
pages: Vec::new(),
|
||||
|
||||
@@ -3,4 +3,4 @@ pub mod dispatch;
|
||||
pub mod types;
|
||||
pub mod domains;
|
||||
|
||||
pub use server::{start, start_with_options};
|
||||
pub use server::{start, start_with_full_options, start_with_options};
|
||||
|
||||
@@ -31,6 +31,15 @@ pub async fn start_with_options(
|
||||
port: u16,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_full_options(port, proxy, stealth, None).await
|
||||
}
|
||||
|
||||
pub async fn start_with_full_options(
|
||||
port: u16,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
@@ -46,7 +55,7 @@ pub async fn start_with_options(
|
||||
.run_until(async {
|
||||
let (msg_tx, msg_rx) = mpsc::unbounded_channel::<ServerMessage>();
|
||||
|
||||
let processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth));
|
||||
let processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth, user_agent));
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
@@ -72,8 +81,9 @@ async fn cdp_processor(
|
||||
mut rx: mpsc::UnboundedReceiver<ServerMessage>,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) {
|
||||
let mut ctx = CdpContext::new_with_options(proxy, stealth);
|
||||
let mut ctx = CdpContext::new_with_full_options(proxy, stealth, user_agent);
|
||||
let (itx, irx) = mpsc::unbounded_channel::<obscura_js::ops::InterceptedRequest>();
|
||||
ctx.intercept_tx = Some(itx);
|
||||
let mut intercept_rx: Option<mpsc::UnboundedReceiver<obscura_js::ops::InterceptedRequest>> = Some(irx);
|
||||
|
||||
@@ -152,9 +152,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
if workers > 1 {
|
||||
tracing::info!("{} worker processes", workers);
|
||||
run_multi_worker_serve(port, workers, proxy, stealth).await?;
|
||||
run_multi_worker_serve(port, workers, proxy, stealth, user_agent).await?;
|
||||
} else {
|
||||
obscura_cdp::start_with_options(port, proxy, stealth).await?;
|
||||
obscura_cdp::start_with_full_options(port, proxy, stealth, user_agent).await?;
|
||||
}
|
||||
}
|
||||
Some(Command::Fetch { url, dump, selector, wait, timeout, wait_until, user_agent, stealth, eval, quiet }) => {
|
||||
@@ -180,6 +180,7 @@ async fn run_multi_worker_serve(
|
||||
workers: u16,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::AsyncWriteExt as _;
|
||||
@@ -194,6 +195,9 @@ async fn run_multi_worker_serve(
|
||||
if let Some(ref p) = proxy {
|
||||
cmd.arg("--proxy").arg(p);
|
||||
}
|
||||
if let Some(ref ua) = user_agent {
|
||||
cmd.arg("--user-agent").arg(ua);
|
||||
}
|
||||
if stealth {
|
||||
cmd.arg("--stealth");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user