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::request::Request;
6use crate::types::Headers;
7use base64::Engine;
8use parking_lot::Mutex;
9use serde_json::{json, Value};
10use std::sync::Arc;
11
12/// An HTTP response captured for a page.
13#[derive(Clone)]
14pub struct Response {
15    inner: Arc<ResponseInner>,
16}
17
18struct ResponseInner {
19    url: String,
20    status: u16,
21    status_text: String,
22    headers: Headers,
23    request_id: String,
24    from_cache: bool,
25    from_service_worker: bool,
26    session: Arc<CdpSession>,
27    body: Mutex<Option<Arc<[u8]>>>,
28    request: Request,
29}
30
31impl Response {
32    #[allow(clippy::too_many_arguments)]
33    pub(crate) fn new(
34        url: String,
35        status: u16,
36        status_text: String,
37        headers: Headers,
38        request_id: String,
39        from_cache: bool,
40        from_service_worker: bool,
41        session: Arc<CdpSession>,
42        request: Request,
43    ) -> Self {
44        Self {
45            inner: Arc::new(ResponseInner {
46                url,
47                status,
48                status_text,
49                headers,
50                request_id,
51                from_cache,
52                from_service_worker,
53                session,
54                body: Mutex::new(None),
55                request,
56            }),
57        }
58    }
59
60    pub fn url(&self) -> &str {
61        &self.inner.url
62    }
63
64    pub fn status(&self) -> u16 {
65        self.inner.status
66    }
67
68    pub fn status_text(&self) -> &str {
69        &self.inner.status_text
70    }
71
72    pub fn ok(&self) -> bool {
73        (200..300).contains(&self.inner.status)
74    }
75
76    pub fn headers(&self) -> &Headers {
77        &self.inner.headers
78    }
79
80    pub fn from_cache(&self) -> bool {
81        self.inner.from_cache
82    }
83
84    pub fn from_service_worker(&self) -> bool {
85        self.inner.from_service_worker
86    }
87
88    pub fn request(&self) -> Request {
89        self.inner.request.clone()
90    }
91
92    /// Fetch the response body (cached after the first call).
93    pub async fn body(&self) -> Result<Vec<u8>> {
94        if let Some(b) = self.inner.body.lock().clone() {
95            return Ok(b.to_vec());
96        }
97        let v = self
98            .inner
99            .session
100            .send(
101                "Network.getResponseBody",
102                json!({ "requestId": self.inner.request_id }),
103            )
104            .await?;
105        let body_str = v.get("body").and_then(|x| x.as_str()).unwrap_or("");
106        let encoded = v.get("base64Encoded").and_then(|x| x.as_bool()).unwrap_or(false);
107        let bytes = if encoded {
108            base64::engine::general_purpose::STANDARD
109                .decode(body_str)
110                .map_err(|e| Error::ProtocolError(format!("response body base64 decode: {e}")))?
111        } else {
112            body_str.as_bytes().to_vec()
113        };
114        *self.inner.body.lock() = Some(Arc::from(bytes.as_slice()));
115        Ok(bytes)
116    }
117
118    pub async fn text(&self) -> Result<String> {
119        let bytes = self.body().await?;
120        Ok(String::from_utf8_lossy(&bytes).into_owned())
121    }
122
123    pub async fn json(&self) -> Result<Value> {
124        let text = self.text().await?;
125        serde_json::from_str(&text).map_err(Error::from)
126    }
127}