Skip to main content

playwright_cdp/
response.rs

1//! `Response` — an HTTP response observed via the CDP `Network` domain.
2
3use crate::cdp::session::CdpSession;
4use crate::error::{Error, Result};
5use crate::network::{NetworkStore, SecurityDetails, ServerAddr, Sizes, Timing};
6use crate::request::Request;
7use crate::types::Headers;
8use base64::Engine;
9use parking_lot::Mutex;
10use serde_json::{json, Value};
11use std::sync::Arc;
12
13/// An HTTP response captured for a page.
14#[derive(Clone)]
15pub struct Response {
16    inner: Arc<ResponseInner>,
17}
18
19struct ResponseInner {
20    url: String,
21    status: u16,
22    status_text: String,
23    headers: Headers,
24    request_id: String,
25    from_cache: bool,
26    from_service_worker: bool,
27    session: Arc<CdpSession>,
28    body: Mutex<Option<Arc<[u8]>>>,
29    request: Request,
30}
31
32impl Response {
33    #[allow(clippy::too_many_arguments)]
34    pub(crate) fn new(
35        url: String,
36        status: u16,
37        status_text: String,
38        headers: Headers,
39        request_id: String,
40        from_cache: bool,
41        from_service_worker: bool,
42        session: Arc<CdpSession>,
43        request: Request,
44    ) -> Self {
45        Self {
46            inner: Arc::new(ResponseInner {
47                url,
48                status,
49                status_text,
50                headers,
51                request_id,
52                from_cache,
53                from_service_worker,
54                session,
55                body: Mutex::new(None),
56                request,
57            }),
58        }
59    }
60
61    pub fn url(&self) -> &str {
62        &self.inner.url
63    }
64
65    pub fn status(&self) -> u16 {
66        self.inner.status
67    }
68
69    pub fn status_text(&self) -> &str {
70        &self.inner.status_text
71    }
72
73    pub fn ok(&self) -> bool {
74        (200..300).contains(&self.inner.status)
75    }
76
77    pub fn headers(&self) -> &Headers {
78        &self.inner.headers
79    }
80
81    pub fn from_cache(&self) -> bool {
82        self.inner.from_cache
83    }
84
85    pub fn from_service_worker(&self) -> bool {
86        self.inner.from_service_worker
87    }
88
89    pub fn request(&self) -> Request {
90        self.inner.request.clone()
91    }
92
93    /// HTTP / protocol version reported by the server (e.g. "http/1.1",
94    /// "h2"), derived from `Network.Response.protocol`. Empty string when not
95    /// captured.
96    pub fn http_version(&self) -> String {
97        self.store()
98            .and_then(|s| s.http_version(&self.inner.request_id))
99            .unwrap_or_default()
100    }
101
102    /// TLS/SSL security details for the connection, if captured.
103    pub fn security_details(&self) -> Option<SecurityDetails> {
104        self.store()
105            .and_then(|s| s.security_details(&self.inner.request_id))
106    }
107
108    /// The remote server address this response came from, if captured.
109    pub fn server_addr(&self) -> Option<ServerAddr> {
110        self.store()
111            .and_then(|s| s.server_addr(&self.inner.request_id))
112    }
113
114    /// Transferred size information, if `Network.loadingFinished` was received.
115    pub fn sizes(&self) -> Option<Sizes> {
116        self.store()
117            .and_then(|s| s.sizes(&self.inner.request_id))
118    }
119
120    /// Timing information for this response, if captured.
121    pub fn timing(&self) -> Option<Timing> {
122        self.store()
123            .and_then(|s| s.timing(&self.inner.request_id))
124    }
125
126    /// All raw response headers (original case). Falls back to the lowercased
127    /// `headers()` map when raw headers were not captured.
128    pub fn all_headers(&self) -> Headers {
129        self.store()
130            .and_then(|s| s.raw_response_headers(&self.inner.request_id))
131            .unwrap_or_else(|| self.inner.headers.clone())
132    }
133
134    /// Resolves successfully once `Network.loadingFinished` was observed for
135    /// this response; otherwise returns an error describing why.
136    pub fn finished(&self) -> Result<()> {
137        if let Some(store) = self.store() {
138            if store.finished(&self.inner.request_id) {
139                return Ok(());
140            }
141            // Distinguish a known failure from a still-pending load.
142            if let Some(text) = store.failure(&self.inner.request_id) {
143                return Err(Error::ProtocolError(format!(
144                    "request failed: {text}"
145                )));
146            }
147        }
148        Err(Error::ProtocolError(
149            "request has not finished loading".to_string(),
150        ))
151    }
152
153    fn store(&self) -> Option<Arc<NetworkStore>> {
154        self.inner.request.store_weak()
155    }
156
157    /// Fetch the response body (cached after the first call).
158    pub async fn body(&self) -> Result<Vec<u8>> {
159        if let Some(b) = self.inner.body.lock().clone() {
160            return Ok(b.to_vec());
161        }
162        let v = self
163            .inner
164            .session
165            .send(
166                "Network.getResponseBody",
167                json!({ "requestId": self.inner.request_id }),
168            )
169            .await?;
170        let body_str = v.get("body").and_then(|x| x.as_str()).unwrap_or("");
171        let encoded = v.get("base64Encoded").and_then(|x| x.as_bool()).unwrap_or(false);
172        let bytes = if encoded {
173            base64::engine::general_purpose::STANDARD
174                .decode(body_str)
175                .map_err(|e| Error::ProtocolError(format!("response body base64 decode: {e}")))?
176        } else {
177            body_str.as_bytes().to_vec()
178        };
179        *self.inner.body.lock() = Some(Arc::from(bytes.as_slice()));
180        Ok(bytes)
181    }
182
183    pub async fn text(&self) -> Result<String> {
184        let bytes = self.body().await?;
185        Ok(String::from_utf8_lossy(&bytes).into_owned())
186    }
187
188    pub async fn json(&self) -> Result<Value> {
189        let text = self.text().await?;
190        serde_json::from_str(&text).map_err(Error::from)
191    }
192}