ripht_php_sapi/sapi/
server_context.rs1use std::cell::Cell;
2use std::ffi::CString;
3use std::sync::OnceLock;
4
5use crate::execution::{
6 ExecutionContext, ExecutionMessage, ExecutionResult, ResponseHeader,
7};
8use crate::sapi::ServerVarsCString;
9
10const MIN_BUFFER_SIZE: usize = 4096;
11const DEFAULT_BUFFER_SIZE: usize = 65536;
12
13#[derive(Clone, Copy)]
15struct BufferPolicy {
16 initial_cap: usize,
17 strategy: Growth,
18}
19
20#[derive(Clone, Copy)]
22enum Growth {
23 X4,
24 X2,
25 Fixed(usize),
26}
27
28static BUFFER_POLICY: OnceLock<BufferPolicy> = OnceLock::new();
29
30fn buffer_policy() -> &'static BufferPolicy {
31 BUFFER_POLICY.get_or_init(|| {
32 let initial_cap = std::env::var("SAPI_INIT_BUF")
33 .ok()
34 .and_then(|v| v.parse::<usize>().ok())
35 .filter(|&n| n >= MIN_BUFFER_SIZE)
36 .unwrap_or(DEFAULT_BUFFER_SIZE);
37
38 let strategy = match std::env::var("SAPI_BUF_GROWTH")
39 .ok()
40 .as_deref()
41 {
42 Some("x2") | Some("X2") => Growth::X2,
43 Some("fixed32k") => Growth::Fixed(32 * 1024),
44 _ => Growth::X4,
45 };
46
47 BufferPolicy {
48 initial_cap,
49 strategy,
50 }
51 })
52}
53
54type FlushCallback = Box<dyn FnMut()>;
55type OutputCallback = Box<dyn FnMut(&[u8])>;
56
57pub struct ServerContext {
65 status_code: Cell<u16>,
66 pub post_data: Vec<u8>,
67 post_position: Cell<usize>,
68 pub output_buffer: Vec<u8>,
69 pub messages: Vec<ExecutionMessage>,
70 pub vars: Option<ServerVarsCString>,
71 pub env_vars: Vec<(CString, CString)>,
72 pub ini_overrides: Vec<(CString, CString)>,
73 pub response_headers: Vec<ResponseHeader>,
74 pub output_callback: Option<OutputCallback>,
75 pub flush_callback: Option<FlushCallback>,
76 pub log_to_stderr: bool,
77}
78
79impl Default for ServerContext {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl ServerContext {
86 pub fn new() -> Self {
87 let policy = buffer_policy();
88
89 Self {
90 post_data: Vec::new(),
91 post_position: Cell::new(0),
92 output_buffer: Vec::with_capacity(policy.initial_cap),
93 status_code: Cell::new(200),
94 messages: Vec::with_capacity(8),
95 vars: None,
96 env_vars: Vec::new(),
97 ini_overrides: Vec::new(),
98 response_headers: Vec::with_capacity(16),
99 output_callback: None,
100 flush_callback: None,
101 log_to_stderr: false,
102 }
103 }
104
105 pub fn status_code(&self) -> u16 {
106 self.status_code.get()
107 }
108
109 pub fn content_type_ptr(&self) -> *const std::ffi::c_char {
110 self.vars
111 .as_ref()
112 .map(|v| v.content_type_ptr())
113 .unwrap_or(std::ptr::null())
114 }
115
116 pub fn query_string_ptr(&self) -> *mut std::ffi::c_char {
117 self.vars
118 .as_ref()
119 .map(|v| v.query_string_ptr())
120 .unwrap_or(std::ptr::null_mut())
121 }
122
123 pub fn cookie_data_ptr(&self) -> *mut std::ffi::c_char {
124 self.vars
125 .as_ref()
126 .map(|v| v.cookie_ptr())
127 .unwrap_or(std::ptr::null_mut())
128 }
129
130 pub fn request_method_ptr(&self) -> *const std::ffi::c_char {
131 self.vars
132 .as_ref()
133 .map(|v| v.request_method_ptr())
134 .unwrap_or(c"GET".as_ptr())
135 }
136
137 pub fn server_vars(&self) -> &[(CString, CString)] {
138 self.vars
139 .as_ref()
140 .map(|v| v.vars.as_slice())
141 .unwrap_or(&[])
142 }
143
144 pub fn read_post(&self, buffer: &mut [u8]) -> usize {
145 if buffer.is_empty() {
146 return 0;
147 }
148
149 let pos = self.post_position.get();
150 let remaining = self
151 .post_data
152 .len()
153 .saturating_sub(pos);
154 let to_copy = remaining.min(buffer.len());
155
156 if to_copy > 0 {
157 let end = pos + to_copy;
158 buffer[..to_copy].copy_from_slice(&self.post_data[pos..end]);
159 self.post_position.set(end);
160 }
161
162 to_copy
163 }
164
165 pub fn write_output(&mut self, data: &[u8]) -> usize {
166 if let Some(ref mut callback) = self.output_callback {
167 callback(data);
168
169 return data.len();
170 }
171
172 let actual_buffer_length = self.output_buffer.capacity();
173 let required_buffer_length = self.output_buffer.len() + data.len();
174
175 if required_buffer_length > actual_buffer_length {
176 let policy = buffer_policy();
177
178 let new_cap = match policy.strategy {
179 Growth::X4 => actual_buffer_length
180 .saturating_mul(4)
181 .max(required_buffer_length + policy.initial_cap),
182 Growth::X2 => actual_buffer_length
183 .saturating_mul(2)
184 .max(required_buffer_length + policy.initial_cap),
185 Growth::Fixed(step) => {
186 let mut cap = actual_buffer_length;
187 while cap < required_buffer_length {
188 cap = cap.saturating_add(step);
189 }
190 cap
191 }
192 };
193
194 self.output_buffer
195 .reserve(new_cap - self.output_buffer.len());
196 }
197
198 self.output_buffer
199 .extend_from_slice(data);
200 data.len()
201 }
202
203 pub fn add_header(&mut self, header: ResponseHeader) {
204 self.response_headers
205 .push(header);
206 }
207
208 pub fn set_status(&self, code: u16) {
209 self.status_code.set(code);
210 }
211
212 pub fn add_message(&mut self, message: ExecutionMessage) {
213 self.messages.push(message);
214 }
215
216 pub fn set_output_callback<F: FnMut(&[u8]) + 'static>(
217 &mut self,
218 callback: F,
219 ) {
220 self.output_callback = Some(Box::new(callback));
221 }
222
223 pub fn set_flush_callback<F: FnMut() + 'static>(&mut self, callback: F) {
224 self.flush_callback = Some(Box::new(callback));
225 }
226
227 pub fn flush(&mut self) {
228 if let Some(ref mut callback) = self.flush_callback {
229 callback();
230 }
231 }
232
233 pub fn get_env(&self, key: &[u8]) -> Option<*const std::ffi::c_char> {
234 self.env_vars
235 .iter()
236 .find(|(k, _)| k.as_bytes() == key)
237 .map(|(_, v)| v.as_ptr())
238 }
239
240 pub fn into_result(self, body: Vec<u8>) -> ExecutionResult {
241 ExecutionResult::new(
242 self.status_code.get(),
243 body,
244 self.response_headers,
245 self.messages,
246 )
247 }
248}
249
250impl From<ExecutionContext> for Box<ServerContext> {
251 fn from(ctx: ExecutionContext) -> Self {
252 let mut server_ctx = Box::new(ServerContext::new());
253
254 server_ctx.post_data = ctx.input;
255 server_ctx.log_to_stderr = ctx.log_to_stderr;
256
257 server_ctx.vars = Some(
258 ctx.server_vars
259 .into_cstring_pairs(),
260 );
261
262 server_ctx.env_vars = ctx
263 .env_vars
264 .into_iter()
265 .filter_map(|(k, v)| {
266 Some((CString::new(k).ok()?, CString::new(v).ok()?))
267 })
268 .collect();
269
270 server_ctx.ini_overrides = ctx
271 .ini_overrides
272 .into_iter()
273 .filter_map(|(k, v)| {
274 Some((CString::new(k).ok()?, CString::new(v).ok()?))
275 })
276 .collect();
277
278 server_ctx
279 }
280}