spire_thirtyfour/session/
mod.rs1mod document;
2mod navigation;
3
4use std::fmt;
5use std::ops::Deref;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::time::Duration;
8
9use bytes::Bytes;
10use deadpool::managed::Object;
11use futures::future::BoxFuture;
12use spire_core::backend::document::{Document, DocumentLinks};
13use spire_core::backend::{Session, StatusPolicy};
14use spire_core::http::http::header::{
15 CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, TRANSFER_ENCODING,
16};
17use spire_core::http::{Request, Response, ResponseExt};
18use spire_core::{Error, Result};
19use thirtyfour::WebDriver;
20use thirtyfour::bidi::{BiDi, BrowsingContextId};
21
22use self::navigation::{Overrides, navigate};
23use crate::backend::pool::Sessions;
24use crate::error::from_webdriver;
25
26pub struct ThirtyfourSession {
41 driver: Object<Sessions>,
42 bidi: BiDi,
43 context: BrowsingContextId,
44 page_load_timeout: Duration,
45 status_policy: StatusPolicy,
46 navigated: AtomicBool,
48}
49
50impl ThirtyfourSession {
51 pub(crate) const fn new(
52 driver: Object<Sessions>,
53 bidi: BiDi,
54 context: BrowsingContextId,
55 page_load_timeout: Duration,
56 status_policy: StatusPolicy,
57 ) -> Self {
58 Self {
59 driver,
60 bidi,
61 context,
62 page_load_timeout,
63 status_policy,
64 navigated: AtomicBool::new(false),
65 }
66 }
67
68 #[must_use]
73 pub fn driver(&self) -> WebDriver {
74 (*self.driver).clone()
75 }
76
77 #[must_use]
80 pub const fn bidi(&self) -> &BiDi {
81 &self.bidi
82 }
83
84 #[must_use]
86 pub const fn context(&self) -> &BrowsingContextId {
87 &self.context
88 }
89
90 pub async fn page_source(&self) -> Result<String> {
97 self.source().await.map_err(from_webdriver)
98 }
99}
100
101impl Deref for ThirtyfourSession {
102 type Target = WebDriver;
103
104 fn deref(&self) -> &Self::Target {
105 &self.driver
106 }
107}
108
109impl fmt::Debug for ThirtyfourSession {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.debug_struct("ThirtyfourSession")
112 .field("context", &self.context)
113 .finish_non_exhaustive()
114 }
115}
116
117impl Session for ThirtyfourSession {
118 async fn fetch(&self, request: &Request) -> Result<Response> {
126 let overrides = Overrides {
127 method: request.method().clone(),
128 headers: request.headers().clone(),
129 body: request.body().to_vec(),
130 };
131 let navigation = navigate(
132 &self.bidi,
133 &self.context,
134 request.uri(),
135 &overrides,
136 self.page_load_timeout,
137 )
138 .await?;
139
140 let mut head = Response::new(());
141 *head.status_mut() = navigation.status();
142 let headers = head.headers_mut();
143 *headers = navigation.headers().clone();
144 for header in [CONTENT_LENGTH, CONTENT_ENCODING, TRANSFER_ENCODING] {
145 headers.remove(header);
146 }
147 let content_type = HeaderValue::from_static("text/html; charset=utf-8");
148 headers.insert(CONTENT_TYPE, content_type);
149 head.set_final_uri(navigation.uri().clone());
150 self.navigated.store(true, Ordering::Release);
151 let head = self.status_policy.check(head)?;
152 let body = Bytes::from(self.page_source().await?);
153 Ok(head.map(|()| body))
154 }
155
156 fn document(&self) -> Option<&dyn Document> {
158 Some(self)
159 }
160}
161
162impl Document for ThirtyfourSession {
163 fn links<'a>(&'a self, selector: Option<&'a str>) -> BoxFuture<'a, Result<DocumentLinks>> {
164 Box::pin(async move {
165 if !self.navigated.load(Ordering::Acquire) {
166 return Err(Error::worker("the request was not fetched yet"));
167 }
168 self::document::capture(self, selector).await
169 })
170 }
171}