varnish_sys/vcl/ctx.rs
1//! Expose the Varnish context [`vrt_ctx`] as a Rust object
2//!
3use std::ffi::{c_int, c_uint, c_void, CStr};
4
5use crate::ffi;
6use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
7use crate::vcl::{subroutine::Subroutine, HttpHeaders, LogTag, TestWS, VclError, Workspace};
8
9/// VCL context
10///
11/// A mutable reference to this structure is always passed to vmod functions and provides access to
12/// the available HTTP objects, as well as the workspace.
13///
14/// This struct is a pure Rust structure, mirroring some of the C fields, so you should always use
15/// the provided methods to interact with them. If they are not enough, the `raw` field is actually
16/// the C original pointer that can be used to directly, and unsafely, act on the structure.
17///
18/// Which `http_*` are present will depend on which VCL sub routine the function is called from.
19///
20/// ``` rust
21/// # mod varnish { pub use varnish_sys::vcl; }
22/// use varnish::vcl::Ctx;
23///
24/// fn foo(ctx: &Ctx) {
25/// if let Some(ref req) = ctx.http_req {
26/// for (name, value) in req {
27/// println!("header {name} has value {value:?}");
28/// }
29/// }
30/// }
31/// ```
32#[derive(Debug)]
33pub struct Ctx<'a> {
34 pub raw: &'a mut vrt_ctx,
35 pub http_req: Option<HttpHeaders<'a>>,
36 pub http_req_top: Option<HttpHeaders<'a>>,
37 pub http_resp: Option<HttpHeaders<'a>>,
38 pub http_bereq: Option<HttpHeaders<'a>>,
39 pub http_beresp: Option<HttpHeaders<'a>>,
40 pub ws: Workspace<'a>,
41
42 req: Option<Req<'a>>,
43}
44
45impl<'a> Ctx<'a> {
46 /// Wrap a raw pointer into an object we can use.
47 ///
48 /// The pointer must be non-null, and the magic must match
49 pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
50 Self::from_ref(
51 ptr.cast_mut()
52 .as_mut()
53 .expect("vrt_ctx pointer must not be null"),
54 )
55 }
56
57 /// Instantiate from a mutable reference to a [`vrt_ctx`].
58 pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
59 assert_eq!(raw.magic, VRT_CTX_MAGIC);
60 Self {
61 http_req: HttpHeaders::from_ptr(raw.http_req),
62 http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
63 http_resp: HttpHeaders::from_ptr(raw.http_resp),
64 http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
65 http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
66 ws: Workspace::from_ptr(raw.ws),
67 req: unsafe { Req::from_ptr(raw.req) },
68 raw,
69 }
70 }
71
72 /// Log an error message and fail the current VSL task.
73 ///
74 /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
75 /// and will return a synthetic error to the client.
76 pub fn fail(&mut self, msg: impl Into<VclError>) {
77 let msg = msg.into();
78 let msg = msg.as_str();
79 unsafe {
80 VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
81 }
82 }
83
84 /// Log a message, attached to the current context
85 pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
86 unsafe {
87 let vsl = self.raw.vsl;
88 if vsl.is_null() {
89 log(tag, msg);
90 } else {
91 let msg = ffi::txt::from_str(msg.as_ref());
92 ffi::VSLbt(vsl, tag, msg);
93 }
94 }
95 }
96
97 /// Return the name of the listener socket that received the current request.
98 ///
99 /// This corresponds to the VCL variable `local.socket` and returns the `-a` socket
100 /// name (e.g., `"a0"`, `"http-80"`). Returns an `Err` in backend context where the
101 /// session isn't available, or if the name is non-UTF-8.
102 pub fn local_socket(&self) -> Result<&'a str, VclError> {
103 // we're breaking abstraction here, but the other ways are to just reimplement the
104 // whole logic in rust (which is admittedly short), or to let the user crash
105 if self.raw.req.is_null() && self.raw.bo.is_null() {
106 return Err("local.socket isn't available in this context".into());
107 }
108 let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
109 let cstr = <&CStr>::from(raw);
110 Ok(cstr.to_str()?)
111 }
112
113 /// Return the address of the local endpoint that received the current request.
114 ///
115 /// This corresponds to the VCL variable `local.endpoint` and returns the address
116 /// string (e.g., `"127.0.0.1:8080"`, `"/var/run/varnish.sock"`). Returns an `Err` in
117 /// backend context where the session isn't available, or if the value is non-UTF-8.
118 // same notes as for local_socket
119 pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
120 if self.raw.req.is_null() && self.raw.bo.is_null() {
121 return Err("local.endpoint isn't available in this context".into());
122 }
123 let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
124 let cstr = <&CStr>::from(raw);
125 Ok(cstr.to_str()?)
126 }
127
128 /// Call a VCL subroutine.
129 ///
130 /// Returns `Ok(true)` if the request was handled after the call, `Ok(false)` otherwise.
131 /// Returns `Err` if the subroutine cannot be called in the current context (e.g. wrong VCL
132 /// state or incompatible subroutine type).
133 /// If `Ok(true)` was returned, no other subroutine can be called, and doing so will result
134 /// in a VCL error.
135 pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
136 self.check_call_sub(sub)?;
137 unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
138 Ok(self.is_handled())
139 }
140
141 /// Check whether a VCL subroutine can be called in the current context.
142 ///
143 /// Returns `Ok(())` if the call is valid, or `Err` with the reason otherwise.
144 pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
145 let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
146 if result.0.is_null() {
147 Ok(())
148 } else {
149 let msg = unsafe { CStr::from_ptr(result.0) }
150 .to_string_lossy()
151 .into_owned();
152 Err(VclError::new(msg))
153 }
154 }
155
156 /// Returns `true` if the current request has already been handled.
157 /// If `true`, no other subroutine can be called, and doing so will result
158 /// in a VCL error.
159 pub fn is_handled(&self) -> bool {
160 unsafe { VRT_handled(self.raw) != 0 }
161 }
162
163 /// Retrieve the cached request body as a list of byte slices.
164 ///
165 /// Returns slices pointing into the workspace; each slice is a contiguous chunk of the body.
166 /// Fails if the body has not been cached (i.e. `std.cache_req_body()` was not called in VCL
167 /// before this subroutine ran).
168 pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
169 unsafe extern "C" fn chunk_collector(
170 priv_: *mut c_void,
171 _flush: c_uint,
172 ptr: *const c_void,
173 len: isize,
174 ) -> c_int {
175 let v = priv_
176 .cast::<Vec<&[u8]>>()
177 .as_mut()
178 .expect("cached_req_body callback priv pointer must not be null");
179 let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
180 v.push(buf);
181 0
182 }
183
184 let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
185 unsafe {
186 if req.req_body_status != ffi::BS_CACHED.as_ptr() {
187 return Err("request body hasn't been previously cached".into());
188 }
189 }
190 let mut v: Box<Vec<&'a [u8]>> = Box::default();
191 let p: *mut Vec<&'a [u8]> = &raw mut *v;
192 match unsafe {
193 ffi::VRB_Iterate(
194 req.wrk,
195 req.vsl.as_mut_ptr(),
196 req,
197 Some(chunk_collector),
198 p.cast::<c_void>(),
199 )
200 } {
201 0 => Ok(*v),
202 _ => Err("req.body iteration failed".into()),
203 }
204 }
205
206 /// Return a shared reference to the client request object, if present.
207 ///
208 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
209 pub fn req(&self) -> Option<&Req<'_>> {
210 self.req.as_ref()
211 }
212
213 /// Return a mutable reference to the client request object, if present.
214 ///
215 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
216 pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
217 self.req.as_mut()
218 }
219}
220
221/// Rust proxy for the C `req` struct.
222/// Its methods provide getters and setters for various fields that control how the client request
223/// is processed by Varnish.
224#[derive(Debug)]
225pub struct Req<'a> {
226 raw: &'a mut ffi::req,
227}
228
229impl Req<'_> {
230 /// Wrap a raw pointer into an object we can use.
231 pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
232 Some(Req { raw: p.as_mut()? })
233 }
234
235 /// Return whether this request bypasses the cache lookup and is always treated as a miss.
236 ///
237 /// Equivalent to `req.hash_always_miss` in VCL.
238 pub fn hash_always_miss(&self) -> bool {
239 self.raw.hash_always_miss() == 1
240 }
241
242 /// Force this request to be treated as a cache miss, skipping any existing cached object.
243 ///
244 /// Equivalent to setting `req.hash_always_miss` in VCL.
245 pub fn set_hash_always_miss(&mut self, val: bool) {
246 self.raw.set_hash_always_miss(val.into());
247 }
248
249 /// Return whether this request ignores busy (locked) cache objects and fetches from the backend instead of waiting.
250 ///
251 /// Equivalent to `req.hash_ignore_busy` in VCL.
252 pub fn hash_ignore_busy(&self) -> bool {
253 self.raw.hash_ignore_busy() == 1
254 }
255
256 /// Make this request skip waiting on busy cache objects and go straight to the backend.
257 ///
258 /// Equivalent to setting `req.hash_ignore_busy` in VCL.
259 pub fn set_hash_ignore_busy(&mut self, val: bool) {
260 self.raw.set_hash_ignore_busy(val.into());
261 }
262
263 /// Return whether this request ignores `Vary` headers during cache lookup.
264 ///
265 /// Equivalent to `req.hash_ignore_vary` in VCL.
266 pub fn hash_ignore_vary(&self) -> bool {
267 self.raw.hash_ignore_vary() == 1
268 }
269
270 /// Make this request ignore `Vary` headers during cache lookup, collapsing all variants into one cache key.
271 ///
272 /// Equivalent to setting `req.hash_ignore_vary` in VCL.
273 pub fn set_hash_ignore_vary(&mut self, val: bool) {
274 self.raw.set_hash_ignore_vary(val.into());
275 }
276}
277
278/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
279///
280/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
281/// used elsewhere.
282#[derive(Debug)]
283pub struct TestCtx {
284 vrt_ctx: vrt_ctx,
285 test_ws: TestWS,
286}
287
288impl TestCtx {
289 /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
290 pub fn new(sz: usize) -> Self {
291 let mut test_ctx = Self {
292 vrt_ctx: vrt_ctx {
293 magic: VRT_CTX_MAGIC,
294 ..vrt_ctx::default()
295 },
296 test_ws: TestWS::new(sz),
297 };
298 test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
299 test_ctx
300 }
301
302 /// Return a [`Ctx`] wrapping this test context, for use in unit tests.
303 pub fn ctx(&mut self) -> Ctx<'_> {
304 Ctx::from_ref(&mut self.vrt_ctx)
305 }
306}
307
308/// Log a message outside of a request context using a VSL tag.
309///
310/// Useful in event handlers or other places where no [`Ctx`] is available.
311pub fn log(tag: LogTag, msg: impl AsRef<str>) {
312 let msg = msg.as_ref();
313 unsafe {
314 let vxids = ffi::vxids::default();
315 ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn ctx_test() {
325 let mut test_ctx = TestCtx::new(100);
326 test_ctx.ctx();
327 }
328}
329
330/// This is an unsafe struct that holds the per-VCL state.
331/// It must be public because it is used by the macro-generated code.
332#[doc(hidden)]
333#[derive(Debug)]
334pub struct PerVclState<T> {
335 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
336 pub fetch_filters: Vec<Box<ffi::vfp>>,
337 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
338 pub delivery_filters: Vec<Box<ffi::vdp>>,
339 pub user_data: Option<Box<T>>,
340}
341
342// Implement the default trait that works even when `T` does not impl `Default`.
343impl<T> Default for PerVclState<T> {
344 fn default() -> Self {
345 Self {
346 fetch_filters: Vec::default(),
347 delivery_filters: Vec::default(),
348 user_data: None,
349 }
350 }
351}
352
353impl<T> PerVclState<T> {
354 pub fn get_user_data(&self) -> Option<&T> {
355 self.user_data.as_ref().map(AsRef::as_ref)
356 }
357}