a3s_boot/http/
response_passthrough.rs1use super::{BootResponse, CookieOptions};
2use crate::{BootError, Result};
3use std::collections::BTreeMap;
4use std::fmt;
5use std::sync::{Arc, RwLock};
6
7#[derive(Clone, Default)]
9pub struct ResponsePassthrough {
10 state: Arc<RwLock<ResponsePassthroughState>>,
11}
12
13#[derive(Debug, Clone, Default)]
14struct ResponsePassthroughState {
15 status: Option<u16>,
16 headers: BTreeMap<String, String>,
17 appended_headers: Vec<(String, String)>,
18}
19
20impl fmt::Debug for ResponsePassthrough {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self.snapshot() {
23 Ok(state) => f
24 .debug_struct("ResponsePassthrough")
25 .field("status", &state.status)
26 .field("headers", &state.headers)
27 .field("appended_headers", &state.appended_headers)
28 .finish(),
29 Err(_) => f
30 .debug_struct("ResponsePassthrough")
31 .field("state", &"<poisoned>")
32 .finish(),
33 }
34 }
35}
36
37impl ResponsePassthrough {
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn set_status(&self, status: u16) -> Result<()> {
43 BootResponse::empty(status).validate_status()?;
44 self.write_state()?.status = Some(status);
45 Ok(())
46 }
47
48 pub fn status(&self, status: u16) -> Result<Self> {
49 self.set_status(status)?;
50 Ok(self.clone())
51 }
52
53 pub fn set_header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
54 let (name, value) = normalized_header(name, value)?;
55 self.write_state()?.headers.insert(name, value);
56 Ok(())
57 }
58
59 pub fn header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<Self> {
60 self.set_header(name, value)?;
61 Ok(self.clone())
62 }
63
64 pub fn append_header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
65 let (name, value) = normalized_header(name, value)?;
66 self.write_state()?.appended_headers.push((name, value));
67 Ok(())
68 }
69
70 pub fn set_cookie(
71 &self,
72 name: impl AsRef<str>,
73 value: impl AsRef<str>,
74 options: CookieOptions,
75 ) -> Result<()> {
76 let header = options.set_cookie_header(name.as_ref(), value.as_ref())?;
77 self.append_header("set-cookie", header)
78 }
79
80 pub fn cookie(
81 &self,
82 name: impl AsRef<str>,
83 value: impl AsRef<str>,
84 options: CookieOptions,
85 ) -> Result<Self> {
86 self.set_cookie(name, value, options)?;
87 Ok(self.clone())
88 }
89
90 pub fn delete_cookie(&self, name: impl AsRef<str>, options: CookieOptions) -> Result<()> {
91 let header = options.delete_cookie_header(name.as_ref())?;
92 self.append_header("set-cookie", header)
93 }
94
95 pub fn has_changes(&self) -> Result<bool> {
96 let state = self.snapshot()?;
97 Ok(state.status.is_some()
98 || !state.headers.is_empty()
99 || !state.appended_headers.is_empty())
100 }
101
102 pub fn apply(&self, mut response: BootResponse) -> Result<BootResponse> {
103 let state = self.snapshot()?;
104 if let Some(status) = state.status {
105 response = response.with_status(status);
106 }
107 for (name, value) in state.headers {
108 response = response.with_header(name, value);
109 }
110 for (name, value) in state.appended_headers {
111 response = response.append_header(name, value);
112 }
113 Ok(response)
114 }
115
116 fn snapshot(&self) -> Result<ResponsePassthroughState> {
117 self.state
118 .read()
119 .map_err(|_| BootError::Internal("response passthrough lock is poisoned".to_string()))
120 .map(|state| state.clone())
121 }
122
123 fn write_state(&self) -> Result<std::sync::RwLockWriteGuard<'_, ResponsePassthroughState>> {
124 self.state
125 .write()
126 .map_err(|_| BootError::Internal("response passthrough lock is poisoned".to_string()))
127 }
128}
129
130fn normalized_header(
131 name: impl Into<String>,
132 value: impl Into<String>,
133) -> Result<(String, String)> {
134 let response = BootResponse::empty(200).with_header(name, value);
135 response.validate_headers()?;
136 response
137 .headers
138 .into_iter()
139 .next()
140 .ok_or_else(|| BootError::Internal("missing normalized response header".to_string()))
141}