use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use async_std::task::sleep; use base64::prelude::BASE64_STANDARD; use base64::Engine; use chromiumoxide::cdp::browser_protocol::fetch::{ self, ContinueRequestParams, EventRequestPaused, FailRequestParams, FulfillRequestParams, }; use chromiumoxide::cdp::browser_protocol::network::{ self, ErrorReason, EventRequestWillBeSent, ResourceType, }; use chromiumoxide::Page; use futures::{select, StreamExt}; use chromiumoxide::browser::{Browser, BrowserConfig}; const CONTENT: &str = "

TEST

"; const TARGET: &str = "http://google.com/"; #[async_std::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); // Spawn browser let (mut browser, mut handler) = Browser::launch( BrowserConfig::builder() .enable_request_intercept() .disable_cache() .request_timeout(Duration::from_secs(1)) .build()?, ) .await?; let browser_handle = async_std::task::spawn(async move { while let Some(h) = handler.next().await { if h.is_err() { break; } } }); // Setup request interception let page = Arc::new(browser.new_page("about:blank").await?); let mut request_will_be_sent = page .event_listener::() .await .unwrap() .fuse(); let mut request_paused = page .event_listener::() .await .unwrap() .fuse(); let intercept_page = page.clone(); let intercept_handle = async_std::task::spawn(async move { let mut resolutions: HashMap = HashMap::new(); loop { select! { event = request_paused.next() => { if let Some(event) = event { // Responses if event.response_status_code.is_some() { forward(&intercept_page, &event.request_id).await; continue; } if let Some(network_id) = event.network_id.as_ref().map(|id| id.as_network_id()) { let resolution = resolutions.entry(network_id.clone()).or_insert(InterceptResolution::new()); resolution.request_id = Some(event.request_id.clone()); if event.request.url == TARGET { resolution.action = InterceptAction::Fullfill; } println!("paused: {resolution:?}, network: {network_id:?}"); resolve(&intercept_page, &network_id, &mut resolutions).await; } } }, event = request_will_be_sent.next() => { if let Some(event) = event { let resolution = resolutions.entry(event.request_id.clone()).or_insert(InterceptResolution::new()); let action = if is_navigation(&event) { InterceptAction::Abort } else { InterceptAction::Forward }; resolution.action = action; println!("sent: {resolution:?}"); resolve(&intercept_page, &event.request_id, &mut resolutions).await; } }, complete => break, } } println!("done"); }); sleep(Duration::from_secs(5)).await; // Navigate to target page.goto("http://google.com").await?; let content = page.content().await?; println!("Content: {:?}", content); browser.close().await?; browser_handle.await; intercept_handle.await; Ok(()) } #[derive(Debug)] enum InterceptAction { Forward, Abort, Fullfill, None, } #[derive(Debug)] struct InterceptResolution { action: InterceptAction, request_id: Option, } impl InterceptResolution { pub fn new() -> Self { Self { action: InterceptAction::None, request_id: None, } } } trait RequestIdExt { fn as_network_id(&self) -> network::RequestId; } impl RequestIdExt for network::RequestId { fn as_network_id(&self) -> network::RequestId { network::RequestId::new(self.inner().clone()) } } fn is_navigation(event: &EventRequestWillBeSent) -> bool { if event.request_id.inner() == event.loader_id.inner() && event .r#type .as_ref() .map(|t| *t == ResourceType::Document) .unwrap_or(false) { return true; } false } async fn resolve( page: &Page, network_id: &network::RequestId, resolutions: &mut HashMap, ) { if let Some(resolution) = resolutions.get(network_id) { if let Some(request_id) = &resolution.request_id { match resolution.action { InterceptAction::Forward => { forward(page, request_id).await; resolutions.remove(network_id); } InterceptAction::Abort => { abort(page, request_id).await; resolutions.remove(network_id); } InterceptAction::Fullfill => { fullfill(page, request_id).await; resolutions.remove(network_id); } InterceptAction::None => (), // Processed pausd but not will be sent } } } } async fn forward(page: &Page, request_id: &fetch::RequestId) { println!("Request {request_id:?} forwarded"); if let Err(e) = page .execute(ContinueRequestParams::new(request_id.clone())) .await { println!("Failed to forward request: {e}"); } } async fn abort(page: &Page, request_id: &fetch::RequestId) { println!("Request {request_id:?} aborted"); if let Err(e) = page .execute(FailRequestParams::new( request_id.clone(), ErrorReason::Aborted, )) .await { println!("Failed to abort request: {e}"); } } async fn fullfill(page: &Page, request_id: &fetch::RequestId) { println!("Request {request_id:?} fullfilled"); if let Err(e) = page .execute( FulfillRequestParams::builder() .request_id(request_id.clone()) .body(BASE64_STANDARD.encode(CONTENT)) .response_code(200) .build() .unwrap(), ) .await { println!("Failed to fullfill request: {e}"); } }