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