Skip to main content

playwright_rs/protocol/
response.rs

1// Response protocol object
2//
3// Represents an HTTP response from navigation operations.
4// Response objects are created by the server when Frame.goto() or similar navigation
5// methods complete successfully.
6
7use crate::error::Result;
8use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
9use serde_json::Value;
10use std::any::Any;
11use std::sync::Arc;
12
13/// TLS/SSL security details for an HTTPS response.
14///
15/// All fields are optional — the server provides what's available.
16///
17/// See: <https://playwright.dev/docs/api/class-response#response-security-details>
18#[derive(Debug, Clone)]
19#[non_exhaustive]
20pub struct SecurityDetails {
21    /// Certificate issuer name.
22    pub issuer: Option<String>,
23    /// TLS protocol version (e.g., "TLS 1.3").
24    pub protocol: Option<String>,
25    /// Certificate subject name.
26    pub subject_name: Option<String>,
27    /// Unix timestamp (seconds) when the certificate becomes valid.
28    pub valid_from: Option<f64>,
29    /// Unix timestamp (seconds) when the certificate expires.
30    pub valid_to: Option<f64>,
31}
32
33/// Remote server address (IP and port).
34///
35/// See: <https://playwright.dev/docs/api/class-response#response-server-addr>
36#[derive(Debug, Clone)]
37#[non_exhaustive]
38pub struct RemoteAddr {
39    /// Server IP address.
40    pub ip_address: String,
41    /// Server port.
42    pub port: u16,
43}
44
45/// Resource size information for a request/response pair.
46///
47/// See: <https://playwright.dev/docs/api/class-request#request-sizes>
48#[derive(Debug, Clone)]
49#[non_exhaustive]
50pub struct RequestSizes {
51    /// Size of the request body in bytes. Set to 0 if there was no body.
52    pub request_body_size: i64,
53    /// Total number of bytes from the start of the HTTP request message
54    /// until (and including) the double CRLF before the body.
55    pub request_headers_size: i64,
56    /// Size of the received response body in bytes.
57    pub response_body_size: i64,
58    /// Total number of bytes from the start of the HTTP response message
59    /// until (and including) the double CRLF before the body.
60    pub response_headers_size: i64,
61}
62
63/// A single HTTP header entry with a name and value.
64///
65/// Used by `Response::headers_array()` to return all headers preserving duplicates.
66///
67/// See: <https://playwright.dev/docs/api/class-response#response-headers-array>
68#[derive(Debug, Clone)]
69#[non_exhaustive]
70pub struct HeaderEntry {
71    /// Header name (lowercase)
72    pub name: String,
73    /// Header value
74    pub value: String,
75}
76
77/// Response represents an HTTP response from a navigation operation.
78///
79/// Response objects are not created directly - they are returned from
80/// navigation methods like page.goto() or page.reload().
81///
82/// See: <https://playwright.dev/docs/api/class-response>
83#[derive(Clone)]
84pub struct ResponseObject {
85    base: ChannelOwnerImpl,
86}
87
88impl ResponseObject {
89    /// Creates a new Response from protocol initialization
90    ///
91    /// This is called by the object factory when the server sends a `__create__` message
92    /// for a Response object.
93    pub fn new(
94        parent: Arc<dyn ChannelOwner>,
95        type_name: String,
96        guid: Arc<str>,
97        initializer: Value,
98    ) -> Result<Self> {
99        let base = ChannelOwnerImpl::new(
100            ParentOrConnection::Parent(parent),
101            type_name,
102            guid,
103            initializer,
104        );
105
106        Ok(Self { base })
107    }
108
109    /// Returns the status code of the response (e.g., 200 for a success).
110    ///
111    /// See: <https://playwright.dev/docs/api/class-response#response-status>
112    pub fn status(&self) -> u16 {
113        self.initializer()
114            .get("status")
115            .and_then(|v| v.as_u64())
116            .unwrap_or(0) as u16
117    }
118
119    /// Returns the status text of the response (e.g. usually an "OK" for a success).
120    ///
121    /// See: <https://playwright.dev/docs/api/class-response#response-status-text>
122    pub fn status_text(&self) -> &str {
123        self.initializer()
124            .get("statusText")
125            .and_then(|v| v.as_str())
126            .unwrap_or("")
127    }
128
129    /// Returns the URL of the response.
130    ///
131    /// See: <https://playwright.dev/docs/api/class-response#response-url>
132    pub fn url(&self) -> &str {
133        self.initializer()
134            .get("url")
135            .and_then(|v| v.as_str())
136            .unwrap_or("")
137    }
138
139    /// Returns the response body as bytes.
140    ///
141    /// Sends a `"body"` RPC call to the Playwright server, which returns the body
142    /// as a base64-encoded binary string.
143    ///
144    /// See: <https://playwright.dev/docs/api/class-response#response-body>
145    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), bytes_len = tracing::field::Empty))]
146    pub async fn body(&self) -> Result<Vec<u8>> {
147        use serde::Deserialize;
148
149        #[derive(Deserialize)]
150        struct BodyResponse {
151            binary: String,
152        }
153
154        let result: BodyResponse = self.channel().send("body", serde_json::json!({})).await?;
155
156        use base64::Engine;
157        let bytes = base64::engine::general_purpose::STANDARD
158            .decode(&result.binary)
159            .map_err(|e| {
160                crate::error::Error::ProtocolError(format!(
161                    "Failed to decode response body from base64: {}",
162                    e
163                ))
164            })?;
165        Ok(bytes)
166    }
167
168    /// Returns TLS/SSL security details for HTTPS connections, or `None` for HTTP.
169    ///
170    /// See: <https://playwright.dev/docs/api/class-response#response-security-details>
171    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
172    pub async fn security_details(&self) -> Result<Option<SecurityDetails>> {
173        let result: serde_json::Value = self
174            .channel()
175            .send("securityDetails", serde_json::json!({}))
176            .await?;
177
178        let value = result.get("value");
179        match value {
180            Some(v) if v.as_object().is_some_and(|obj| !obj.is_empty()) => {
181                Ok(Some(SecurityDetails {
182                    issuer: v.get("issuer").and_then(|v| v.as_str()).map(String::from),
183                    protocol: v.get("protocol").and_then(|v| v.as_str()).map(String::from),
184                    subject_name: v
185                        .get("subjectName")
186                        .and_then(|v| v.as_str())
187                        .map(String::from),
188                    valid_from: v.get("validFrom").and_then(|v| v.as_f64()),
189                    valid_to: v.get("validTo").and_then(|v| v.as_f64()),
190                }))
191            }
192            _ => Ok(None),
193        }
194    }
195
196    /// Returns the server's IP address and port for this response, or `None`.
197    ///
198    /// See: <https://playwright.dev/docs/api/class-response#response-server-addr>
199    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
200    pub async fn server_addr(&self) -> Result<Option<RemoteAddr>> {
201        let result: serde_json::Value = self
202            .channel()
203            .send("serverAddr", serde_json::json!({}))
204            .await?;
205
206        let value = result.get("value");
207        match value {
208            Some(v) if !v.is_null() => {
209                let ip_address = v
210                    .get("ipAddress")
211                    .and_then(|v| v.as_str())
212                    .unwrap_or("")
213                    .to_string();
214                let port = v.get("port").and_then(|v| v.as_u64()).unwrap_or(0) as u16;
215                Ok(Some(RemoteAddr { ip_address, port }))
216            }
217            _ => Ok(None),
218        }
219    }
220
221    /// Returns resource size information for this response.
222    ///
223    /// See: <https://playwright.dev/docs/api/class-request#request-sizes>
224    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
225    pub async fn sizes(&self) -> Result<RequestSizes> {
226        use serde::Deserialize;
227
228        #[derive(Deserialize)]
229        #[serde(rename_all = "camelCase")]
230        struct SizesRaw {
231            request_body_size: i64,
232            request_headers_size: i64,
233            response_body_size: i64,
234            response_headers_size: i64,
235        }
236
237        #[derive(Deserialize)]
238        struct RpcResult {
239            sizes: SizesRaw,
240        }
241
242        let result: RpcResult = self.channel().send("sizes", serde_json::json!({})).await?;
243
244        Ok(RequestSizes {
245            request_body_size: result.sizes.request_body_size,
246            request_headers_size: result.sizes.request_headers_size,
247            response_body_size: result.sizes.response_body_size,
248            response_headers_size: result.sizes.response_headers_size,
249        })
250    }
251
252    /// Returns the HTTP version used by this response (e.g. `"HTTP/1.1"` or `"HTTP/2.0"`).
253    ///
254    /// Sends a `"httpVersion"` RPC call to the Playwright server.
255    ///
256    /// See: <https://playwright.dev/docs/api/class-response#response-http-version>
257    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), version = tracing::field::Empty))]
258    pub async fn http_version(&self) -> Result<String> {
259        use serde::Deserialize;
260
261        #[derive(Deserialize)]
262        struct HttpVersionResponse {
263            value: String,
264        }
265
266        let result: HttpVersionResponse = self
267            .channel()
268            .send("httpVersion", serde_json::json!({}))
269            .await?;
270        Ok(result.value)
271    }
272
273    /// Returns the raw response headers as name-value pairs (preserving duplicates).
274    ///
275    /// Sends a `"rawResponseHeaders"` RPC call to the Playwright server.
276    ///
277    /// See: <https://playwright.dev/docs/api/class-response#response-headers-array>
278    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
279    pub async fn raw_headers(&self) -> Result<Vec<HeaderEntry>> {
280        use serde::Deserialize;
281
282        #[derive(Deserialize)]
283        struct RawHeadersResponse {
284            headers: Vec<HeaderEntryRaw>,
285        }
286
287        #[derive(Deserialize)]
288        struct HeaderEntryRaw {
289            name: String,
290            value: String,
291        }
292
293        let result: RawHeadersResponse = self
294            .channel()
295            .send("rawResponseHeaders", serde_json::json!({}))
296            .await?;
297
298        Ok(result
299            .headers
300            .into_iter()
301            .map(|h| HeaderEntry {
302                name: h.name,
303                value: h.value,
304            })
305            .collect())
306    }
307}
308
309impl ChannelOwner for ResponseObject {
310    fn guid(&self) -> &str {
311        self.base.guid()
312    }
313
314    fn type_name(&self) -> &str {
315        self.base.type_name()
316    }
317
318    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
319        self.base.parent()
320    }
321
322    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
323        self.base.connection()
324    }
325
326    fn initializer(&self) -> &Value {
327        self.base.initializer()
328    }
329
330    fn channel(&self) -> &crate::server::channel::Channel {
331        self.base.channel()
332    }
333
334    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
335        self.base.dispose(reason)
336    }
337
338    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
339        self.base.adopt(child)
340    }
341
342    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
343        self.base.add_child(guid, child)
344    }
345
346    fn remove_child(&self, guid: &str) {
347        self.base.remove_child(guid)
348    }
349
350    fn on_event(&self, _method: &str, _params: Value) {
351        // Response objects don't have events
352    }
353
354    fn was_collected(&self) -> bool {
355        self.base.was_collected()
356    }
357
358    fn as_any(&self) -> &dyn Any {
359        self
360    }
361}
362
363impl std::fmt::Debug for ResponseObject {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        f.debug_struct("ResponseObject")
366            .field("guid", &self.guid())
367            .finish()
368    }
369}