Skip to main content

varnish_sys/vcl/backend/
director.rs

1use std::ffi::{c_char, c_void, CString};
2use std::ptr;
3use std::ptr::null;
4
5use crate::ffi::{VclEvent, VCL_BACKEND, VCL_BOOL, VCL_TIME};
6use crate::vcl::{Buffer, Ctx, VclResult};
7use crate::{ffi, validate_director};
8
9use super::{BackendRef, ProbeResult};
10
11/// Trait for wrapping a C `struct director`
12///
13/// This trait provides a safe interface to interact with Varnish directors,
14/// which are responsible for selecting backends. Directors receive requests
15/// and decide which backend should handle them, implementing load balancing
16/// and health checking strategies.
17///
18/// The trait methods map to the C `vdi_methods` structure function pointers.
19pub trait VclDirector {
20    /// Resolve the director to a concrete backend
21    ///
22    /// This is called when Varnish needs to select a backend to handle a request.
23    /// The director should inspect the context (request headers, etc.) and return
24    /// the appropriate backend, or `None` if no backend is available.
25    ///
26    /// Corresponds to the `resolve` callback in `vdi_methods`.
27    fn resolve(&self, ctx: &mut Ctx) -> Option<BackendRef>;
28
29    /// Check if the director (or its backends) are healthy
30    ///
31    /// Returns a `ProbeResult` containing the health status and when it last changed.
32    ///
33    /// Corresponds to the `healthy` callback in `vdi_methods`.
34    fn probe(&self, ctx: &mut Ctx) -> ProbeResult;
35
36    /// Generate simple report output for `varnishadm backend.list` (no flags)
37    ///
38    /// Corresponds to the `list` callback in `vdi_methods` when neither `-p` nor `-j` is passed.
39    fn report(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
40
41    /// Generate detailed report output for `varnishadm backend.list -p`
42    ///
43    /// Corresponds to the `list` callback in `vdi_methods` when `-p` is passed.
44    fn report_details(&self, _ctx: &mut Ctx, _vsb: &mut Buffer) {}
45
46    /// Generate simple JSON report output for `varnishadm backend.list -j`
47    ///
48    /// Corresponds to the `list` callback in `vdi_methods` when `-j` is passed.
49    fn report_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
50        let _ = vsb.write(&"{}");
51    }
52
53    /// Generate detailed JSON report output for `varnishadm backend.list -j -p`
54    ///
55    /// Corresponds to the `list` callback in `vdi_methods` when both `-j` and `-p` are passed.
56    fn report_details_json(&self, _ctx: &mut Ctx, vsb: &mut Buffer) {
57        let _ = vsb.write(&"{}");
58    }
59
60    /// Called when the VCL temperature changes or is discarded
61    ///
62    /// Corresponds to the `event` callback in `vdi_methods`.
63    fn event(&self, event: VclEvent) {
64        let _ = event;
65    }
66}
67
68/// Safe wrapper around a `struct director` pointer with a trait implementation
69///
70/// This struct wraps a C director along with a Rust implementation that provides
71/// the director's behavior through the [`VclDirector`] trait. The wrapper handles
72/// the FFI boundary and ensures proper lifetime management.
73///
74/// Directors are typically used to implement load balancing strategies (round-robin,
75/// random, hash-based, etc.) by selecting from multiple backends.
76#[derive(Debug)]
77pub struct Director<D: VclDirector> {
78    #[expect(dead_code)]
79    methods: Box<ffi::vdi_methods>,
80    inner: Box<D>,
81    #[expect(dead_code)]
82    ctype: CString,
83    backend_ref: BackendRef,
84}
85
86impl<D: VclDirector> Director<D> {
87    /// Create a new director by calling `VRT_AddDirector`
88    ///
89    /// This registers the director with Varnish and sets up the appropriate callbacks.
90    /// The director will be automatically unregistered when dropped.
91    pub fn new(ctx: &mut Ctx, director_type: &str, vcl_name: &str, inner: D) -> VclResult<Self> {
92        let mut inner = Box::new(inner);
93        let ctype = CString::new(director_type).map_err(|e| e.to_string())?;
94        let cname = CString::new(vcl_name).map_err(|e| e.to_string())?;
95        let methods = Box::new(ffi::vdi_methods {
96            type_: ctype.as_ptr(),
97            magic: ffi::VDI_METHODS_MAGIC,
98            http1pipe: None,
99            healthy: Some(wrap_director_healthy::<D>),
100            resolve: Some(wrap_director_resolve::<D>),
101            gethdrs: None,
102            getip: None,
103            finish: None,
104            event: Some(wrap_director_event::<D>),
105            release: None,
106            destroy: None,
107            panic: None,
108            list: Some(wrap_director_list::<D>),
109        });
110
111        let bep = unsafe {
112            ffi::VRT_AddDirector(
113                ctx.raw,
114                &raw const *methods,
115                ptr::from_mut::<D>(&mut *inner).cast::<c_void>(),
116                c"%.*s".as_ptr(),
117                cname.as_bytes().len(),
118                cname.as_ptr().cast::<c_char>(),
119            )
120        };
121        if bep.0.is_null() {
122            return Err(format!("VRT_AddDirector returned null while creating {vcl_name}").into());
123        }
124
125        unsafe {
126            assert_eq!((*bep.0).magic, ffi::DIRECTOR_MAGIC);
127        }
128
129        let backend_ref = unsafe {
130            BackendRef::new_without_refcount(bep).expect("Backend pointer should never be null")
131        };
132
133        Ok(Director {
134            methods,
135            inner,
136            ctype,
137            backend_ref,
138        })
139    }
140
141    /// Access the bep director implementation
142    pub fn get_inner(&self) -> &D {
143        &self.inner
144    }
145
146    /// Access the bep director implementation mutably
147    pub fn get_inner_mut(&mut self) -> &mut D {
148        &mut self.inner
149    }
150
151    /// Resolve this director to a backend using `VRT_DirectorResolve`
152    ///
153    /// This calls into Varnish's resolution mechanism, which will invoke
154    /// the director's `resolve` method if needed.
155    pub fn resolve(&self, ctx: &Ctx) -> VCL_BACKEND {
156        unsafe { ffi::VRT_DirectorResolve(ctx.raw, self.backend_ref.vcl_ptr()) }
157    }
158
159    /// Check if this director is healthy using `VRT_Healthy`
160    pub fn probe(&self, ctx: &Ctx) -> ProbeResult {
161        self.backend_ref.probe(ctx)
162    }
163}
164
165impl<D: VclDirector> Drop for Director<D> {
166    fn drop(&mut self) {
167        unsafe {
168            let mut bep = self.backend_ref.vcl_ptr();
169            ffi::VRT_DelDirector(&raw mut bep);
170        }
171    }
172}
173
174impl<D: VclDirector> AsRef<BackendRef> for Director<D> {
175    fn as_ref(&self) -> &BackendRef {
176        &self.backend_ref
177    }
178}
179
180// C FFI wrapper functions
181
182unsafe extern "C" fn wrap_director_resolve<D: VclDirector>(
183    ctxp: *const ffi::vrt_ctx,
184    director: VCL_BACKEND,
185) -> VCL_BACKEND {
186    let mut ctx = Ctx::from_ptr(ctxp);
187    let dir = validate_director(director);
188    let dir_impl: &D = &*dir.priv_.cast::<D>();
189    dir_impl
190        .resolve(&mut ctx)
191        .map_or(VCL_BACKEND(null()), |backend_ref| backend_ref.vcl_ptr())
192}
193
194unsafe extern "C" fn wrap_director_healthy<D: VclDirector>(
195    ctxp: *const ffi::vrt_ctx,
196    director: VCL_BACKEND,
197    changed: *mut VCL_TIME,
198) -> VCL_BOOL {
199    let mut ctx = Ctx::from_ptr(ctxp);
200    let dir = validate_director(director);
201    let dir_impl: &D = &*dir.priv_.cast::<D>();
202    let result = dir_impl.probe(&mut ctx);
203    if !changed.is_null() {
204        // SystemTime->VCL_TIME can fail for times before UNIX_EPOCH. Avoid panicking
205        // across the FFI boundary; leave `*changed` untouched on conversion failure.
206        if let Ok(t) = result.last_changed.try_into() {
207            *changed = t;
208        }
209    }
210    result.healthy.into()
211}
212
213unsafe extern "C" fn wrap_director_list<D: VclDirector>(
214    ctxp: *const ffi::vrt_ctx,
215    director: VCL_BACKEND,
216    vsbp: *mut ffi::vsb,
217    detailed: i32,
218    json: i32,
219) {
220    let mut ctx = Ctx::from_ptr(ctxp);
221    let mut vsb = Buffer::from_ptr(vsbp);
222    let dir = validate_director(director);
223    let dir_impl: &D = &*dir.priv_.cast::<D>();
224    match (json != 0, detailed != 0) {
225        (true, true) => dir_impl.report_details_json(&mut ctx, &mut vsb),
226        (true, false) => dir_impl.report_json(&mut ctx, &mut vsb),
227        (false, true) => dir_impl.report_details(&mut ctx, &mut vsb),
228        (false, false) => dir_impl.report(&mut ctx, &mut vsb),
229    }
230}
231
232unsafe extern "C" fn wrap_director_event<D: VclDirector>(director: VCL_BACKEND, ev: VclEvent) {
233    let dir = validate_director(director);
234    let dir_impl: &D = &*dir.priv_.cast::<D>();
235    dir_impl.event(ev);
236}