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};
4use std::io::{self, Write};
5use std::net::SocketAddr;
6
7use crate::ffi;
8use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
9use crate::vcl::{
10 sc_to_ptr,
11 subroutine::{Id, Subroutine},
12 Acl, Buffer, HttpHeaders, LogTag, StreamClose, TestWS, VclError, VclResult, Workspace,
13};
14
15/// VCL context
16///
17/// A mutable reference to this structure is always passed to vmod functions and provides access to
18/// the available HTTP objects, as well as the workspace.
19///
20/// This struct is a pure Rust structure, mirroring some of the C fields, so you should always use
21/// the provided methods to interact with them. If they are not enough, the `raw` field is actually
22/// the C original pointer that can be used to directly, and unsafely, act on the structure.
23///
24/// Which `http_*` are present will depend on which VCL sub routine the function is called from.
25///
26/// ``` rust
27/// # mod varnish { pub use varnish_sys::vcl; }
28/// use varnish::vcl::Ctx;
29///
30/// fn foo(ctx: &Ctx) {
31/// if let Some(ref req) = ctx.http_req {
32/// for (name, value) in req {
33/// println!("header {name} has value {value:?}");
34/// }
35/// }
36/// }
37/// ```
38#[derive(Debug)]
39pub struct Ctx<'a> {
40 pub raw: &'a mut vrt_ctx,
41 pub http_req: Option<HttpHeaders<'a>>,
42 pub http_req_top: Option<HttpHeaders<'a>>,
43 pub http_resp: Option<HttpHeaders<'a>>,
44 pub http_bereq: Option<HttpHeaders<'a>>,
45 pub http_beresp: Option<HttpHeaders<'a>>,
46 pub ws: Workspace<'a>,
47
48 req: Option<Req<'a>>,
49}
50
51/// The state of a request or response body, mirroring Varnish's `body_status_t`
52/// (see `tbl/body_status.h`).
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum BodyState {
55 /// No body.
56 None,
57 /// An error occurred while producing/reading the body.
58 Error,
59 /// Chunked transfer encoding; the length isn't known upfront.
60 Chunked,
61 /// A known length (e.g. `Content-Length`).
62 Length,
63 /// The body ends when the connection closes; the length isn't known upfront.
64 Eof,
65 /// The body has already been consumed by someone else.
66 Taken,
67 /// The body has been fully read and is cached as an object.
68 Cached,
69}
70
71impl BodyState {
72 fn from_raw(ptr: ffi::body_status_t) -> Self {
73 unsafe {
74 if ptr == ffi::BS_NONE.as_ptr() {
75 Self::None
76 } else if ptr == ffi::BS_ERROR.as_ptr() {
77 Self::Error
78 } else if ptr == ffi::BS_CHUNKED.as_ptr() {
79 Self::Chunked
80 } else if ptr == ffi::BS_LENGTH.as_ptr() {
81 Self::Length
82 } else if ptr == ffi::BS_EOF.as_ptr() {
83 Self::Eof
84 } else if ptr == ffi::BS_TAKEN.as_ptr() {
85 Self::Taken
86 } else if ptr == ffi::BS_CACHED.as_ptr() {
87 Self::Cached
88 } else {
89 unreachable!("unknown body_status_t")
90 }
91 }
92 }
93}
94
95/// State threaded through Varnish's body-iterate C callback via its `priv_` pointer.
96///
97/// Used by [`Ctx::req_body`].
98struct BodyWriterState<'w, W: Write> {
99 writer: &'w mut W,
100 error: Option<io::Error>,
101}
102
103/// Bridges Varnish's body-iterate callback (`objiterate_f`) to `W::write_all`.
104///
105/// Monomorphized per `W`. `ObjIterate`/`VRB_Iterate` call this synchronously
106/// and sequentially, so at most one `&mut` derived from `priv_` is ever live,
107/// and it never outlives that call. Used by [`Ctx::req_body`].
108///
109/// The `ptr.is_null() || len <= 0` guard below means `W::write_all` (and thus
110/// a caller's own `Write` impl) is never invoked with an empty buffer through
111/// this path — callers of [`Ctx::req_body`] can rely on that when implementing
112/// `W`.
113unsafe extern "C" fn write_body_iterate<W: Write>(
114 priv_: *mut c_void,
115 _flush: c_uint,
116 ptr: *const c_void,
117 len: isize,
118) -> c_int {
119 if ptr.is_null() || len <= 0 {
120 return 0;
121 }
122 let writer_state = priv_
123 .cast::<BodyWriterState<W>>()
124 .as_mut()
125 .expect("body-iterate callback priv pointer must not be null");
126 let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
127 match writer_state.writer.write_all(buf) {
128 Ok(()) => 0,
129 Err(e) => {
130 writer_state.error = Some(e);
131 1
132 }
133 }
134}
135
136/// Resolves a completed `VRB_Iterate` call into [`Ctx::req_body`]'s return value: a
137/// captured writer error always takes priority over the raw C return code (`rv < 0`
138/// signals a stream-side failure instead).
139fn resolve_iterate_result<W: Write>(
140 rv: isize,
141 writer_state: &mut BodyWriterState<'_, W>,
142 stream_err: &'static str,
143) -> VclResult<bool> {
144 if let Some(e) = writer_state.error.take() {
145 return Err(e.to_string().into());
146 }
147 if rv < 0 {
148 return Err(stream_err.into());
149 }
150 Ok(true)
151}
152
153impl<'a> Ctx<'a> {
154 /// Wrap a raw pointer into an object we can use.
155 ///
156 /// The pointer must be non-null, and the magic must match
157 pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
158 Self::from_ref(
159 ptr.cast_mut()
160 .as_mut()
161 .expect("vrt_ctx pointer must not be null"),
162 )
163 }
164
165 /// Instantiate from a mutable reference to a [`vrt_ctx`].
166 pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
167 assert_eq!(raw.magic, VRT_CTX_MAGIC);
168 Self {
169 http_req: HttpHeaders::from_ptr(raw.http_req),
170 http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
171 http_resp: HttpHeaders::from_ptr(raw.http_resp),
172 http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
173 http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
174 ws: Workspace::from_ptr(raw.ws),
175 req: unsafe { Req::from_ptr(raw.req) },
176 raw,
177 }
178 }
179
180 /// Log an error message and fail the current VSL task.
181 ///
182 /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
183 /// and will return a synthetic error to the client.
184 pub fn fail(&mut self, msg: impl Into<VclError>) {
185 let msg = msg.into();
186 let msg = msg.as_str();
187 unsafe {
188 VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
189 }
190 }
191
192 /// Log a message, attached to the current context
193 pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
194 unsafe {
195 let vsl = self.raw.vsl;
196 if vsl.is_null() {
197 log(tag, msg);
198 } else {
199 let msg = ffi::txt::from_str(msg.as_ref());
200 ffi::VSLbt(vsl, tag, msg);
201 }
202 }
203 }
204
205 /// Match an ACL against a provided address.
206 pub fn acl_match(&self, acl: &Acl, addr: SocketAddr) -> bool {
207 assert!(!acl.raw.0.is_null());
208
209 unsafe {
210 let mut sa_buf = vec![0u8; ffi::vsa_suckaddr_len];
211 crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
212 ffi::VRT_acl_match(self.raw, acl.raw, ffi::VCL_IP(sa_buf.as_ptr().cast())) == 1
213 }
214 }
215
216 /// Return the name of the listener socket that received the current request.
217 ///
218 /// This corresponds to the VCL variable `local.socket` and returns the `-a` socket
219 /// name (e.g., `"a0"`, `"http-80"`). Returns an `Err` in backend context where the
220 /// session isn't available, or if the name is non-UTF-8.
221 pub fn local_socket(&self) -> Result<&'a str, VclError> {
222 // we're breaking abstraction here, but the other ways are to just reimplement the
223 // whole logic in rust (which is admittedly short), or to let the user crash
224 if self.raw.req.is_null() && self.raw.bo.is_null() {
225 return Err("local.socket isn't available in this context".into());
226 }
227 let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
228 let cstr = <&CStr>::from(raw);
229 Ok(cstr.to_str()?)
230 }
231
232 /// Return the address of the local endpoint that received the current request.
233 ///
234 /// This corresponds to the VCL variable `local.endpoint` and returns the address
235 /// string (e.g., `"127.0.0.1:8080"`, `"/var/run/varnish.sock"`). Returns an `Err` in
236 /// backend context where the session isn't available, or if the value is non-UTF-8.
237 // same notes as for local_socket
238 pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
239 if self.raw.req.is_null() && self.raw.bo.is_null() {
240 return Err("local.endpoint isn't available in this context".into());
241 }
242 let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
243 let cstr = <&CStr>::from(raw);
244 Ok(cstr.to_str()?)
245 }
246
247 /// Call a VCL subroutine.
248 ///
249 /// Returns `Ok(true)` if the request was handled after the call, `Ok(false)` otherwise.
250 /// Returns `Err` if the subroutine cannot be called in the current context (e.g. wrong VCL
251 /// state or incompatible subroutine type).
252 /// If `Ok(true)` was returned, no other subroutine can be called, and doing so will result
253 /// in a VCL error.
254 pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
255 self.check_call_sub(sub)?;
256 unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
257 Ok(self.is_handled())
258 }
259
260 /// Check whether a VCL subroutine can be called in the current context.
261 ///
262 /// Returns `Ok(())` if the call is valid, or `Err` with the reason otherwise.
263 pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
264 let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
265 if result.0.is_null() {
266 Ok(())
267 } else {
268 let msg = unsafe { CStr::from_ptr(result.0) }
269 .to_string_lossy()
270 .into_owned();
271 Err(VclError::new(msg))
272 }
273 }
274
275 /// Returns `true` if the current request has already been handled.
276 /// If `true`, no other subroutine can be called, and doing so will result
277 /// in a VCL error.
278 pub fn is_handled(&self) -> bool {
279 unsafe { VRT_handled(self.raw) != 0 }
280 }
281
282 /// Return the VCL subroutine currently executing in this context.
283 ///
284 /// # Panics
285 /// Panics if `ctx.raw.method` doesn't match any known subroutine.
286 /// Every real `vrt_ctx` must be stamped with exactly one method bit while a subroutine body runs.
287 pub fn subroutine(&self) -> Id {
288 Id::from_bitfield(self.raw.method)
289 .expect("vrt_ctx.method must match a known VCL subroutine")
290 }
291
292 /// Retrieve the cached request body as a list of byte slices.
293 ///
294 /// Returns slices pointing into the workspace; each slice is a contiguous chunk of the body.
295 /// Fails if the body has not been cached (i.e. `std.cache_req_body()` was not called in VCL
296 /// before this subroutine ran).
297 pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
298 unsafe extern "C" fn chunk_collector(
299 priv_: *mut c_void,
300 _flush: c_uint,
301 ptr: *const c_void,
302 len: isize,
303 ) -> c_int {
304 let v = priv_
305 .cast::<Vec<&[u8]>>()
306 .as_mut()
307 .expect("cached_req_body callback priv pointer must not be null");
308 let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
309 v.push(buf);
310 0
311 }
312
313 let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
314 unsafe {
315 if req.req_body_status != ffi::BS_CACHED.as_ptr() {
316 return Err("request body hasn't been previously cached".into());
317 }
318 }
319 let mut v: Box<Vec<&'a [u8]>> = Box::default();
320 let p: *mut Vec<&'a [u8]> = &raw mut *v;
321 match unsafe {
322 ffi::VRB_Iterate(
323 req.wrk,
324 req.vsl.as_mut_ptr(),
325 req,
326 Some(chunk_collector),
327 p.cast::<c_void>(),
328 )
329 } {
330 0 => Ok(*v),
331 _ => Err("req.body iteration failed".into()),
332 }
333 }
334
335 /// Return the current state of the request body — `bereq`'s body from a
336 /// backend context, or the client `req`'s body directly if called earlier
337 /// (`vcl_recv` and later, before any backend is involved).
338 ///
339 /// From backend context (busyobj set - typically
340 /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)): if
341 /// the body has already been cached as an object (e.g. after
342 /// `std.cache_req_body()`, or on a fetch retry), returns
343 /// [`BodyState::Cached`]; otherwise reflects the live client body's state,
344 /// or [`BodyState::None`] if there is no client request to read from.
345 ///
346 /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reflects
347 /// `req`'s own state directly - [`BodyState::Cached`] after
348 /// `std.cache_req_body()`, otherwise whatever the live, not-yet-consumed
349 /// client body's state is.
350 pub fn req_body_state(&self) -> VclResult<BodyState> {
351 if let Some(bo) = unsafe { self.raw.bo.as_ref() } {
352 // mirrors V1F_SendReq's `AZ(bo->req)` in its `bo->bereq_body != NULL`
353 // branch: a live `bo.req` and an already-cached `bo.bereq_body` are
354 // normally mutually exclusive by the time a backend runs, but prefer
355 // the cached body rather than assert the invariant, so an unexpected
356 // combination degrades gracefully instead of panicking the worker.
357 if !bo.bereq_body.is_null() {
358 return Ok(BodyState::Cached);
359 }
360 if !bo.req.is_null() {
361 return Ok(BodyState::from_raw(unsafe { (*bo.req).req_body_status }));
362 }
363 return Ok(BodyState::None);
364 }
365 if let Some(req) = self.req.as_ref() {
366 return Ok(BodyState::from_raw(req.raw.req_body_status));
367 }
368 Err("req.body/bereq.body isn't available in this context".into())
369 }
370
371 /// Copy the request body into `writer` — `bereq`'s body from a backend
372 /// context, or the client `req`'s body directly if called earlier
373 /// (`vcl_recv` and later, before any backend is involved).
374 ///
375 /// From backend context (busyobj set - typically
376 /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)):
377 /// transparently handles both a body already cached as an object (e.g.
378 /// after `std.cache_req_body()`, or on a fetch retry) and a body streamed
379 /// live from the client, hiding the underlying `ObjIterate`/`VRB_Iterate`
380 /// choice and bookkeeping.
381 ///
382 /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reads
383 /// `req`'s body directly, same `ObjIterate`/`VRB_Iterate` machinery, minus
384 /// the busyobj-specific `no_retry`/`doclose` bookkeeping (there's no fetch
385 /// yet to retry or close).
386 ///
387 /// **Read-once tradeoff**: per Varnish's own rule, an uncached body can be
388 /// read exactly once - either by you here, or later by whatever backend
389 /// ends up handling this request (a custom
390 /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response),
391 /// or a plain upstream backend forwarding it). Read it once in `vcl_recv`
392 /// without caching first, and that *later* read fails
393 /// (`BodyState::Taken`/an error), not this one. Call `std.cache_req_body()`
394 /// before reading if the body needs to survive for a backend (or a retry)
395 /// to read too - check first if you're not sure:
396 ///
397 /// ```
398 /// # mod varnish { pub use varnish_sys::vcl; }
399 /// # use varnish::vcl::{Ctx, BodyState};
400 /// # fn f(ctx: &mut Ctx) -> Result<(), Box<dyn std::error::Error>> {
401 /// match ctx.req_body_state()? {
402 /// BodyState::Cached => {
403 /// // safe: a cached body can be read here and still be read again
404 /// // later (by a backend, or after a retry).
405 /// let mut buf = Vec::new();
406 /// ctx.req_body(&mut buf)?;
407 /// }
408 /// BodyState::None => {
409 /// // no body at all - nothing to read, nothing to worry about.
410 /// }
411 /// BodyState::Length | BodyState::Chunked | BodyState::Eof => {
412 /// // live and not cached: reading now consumes it. Only do this if
413 /// // you're sure no backend/retry downstream also needs it, or call
414 /// // `std.cache_req_body()` first if they might.
415 /// }
416 /// BodyState::Taken | BodyState::Error => {
417 /// // already gone (consumed elsewhere) or failed - nothing left to read.
418 /// }
419 /// }
420 /// # Ok(()) }
421 /// ```
422 ///
423 /// To consume the body without keeping it, pass [`std::io::sink()`] as `writer`.
424 ///
425 /// `writer` is only ever fed non-empty chunks — a custom `Write` impl
426 /// doesn't need to handle a zero-length `buf` in its `write`.
427 ///
428 /// Returns `Ok(false)` without touching `writer` if there is no body at all.
429 /// Returns `Ok(true)` once the full body has been copied into `writer`.
430 /// Returns `Err(_)` if called outside both a backend and a client context,
431 /// if the body stream itself failed to read, or if `writer` errors (the
432 /// `io::Error` is captured and turned into a [`VclError`]). Mirroring
433 /// upstream's `V1F_SendReq`, a failure while iterating an already-cached
434 /// body is not treated as fatal on its own (only a `writer` error is).
435 ///
436 /// Side effect (backend context only): if the body isn't already cached,
437 /// reading it marks the fetch as non-retryable (`bo.no_retry`), mirroring
438 /// `V1F_SendReq` — call `std.cache_req_body()` in `vcl_recv` first if the
439 /// backend may need to retry after reading the body.
440 pub fn req_body<W: Write>(&mut self, writer: &mut W) -> VclResult<bool> {
441 let state = self.req_body_state()?;
442 if state == BodyState::None {
443 return Ok(false);
444 }
445
446 if self.raw.bo.is_null() {
447 // client context: no busyobj yet, so none of the backend-only
448 // no_retry/doclose/err_code bookkeeping below applies - there's no
449 // fetch yet to retry or close. Read the read-once tradeoff warning
450 // above before relying on this branch.
451 let req = &mut *self
452 .req
453 .as_mut()
454 .ok_or("req.body/bereq.body isn't available in this context")?
455 .raw;
456 let mut writer_state = BodyWriterState {
457 writer,
458 error: None,
459 };
460 let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
461 let rv = unsafe {
462 ffi::VRB_Iterate(
463 req.wrk,
464 req.vsl.as_mut_ptr(),
465 req,
466 Some(write_body_iterate::<W>),
467 state_ptr.cast::<c_void>(),
468 )
469 };
470 return resolve_iterate_result(rv, &mut writer_state, "req.body read error");
471 }
472
473 let bo = unsafe { self.raw.bo.as_mut() }
474 .ok_or("bereq.body isn't available in this context (not a backend fetch)")?;
475
476 let mut writer_state = BodyWriterState {
477 writer,
478 error: None,
479 };
480 let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
481
482 if state == BodyState::Cached {
483 // a previously-cached bereq.body (e.g. via std.cache_req_body(), or a
484 // retried fetch) takes priority, mirroring V1F_SendReq. Its return
485 // value is deliberately ignored, same as upstream's `(void)ObjIterate(...)`
486 // in V1F_SendReq: it conflates "callback stopped iteration" with an
487 // internal storage error, and upstream never treats it as fatal here.
488 // Untested: a failing cached-storage iteration isn't easily forced
489 // from VCL, so this specific claim rests on reading V1F_SendReq, not
490 // on a `.vtc` reproduction.
491 unsafe {
492 ffi::ObjIterate(
493 bo.wrk,
494 bo.bereq_body,
495 state_ptr.cast::<c_void>(),
496 Some(write_body_iterate::<W>),
497 0,
498 );
499 }
500 if let Some(e) = writer_state.error.take() {
501 return Err(e.to_string().into());
502 }
503 Ok(true)
504 } else {
505 let rv = unsafe {
506 ffi::VRB_Iterate(
507 bo.wrk,
508 bo.vsl.as_mut_ptr(),
509 bo.req,
510 Some(write_body_iterate::<W>),
511 state_ptr.cast::<c_void>(),
512 )
513 };
514
515 // bookkeeping mirrored from V1F_SendReq: needed regardless of whether
516 // the writer itself failed, since it reflects the now-(partially-)
517 // consumed client body stream.
518 let req = unsafe { &mut *bo.req };
519 unsafe {
520 if req.req_body_status != ffi::BS_CACHED.as_ptr() {
521 bo.no_retry = c"bereq.body not cached".as_ptr();
522 }
523 // Only treat this as an upstream RX-body failure if the *writer*
524 // didn't cause it: mirroring vrb_pull, any early `func()` failure
525 // (including ours, from a `writer` error) also leaves the C side's
526 // `req_body_status` as `BS_ERROR` unless it happened to coincide
527 // with the last chunk read off the wire — so without this guard, a
528 // writer error on a multi-chunk body would incorrectly get flagged
529 // as a client-stream failure (`doclose`/400) here, even though the
530 // writer's own `Err` below already reports it correctly.
531 if writer_state.error.is_none() && req.req_body_status == ffi::BS_ERROR.as_ptr() {
532 req.doclose = sc_to_ptr(StreamClose::RxBody);
533 bo.err_code = 400;
534 }
535 }
536
537 resolve_iterate_result(rv, &mut writer_state, "bereq.body (streamed) read error")
538 }
539 }
540
541 /// Return a shared reference to the client request object, if present.
542 ///
543 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
544 pub fn req(&self) -> Option<&Req<'_>> {
545 self.req.as_ref()
546 }
547
548 /// Return a mutable reference to the client request object, if present.
549 ///
550 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
551 pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
552 self.req.as_mut()
553 }
554
555 /// Return the VSB used to build the response body, so it can be written
556 /// to directly (e.g. via [`Buffer::write`]).
557 ///
558 /// Valid when called from `vcl_synth` or `vcl_backend_error` — per
559 /// `vrt.h`'s comment on `specific` (`synth+error: struct vsb *`), those
560 /// are the only two subroutines where Varnish points `ctx.specific` at a
561 /// `struct vsb *`: building a synthetic response body (`vcl_synth`) or a
562 /// backend error page (`vcl_backend_error`).
563 ///
564 /// Returns `Err` if called from any other subroutine.
565 ///
566 /// # Panics
567 /// Panics if `specific` is null or isn't a VSB even though the
568 /// subroutine check passed — that would mean the crate's understanding
569 /// of Varnish's contract is wrong, not a recoverable misuse by the caller.
570 pub fn response_buffer(&mut self) -> VclResult<Buffer<'_>> {
571 match self.subroutine() {
572 Id::Synth | Id::BackendError => {
573 Ok(Buffer::from_ptr(self.raw.specific.cast::<ffi::vsb>()))
574 }
575 _ => Err(
576 "ctx.response_buffer() is only available in vcl_synth or vcl_backend_error".into(),
577 ),
578 }
579 }
580}
581
582/// Rust proxy for the C `req` struct.
583/// Its methods provide getters and setters for various fields that control how the client request
584/// is processed by Varnish.
585#[derive(Debug)]
586pub struct Req<'a> {
587 raw: &'a mut ffi::req,
588}
589
590impl Req<'_> {
591 /// Wrap a raw pointer into an object we can use.
592 pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
593 Some(Req { raw: p.as_mut()? })
594 }
595
596 /// Return whether this request bypasses the cache lookup and is always treated as a miss.
597 ///
598 /// Equivalent to `req.hash_always_miss` in VCL.
599 pub fn hash_always_miss(&self) -> bool {
600 self.raw.hash_always_miss() == 1
601 }
602
603 /// Force this request to be treated as a cache miss, skipping any existing cached object.
604 ///
605 /// Equivalent to setting `req.hash_always_miss` in VCL.
606 pub fn set_hash_always_miss(&mut self, val: bool) {
607 self.raw.set_hash_always_miss(val.into());
608 }
609
610 /// Return whether this request ignores busy (locked) cache objects and fetches from the backend instead of waiting.
611 ///
612 /// Equivalent to `req.hash_ignore_busy` in VCL.
613 pub fn hash_ignore_busy(&self) -> bool {
614 self.raw.hash_ignore_busy() == 1
615 }
616
617 /// Make this request skip waiting on busy cache objects and go straight to the backend.
618 ///
619 /// Equivalent to setting `req.hash_ignore_busy` in VCL.
620 pub fn set_hash_ignore_busy(&mut self, val: bool) {
621 self.raw.set_hash_ignore_busy(val.into());
622 }
623
624 /// Return whether this request ignores `Vary` headers during cache lookup.
625 ///
626 /// Equivalent to `req.hash_ignore_vary` in VCL.
627 pub fn hash_ignore_vary(&self) -> bool {
628 self.raw.hash_ignore_vary() == 1
629 }
630
631 /// Make this request ignore `Vary` headers during cache lookup, collapsing all variants into one cache key.
632 ///
633 /// Equivalent to setting `req.hash_ignore_vary` in VCL.
634 pub fn set_hash_ignore_vary(&mut self, val: bool) {
635 self.raw.set_hash_ignore_vary(val.into());
636 }
637}
638
639/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
640///
641/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
642/// used elsewhere.
643#[derive(Debug)]
644pub struct TestCtx {
645 vrt_ctx: vrt_ctx,
646 test_ws: TestWS,
647}
648
649impl TestCtx {
650 /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
651 pub fn new(sz: usize) -> Self {
652 let mut test_ctx = Self {
653 vrt_ctx: vrt_ctx {
654 magic: VRT_CTX_MAGIC,
655 ..vrt_ctx::default()
656 },
657 test_ws: TestWS::new(sz),
658 };
659 test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
660 test_ctx
661 }
662
663 /// Return a [`Ctx`] wrapping this test context, for use in unit tests.
664 pub fn ctx(&mut self) -> Ctx<'_> {
665 Ctx::from_ref(&mut self.vrt_ctx)
666 }
667}
668
669/// Log a message outside of a request context using a VSL tag.
670///
671/// Useful in event handlers or other places where no [`Ctx`] is available.
672pub fn log(tag: LogTag, msg: impl AsRef<str>) {
673 let msg = msg.as_ref();
674 unsafe {
675 let vxids = ffi::vxids::default();
676 ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
677 }
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683
684 #[test]
685 fn ctx_test() {
686 let mut test_ctx = TestCtx::new(100);
687 test_ctx.ctx();
688 }
689
690 #[test]
691 fn subroutine_returns_current_method() {
692 let mut test_ctx = TestCtx::new(100);
693 test_ctx.vrt_ctx.method = Id::Recv.to_bitfield();
694 let ctx = test_ctx.ctx();
695 assert_eq!(ctx.subroutine(), Id::Recv);
696 }
697
698 #[test]
699 fn response_buffer_outside_synth_errs() {
700 let mut test_ctx = TestCtx::new(100);
701 test_ctx.vrt_ctx.method = Id::Recv.to_bitfield();
702 let mut ctx = test_ctx.ctx();
703 assert_eq!(ctx.subroutine(), Id::Recv);
704 assert!(ctx.response_buffer().is_err());
705 }
706
707 #[test]
708 fn response_buffer_in_vcl_synth_ok() {
709 let mut test_ctx = TestCtx::new(100);
710 test_ctx.vrt_ctx.method = Id::Synth.to_bitfield();
711 let mut vsb = unsafe { ffi::VSB_new_auto() };
712 test_ctx.vrt_ctx.specific = vsb.cast::<c_void>();
713
714 let mut ctx = test_ctx.ctx();
715 let mut buf = ctx
716 .response_buffer()
717 .expect("response_buffer should succeed in vcl_synth");
718 assert_eq!(buf.raw.magic, ffi::VSB_MAGIC);
719 buf.write(&"hello").expect("VSB write should succeed");
720
721 unsafe { ffi::VSB_destroy(&raw mut vsb) };
722 }
723}
724
725/// This is an unsafe struct that holds the per-VCL state.
726/// It must be public because it is used by the macro-generated code.
727#[doc(hidden)]
728#[derive(Debug)]
729pub struct PerVclState<T> {
730 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
731 pub fetch_filters: Vec<Box<ffi::vfp>>,
732 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
733 pub delivery_filters: Vec<Box<ffi::vdp>>,
734 pub user_data: Option<Box<T>>,
735}
736
737// Implement the default trait that works even when `T` does not impl `Default`.
738impl<T> Default for PerVclState<T> {
739 fn default() -> Self {
740 Self {
741 fetch_filters: Vec::default(),
742 delivery_filters: Vec::default(),
743 user_data: None,
744 }
745 }
746}
747
748impl<T> PerVclState<T> {
749 pub fn get_user_data(&self) -> Option<&T> {
750 self.user_data.as_ref().map(AsRef::as_ref)
751 }
752}