Skip to main content

bao_cdp_client/api/
response.rs

1//! Response — HTTP 响应句柄(Network.responseReceived 事件)。
2//!
3//! D 类 method 全部本地状态:
4//! - `url() -> &str`
5//! - `status() -> u16`
6//! - `status_text() -> &str`
7//! - `ok() -> bool`(status in [200, 300))
8//! - `headers() -> HashMap<String,String>`
9//! - `from_cache() -> bool`
10//! - `from_service_worker() -> bool`
11//! - `security_details() -> Option<&SecurityDetails>`
12//! - `remote_address() -> Option<&RemoteAddress>`
13//! - `request() -> Rc<Request>`
14//! - `body() -> Option<&str>`(本地缓存)
15//!
16//! @trace REQ-BAO-API-006 [class:Response]
17
18use std::cell::RefCell;
19use std::collections::HashMap;
20use std::rc::Rc;
21
22use serde_json::Value;
23
24use super::request::Request;
25
26/// Security details(TLS 信息)。
27#[derive(Debug, Clone, Default)]
28pub struct SecurityDetails {
29    pub protocol: String,
30    pub subject_name: String,
31    pub issuer: String,
32    pub valid_from: f64,
33    pub valid_to: f64,
34}
35
36/// Remote address(IP:port)。
37#[derive(Debug, Clone, Default)]
38pub struct RemoteAddress {
39    pub ip: String,
40    pub port: u16,
41}
42
43/// Response 本地状态。
44///
45/// @trace REQ-BAO-API-006 [class:Response]
46pub struct Response {
47    url: RefCell<String>,
48    status: RefCell<Option<u16>>,
49    status_text: RefCell<String>,
50    headers: RefCell<HashMap<String, String>>,
51    from_cache: RefCell<bool>,
52    from_service_worker: RefCell<bool>,
53    security_details: RefCell<Option<SecurityDetails>>,
54    remote_address: RefCell<Option<RemoteAddress>>,
55    request: RefCell<Option<Rc<Request>>>,
56    body: RefCell<Option<Vec<u8>>>,
57    body_text: RefCell<Option<String>>,
58    body_json: RefCell<Option<Value>>,
59}
60
61impl std::fmt::Debug for Response {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct("Response")
64            .field("url", &self.url.borrow())
65            .field("status", &self.status.borrow())
66            .field("from_cache", &self.from_cache.borrow())
67            .field("from_service_worker", &self.from_service_worker.borrow())
68            .finish()
69    }
70}
71
72impl Response {
73    /// 构造空 Response(由 responseReceived 事件填入)。
74    ///
75    /// @trace REQ-BAO-API-006 [class:Response]
76    pub fn new() -> Self {
77        Self {
78            url: RefCell::new(String::new()),
79            status: RefCell::new(None),
80            status_text: RefCell::new(String::new()),
81            headers: RefCell::new(HashMap::new()),
82            from_cache: RefCell::new(false),
83            from_service_worker: RefCell::new(false),
84            security_details: RefCell::new(None),
85            remote_address: RefCell::new(None),
86            request: RefCell::new(None),
87            body: RefCell::new(None),
88            body_text: RefCell::new(None),
89            body_json: RefCell::new(None),
90        }
91    }
92
93    /// URL。
94    ///
95    /// @trace REQ-BAO-API-006 [class:Response]
96    pub fn url(&self) -> String {
97        self.url.borrow().clone()
98    }
99
100    /// 设置 URL。
101    ///
102    /// @trace REQ-BAO-API-006 [class:Response]
103    pub fn set_url(&self, url: impl Into<String>) {
104        *self.url.borrow_mut() = url.into();
105    }
106
107    /// Status code。
108    ///
109    /// @trace REQ-BAO-API-006 [class:Response]
110    pub fn status(&self) -> Option<u16> {
111        *self.status.borrow()
112    }
113
114    /// 设置 status。
115    ///
116    /// @trace REQ-BAO-API-006 [class:Response]
117    pub fn set_status(&self, s: u16) {
118        *self.status.borrow_mut() = Some(s);
119    }
120
121    /// Status text。
122    ///
123    /// @trace REQ-BAO-API-006 [class:Response]
124    pub fn status_text(&self) -> String {
125        self.status_text.borrow().clone()
126    }
127
128    /// 设置 status text。
129    ///
130    /// @trace REQ-BAO-API-006 [class:Response]
131    pub fn set_status_text(&self, t: impl Into<String>) {
132        *self.status_text.borrow_mut() = t.into();
133    }
134
135    /// OK = status in [200, 300)。
136    ///
137    /// @trace REQ-BAO-API-006 [class:Response]
138    pub fn ok(&self) -> bool {
139        matches!(self.status.borrow().as_ref(), Some(s) if (200..300).contains(s))
140    }
141
142    /// Headers(克隆)。
143    ///
144    /// @trace REQ-BAO-API-006 [class:Response]
145    pub fn headers(&self) -> HashMap<String, String> {
146        self.headers.borrow().clone()
147    }
148
149    /// 设置 headers。
150    ///
151    /// @trace REQ-BAO-API-006 [class:Response]
152    pub fn set_headers(&self, h: HashMap<String, String>) {
153        *self.headers.borrow_mut() = h;
154    }
155
156    /// 添加单个 header。
157    ///
158    /// @trace REQ-BAO-API-006 [class:Response]
159    pub fn add_header(&self, name: impl Into<String>, value: impl Into<String>) {
160        self.headers.borrow_mut().insert(name.into(), value.into());
161    }
162
163    /// 是否来自 cache。
164    ///
165    /// @trace REQ-BAO-API-006 [class:Response]
166    pub fn from_cache(&self) -> bool {
167        *self.from_cache.borrow()
168    }
169
170    /// 设置 from_cache。
171    ///
172    /// @trace REQ-BAO-API-006 [class:Response]
173    pub fn set_from_cache(&self, v: bool) {
174        *self.from_cache.borrow_mut() = v;
175    }
176
177    /// 是否来自 service worker。
178    ///
179    /// @trace REQ-BAO-API-006 [class:Response]
180    pub fn from_service_worker(&self) -> bool {
181        *self.from_service_worker.borrow()
182    }
183
184    /// 设置 from_service_worker。
185    ///
186    /// @trace REQ-BAO-API-006 [class:Response]
187    pub fn set_from_service_worker(&self, v: bool) {
188        *self.from_service_worker.borrow_mut() = v;
189    }
190
191    /// TLS security details(本地缓存)。
192    ///
193    /// @trace REQ-BAO-API-006 [class:Response]
194    pub fn security_details(&self) -> Option<SecurityDetails> {
195        self.security_details.borrow().clone()
196    }
197
198    /// 设置 security details。
199    ///
200    /// @trace REQ-BAO-API-006 [class:Response]
201    pub fn set_security_details(&self, s: SecurityDetails) {
202        *self.security_details.borrow_mut() = Some(s);
203    }
204
205    /// Remote address。
206    ///
207    /// @trace REQ-BAO-API-006 [class:Response]
208    pub fn remote_address(&self) -> Option<RemoteAddress> {
209        self.remote_address.borrow().clone()
210    }
211
212    /// 设置 remote address。
213    ///
214    /// @trace REQ-BAO-API-006 [class:Response]
215    pub fn set_remote_address(&self, a: RemoteAddress) {
216        *self.remote_address.borrow_mut() = Some(a);
217    }
218
219    /// 关联 Request。
220    ///
221    /// @trace REQ-BAO-API-006 [class:Response]
222    pub fn request(&self) -> Option<Rc<Request>> {
223        self.request.borrow().clone()
224    }
225
226    /// 设置 request(双向链接)。
227    ///
228    /// @trace REQ-BAO-API-006 [class:Response]
229    pub fn set_request(&self, r: Rc<Request>) {
230        *self.request.borrow_mut() = Some(r);
231    }
232
233    /// body bytes(本地缓存)。
234    ///
235    /// @trace REQ-BAO-API-006 [class:Response]
236    pub fn body(&self) -> Option<Vec<u8>> {
237        self.body.borrow().clone()
238    }
239
240    /// 设置 body bytes。
241    ///
242    /// @trace REQ-BAO-API-006 [class:Response]
243    pub fn set_body(&self, b: Vec<u8>) {
244        *self.body.borrow_mut() = Some(b);
245    }
246
247    /// body text(本地缓存)。
248    ///
249    /// @trace REQ-BAO-API-006 [class:Response]
250    pub fn body_text(&self) -> Option<String> {
251        self.body_text.borrow().clone()
252    }
253
254    /// 设置 body text。
255    ///
256    /// @trace REQ-BAO-API-006 [class:Response]
257    pub fn set_body_text(&self, t: impl Into<String>) {
258        *self.body_text.borrow_mut() = Some(t.into());
259    }
260
261    /// body JSON(本地缓存)。
262    ///
263    /// @trace REQ-BAO-API-006 [class:Response]
264    pub fn body_json(&self) -> Option<Value> {
265        self.body_json.borrow().clone()
266    }
267
268    /// 设置 body JSON。
269    ///
270    /// @trace REQ-BAO-API-006 [class:Response]
271    pub fn set_body_json(&self, v: Value) {
272        *self.body_json.borrow_mut() = Some(v);
273    }
274}
275
276impl Default for Response {
277    fn default() -> Self {
278        Self::new()
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn url_round_trip() {
288        let r = Response::new();
289        assert_eq!(r.url(), "");
290        r.set_url("https://example.com");
291        assert_eq!(r.url(), "https://example.com");
292    }
293
294    #[test]
295    fn status_and_ok() {
296        let r = Response::new();
297        assert!(r.status().is_none());
298        assert!(!r.ok());
299        r.set_status(200);
300        assert_eq!(r.status(), Some(200));
301        assert!(r.ok());
302        r.set_status(404);
303        assert!(!r.ok());
304        r.set_status(299);
305        assert!(r.ok());
306        r.set_status(300);
307        assert!(!r.ok());
308    }
309
310    #[test]
311    fn status_text_round_trip() {
312        let r = Response::new();
313        assert_eq!(r.status_text(), "");
314        r.set_status_text("Not Found");
315        assert_eq!(r.status_text(), "Not Found");
316    }
317
318    #[test]
319    fn headers_round_trip() {
320        let r = Response::new();
321        assert_eq!(r.headers().len(), 0);
322        r.add_header("content-type", "text/html");
323        assert_eq!(r.headers().len(), 1);
324        assert_eq!(
325            r.headers().get("content-type"),
326            Some(&"text/html".to_string())
327        );
328    }
329
330    #[test]
331    fn from_cache_default_false() {
332        let r = Response::new();
333        assert!(!r.from_cache());
334        r.set_from_cache(true);
335        assert!(r.from_cache());
336    }
337
338    #[test]
339    fn from_service_worker_default_false() {
340        let r = Response::new();
341        assert!(!r.from_service_worker());
342        r.set_from_service_worker(true);
343        assert!(r.from_service_worker());
344    }
345
346    #[test]
347    fn security_details_round_trip() {
348        let r = Response::new();
349        assert!(r.security_details().is_none());
350        r.set_security_details(SecurityDetails {
351            protocol: "TLS 1.3".into(),
352            subject_name: "example.com".into(),
353            issuer: "Let's Encrypt".into(),
354            valid_from: 0.0,
355            valid_to: 0.0,
356        });
357        let s = r.security_details().unwrap();
358        assert_eq!(s.protocol, "TLS 1.3");
359        assert_eq!(s.subject_name, "example.com");
360    }
361
362    #[test]
363    fn remote_address_round_trip() {
364        let r = Response::new();
365        assert!(r.remote_address().is_none());
366        r.set_remote_address(RemoteAddress {
367            ip: "127.0.0.1".into(),
368            port: 8080,
369        });
370        let a = r.remote_address().unwrap();
371        assert_eq!(a.ip, "127.0.0.1");
372        assert_eq!(a.port, 8080);
373    }
374
375    #[test]
376    fn body_round_trip() {
377        let r = Response::new();
378        assert!(r.body().is_none());
379        r.set_body(vec![1, 2, 3]);
380        assert_eq!(r.body(), Some(vec![1, 2, 3]));
381    }
382
383    #[test]
384    fn body_text_round_trip() {
385        let r = Response::new();
386        assert!(r.body_text().is_none());
387        r.set_body_text("hello");
388        assert_eq!(r.body_text(), Some("hello".into()));
389    }
390
391    #[test]
392    fn body_json_round_trip() {
393        let r = Response::new();
394        assert!(r.body_json().is_none());
395        r.set_body_json(serde_json::json!({"k": "v"}));
396        assert_eq!(r.body_json().unwrap()["k"], "v");
397    }
398
399    #[test]
400    fn request_link() {
401        let r = Response::new();
402        let req = Rc::new(Request::new("REQ-1"));
403        r.set_request(req);
404        assert_eq!(r.request().unwrap().id(), "REQ-1");
405    }
406}