fastly/
experimental.rs

1//! Experimental Compute features.
2#[deprecated(
3    since = "0.11.4",
4    note = "The shielding module is not longer experimental. Please use `fastly::shielding` instead."
5)]
6pub mod shielding;
7
8use crate::{
9    abi::{self, FastlyStatus},
10    convert::{ToHeaderName, ToHeaderValue},
11    error::BufferSizeError,
12    http::{
13        header::{HeaderName, HeaderValue},
14        request::{
15            handle::{redirect_to_grip_proxy, redirect_to_websocket_proxy, RequestHandle},
16            CacheKeyGen, Request, SendError, SendErrorCause,
17        },
18        response::assert_single_downstream_response_is_sent,
19    },
20    Backend, Error,
21};
22use anyhow::anyhow;
23use bytes::Bytes;
24use fastly_sys::fastly_backend;
25use http::header::HeaderMap;
26use sha2::{Digest, Sha256};
27use std::sync::Arc;
28
29#[doc(inline)]
30pub use fastly_sys::fastly_backend::BackendHealth;
31
32pub use crate::backend::{BackendBuilder, BackendCreationError};
33pub use crate::Response;
34
35/// Parse a user agent string.
36#[doc = include_str!("../docs/snippets/experimental.md")]
37#[deprecated(since = "0.10.1")]
38pub fn uap_parse(
39    user_agent: &str,
40) -> Result<(String, Option<String>, Option<String>, Option<String>), Error> {
41    let user_agent: &[u8] = user_agent.as_ref();
42    let max_length = 255;
43    let mut family = Vec::with_capacity(max_length);
44    let mut major = Vec::with_capacity(max_length);
45    let mut minor = Vec::with_capacity(max_length);
46    let mut patch = Vec::with_capacity(max_length);
47    let mut family_nwritten = 0;
48    let mut major_nwritten = 0;
49    let mut minor_nwritten = 0;
50    let mut patch_nwritten = 0;
51
52    let status = unsafe {
53        abi::fastly_uap::parse(
54            user_agent.as_ptr(),
55            user_agent.len(),
56            family.as_mut_ptr(),
57            family.capacity(),
58            &mut family_nwritten,
59            major.as_mut_ptr(),
60            major.capacity(),
61            &mut major_nwritten,
62            minor.as_mut_ptr(),
63            minor.capacity(),
64            &mut minor_nwritten,
65            patch.as_mut_ptr(),
66            patch.capacity(),
67            &mut patch_nwritten,
68        )
69    };
70    if status.is_err() {
71        return Err(Error::msg("fastly_uap::parse failed"));
72    }
73    assert!(
74        family_nwritten <= family.capacity(),
75        "fastly_uap::parse wrote too many bytes for family"
76    );
77    unsafe {
78        family.set_len(family_nwritten);
79    }
80    assert!(
81        major_nwritten <= major.capacity(),
82        "fastly_uap::parse wrote too many bytes for major"
83    );
84    unsafe {
85        major.set_len(major_nwritten);
86    }
87    assert!(
88        minor_nwritten <= minor.capacity(),
89        "fastly_uap::parse wrote too many bytes for minor"
90    );
91    unsafe {
92        minor.set_len(minor_nwritten);
93    }
94    assert!(
95        patch_nwritten <= patch.capacity(),
96        "fastly_uap::parse wrote too many bytes for patch"
97    );
98    unsafe {
99        patch.set_len(patch_nwritten);
100    }
101    Ok((
102        String::from_utf8_lossy(&family).to_string(),
103        Some(String::from_utf8_lossy(&major).to_string()),
104        Some(String::from_utf8_lossy(&minor).to_string()),
105        Some(String::from_utf8_lossy(&patch).to_string()),
106    ))
107}
108
109/// An extension trait for [`Request`]s that adds methods for controlling cache keys.
110#[doc = include_str!("../docs/snippets/experimental.md")]
111pub trait RequestCacheKey {
112    /// See [`Request::set_cache_key()`].
113    #[doc = include_str!("../docs/snippets/experimental.md")]
114    fn set_cache_key(&mut self, key: [u8; 32]);
115    /// See [`Request::with_cache_key()`].
116    #[doc = include_str!("../docs/snippets/experimental.md")]
117    fn with_cache_key(self, key: [u8; 32]) -> Self;
118    /// See [`Request::set_cache_key_fn()`].
119    #[doc = include_str!("../docs/snippets/experimental.md")]
120    fn set_cache_key_fn(&mut self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static);
121    /// See [`Request::with_cache_key_fn()`].
122    #[doc = include_str!("../docs/snippets/experimental.md")]
123    fn with_cache_key_fn(self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static) -> Self;
124    /// See [`Request::set_cache_key_str()`].
125    #[doc = include_str!("../docs/snippets/experimental.md")]
126    fn set_cache_key_str(&mut self, key_str: impl AsRef<[u8]>);
127    /// See [`Request::with_cache_key_str()`].
128    #[doc = include_str!("../docs/snippets/experimental.md")]
129    fn with_cache_key_str(self, key_str: impl AsRef<[u8]>) -> Self;
130}
131
132impl RequestCacheKey for Request {
133    /// Set the cache key to be used when attempting to satisfy this request from a cached response.
134    #[doc = include_str!("../docs/snippets/experimental.md")]
135    fn set_cache_key(&mut self, key: [u8; 32]) {
136        self.metadata.override_cache_key = Some(CacheKeyGen::Set(Vec::from(key).into()));
137    }
138
139    /// Builder-style equivalent of [`set_cache_key()`](Self::set_cache_key()).
140    #[doc = include_str!("../docs/snippets/experimental.md")]
141    fn with_cache_key(mut self, key: [u8; 32]) -> Self {
142        self.set_cache_key(key);
143        self
144    }
145
146    /// Set the function that will be used to compute the cache key for this request.
147    #[doc = include_str!("../docs/snippets/experimental.md")]
148    fn set_cache_key_fn(&mut self, f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static) {
149        self.metadata.override_cache_key = Some(CacheKeyGen::Lazy(Arc::new(move |req| {
150            Bytes::from(Vec::from(f(req)))
151        })));
152    }
153
154    /// Builder-style equivalent of [`set_cache_key_fn()`](Self::set_cache_key_fn()).
155    #[doc = include_str!("../docs/snippets/experimental.md")]
156    fn with_cache_key_fn(
157        mut self,
158        f: impl Fn(&Request) -> [u8; 32] + Send + Sync + 'static,
159    ) -> Self {
160        self.set_cache_key_fn(f);
161        self
162    }
163
164    /// Set a string as the cache key to be used when attempting to satisfy this request from a
165    /// cached response.
166    ///
167    /// The string representation of the key is hashed to the same `[u8; 32]` representation used by
168    /// [`set_cache_key()`][`Self::set_cache_key()`].
169    #[doc = include_str!("../docs/snippets/experimental.md")]
170    fn set_cache_key_str(&mut self, key_str: impl AsRef<[u8]>) {
171        let mut sha = Sha256::new();
172        sha.update(key_str);
173        sha.update(b"\x00\xf0\x9f\xa7\x82\x00"); // extra salt
174        let finalized: [u8; 32] = *sha.finalize().as_ref();
175        self.set_cache_key(finalized)
176    }
177
178    /// Builder-style equivalent of [`set_cache_key_str()`](Self::set_cache_key_str()).
179    #[doc = include_str!("../docs/snippets/experimental.md")]
180    fn with_cache_key_str(mut self, key_str: impl AsRef<[u8]>) -> Self {
181        self.set_cache_key_str(key_str);
182        self
183    }
184}
185
186/// An extension trait for [`RequestHandle`](RequestHandle)s that adds methods for controlling cache
187/// keys.
188#[doc = include_str!("../docs/snippets/experimental.md")]
189pub trait RequestHandleCacheKey {
190    /// See [`RequestHandle::set_cache_key()`](RequestHandle::set_cache_key).
191    #[doc = include_str!("../docs/snippets/experimental.md")]
192    fn set_cache_key(&mut self, key: impl AsRef<[u8]>);
193}
194
195impl RequestHandleCacheKey for RequestHandle {
196    /// Set the cache key to be used when attempting to satisfy this request from a cached response.
197    #[doc = include_str!("../docs/snippets/experimental.md")]
198    fn set_cache_key(&mut self, key: impl AsRef<[u8]>) {
199        let key = key.as_ref();
200        const DIGITS: &[u8; 16] = b"0123456789ABCDEF";
201        let mut hex = Vec::with_capacity(key.len() * 2);
202        for b in key.iter() {
203            hex.push(DIGITS[(b >> 4) as usize]);
204            hex.push(DIGITS[(b & 0xf) as usize]);
205        }
206
207        self.insert_header(
208            &HeaderName::from_static("fastly-xqd-cache-key"),
209            &HeaderValue::from_bytes(&hex).unwrap(),
210        )
211    }
212}
213
214/// An extension trait for [`Request`]s that adds a method for upgrading websockets.
215pub trait RequestUpgradeWebsocket {
216    /// See [`Request::handoff_websocket()`].
217    #[deprecated(
218        since = "0.10.0",
219        note = "The RequestUpgradeWebsocket::handoff_websocket() trait method is now part of Request."
220    )]
221    fn handoff_websocket(self, backend: &str) -> Result<(), SendError>;
222
223    /// See [`Request::handoff_fanout()`].
224    #[deprecated(
225        since = "0.10.0",
226        note = "The RequestUpgradeWebsocket::handoff_fanout() trait method is now part of Request."
227    )]
228    fn handoff_fanout(self, backend: &str) -> Result<(), SendError>;
229}
230impl RequestUpgradeWebsocket for Request {
231    /// Pass the WebSocket directly to a backend.
232    ///
233    /// This can only be used on services that have the WebSockets feature enabled and on requests
234    /// that are valid WebSocket requests.
235    ///
236    /// The sending completes in the background. Once this method has been called, no other
237    /// response can be sent to this request, and the application can exit without affecting the
238    /// send.
239    fn handoff_websocket(self, backend: &str) -> Result<(), SendError> {
240        assert_single_downstream_response_is_sent(true);
241        let cloned = self.clone_without_body();
242        let (req_handle, _) = self.into_handles();
243        let status = redirect_to_websocket_proxy(req_handle, backend);
244        if status.is_err() {
245            Err(SendError::new(
246                backend,
247                cloned,
248                SendErrorCause::status(status),
249            ))
250        } else {
251            Ok(())
252        }
253    }
254
255    /// Pass the request through the Fanout GRIP proxy and on to a backend.
256    ///
257    /// This can only be used on services that have the Fanout feature enabled.
258    ///
259    /// The sending completes in the background. Once this method has been called, no other
260    /// response can be sent to this request, and the application can exit without affecting the
261    /// send.
262    fn handoff_fanout(self, backend: &str) -> Result<(), SendError> {
263        assert_single_downstream_response_is_sent(true);
264        let cloned = self.clone_without_body();
265        let (req_handle, _) = self.into_handles();
266        let status = redirect_to_grip_proxy(req_handle, backend);
267        if status.is_err() {
268            Err(SendError::new(
269                backend,
270                cloned,
271                SendErrorCause::status(status),
272            ))
273        } else {
274            Ok(())
275        }
276    }
277}
278
279/// An extension trait for [`RequestHandle`]s that adds methods for upgrading
280/// websockets.
281pub trait RequestHandleUpgradeWebsocket {
282    /// See [`RequestHandle::handoff_websocket()`].
283    #[deprecated(
284        since = "0.10.0",
285        note = "The RequestHandleUpgradeWebsocket::handoff_websocket() trait method is now part of RequestHandle."
286    )]
287    fn handoff_websocket(&mut self, backend: &str) -> Result<(), SendErrorCause>;
288
289    /// See [`RequestHandle::handoff_fanout()`].
290    #[deprecated(
291        since = "0.10.0",
292        note = "The RequestHandleUpgradeWebsocket::handoff_fanout() trait method is now part of RequestHandle."
293    )]
294    fn handoff_fanout(&mut self, backend: &str) -> Result<(), SendErrorCause>;
295}
296
297impl RequestHandleUpgradeWebsocket for RequestHandle {
298    /// Pass the WebSocket directly to a backend.
299    ///
300    /// This can only be used on services that have the WebSockets feature enabled and on requests
301    /// that are valid WebSocket requests.
302    ///
303    /// The sending completes in the background. Once this method has been called, no other
304    /// response can be sent to this request, and the application can exit without affecting the
305    /// send.
306    fn handoff_websocket(&mut self, backend: &str) -> Result<(), SendErrorCause> {
307        match unsafe {
308            abi::fastly_http_req::redirect_to_websocket_proxy_v2(
309                self.as_u32(),
310                backend.as_ptr(),
311                backend.len(),
312            )
313        } {
314            FastlyStatus::OK => Ok(()),
315            status => Err(SendErrorCause::status(status)),
316        }
317    }
318
319    /// Pass the request through the Fanout GRIP proxy and on to a backend.
320    ///
321    /// This can only be used on services that have the Fanout feature enabled.
322    ///
323    /// The sending completes in the background. Once this method has been called, no other
324    /// response can be sent to this request, and the application can exit without affecting the
325    /// send.
326    fn handoff_fanout(&mut self, backend: &str) -> Result<(), SendErrorCause> {
327        match unsafe {
328            abi::fastly_http_req::redirect_to_grip_proxy_v2(
329                self.as_u32(),
330                backend.as_ptr(),
331                backend.len(),
332            )
333        } {
334            FastlyStatus::OK => Ok(()),
335            status => Err(SendErrorCause::status(status)),
336        }
337    }
338}
339
340/// An extension trait for experimental [`Backend`] methods.
341pub trait BackendExt {
342    /// Return the health of the backend if configured and currently known.
343    ///
344    /// For backends without a configured healthcheck, this will always return `Unknown`.
345    fn is_healthy(&self) -> Result<BackendHealth, Error>;
346}
347
348impl BackendExt for Backend {
349    fn is_healthy(&self) -> Result<BackendHealth, Error> {
350        let mut backend_health_out = BackendHealth::Unknown;
351        unsafe {
352            fastly_backend::is_healthy(
353                self.name().as_ptr(),
354                self.name().len(),
355                &mut backend_health_out,
356            )
357        }
358        .result()
359        .map_err(|e| match e {
360            FastlyStatus::NONE => anyhow!("backend not found"),
361            _ => anyhow!("backend healthcheck error: {:?}", e),
362        })?;
363        Ok(backend_health_out)
364    }
365}
366
367/// An extension trait to support gRPC backend definition.
368pub trait GrpcBackend {
369    /// Set whether or not this backend will be used for gRPC traffic.
370    ///
371    /// Warning: Setting this for backends that will not be used with gRPC may have
372    /// unpredictable effects. Fastly only currently guarantees that this connection
373    /// will work for gRPC traffic.
374    #[doc = include_str!("../docs/snippets/experimental.md")]
375    fn for_grpc(self, value: bool) -> Self;
376}
377
378/// Errors that can arise from reading trailer information.
379#[derive(thiserror::Error, Debug)]
380pub enum BodyHandleError {
381    /// The trailers are not ready; please consume all of the body data before retrying.
382    #[error("trailers not yet ready")]
383    TrailersNotReady,
384}
385
386/// Extensions to the [`crate::handle::BodyHandle`] type to support trailers.
387pub trait BodyHandleExt {
388    /// Get the names of the trailers associated with this body.
389    ///
390    /// The resulting iterator will produce either the header names or an indication
391    /// that the provided buffer size was too small. In that case, calling with a
392    /// larger buffer size (information about how large a buffer is recommended is
393    /// provided in [`BufferSizeError`]) should resolve the situation.
394    #[doc = include_str!("../docs/snippets/trailers.md")]
395    #[doc = include_str!("../docs/snippets/experimental.md")]
396    fn get_trailer_names<'a>(
397        &'a self,
398        buf_size: usize,
399    ) -> Result<Box<dyn Iterator<Item = Result<HeaderName, BufferSizeError>> + 'a>, BodyHandleError>;
400
401    /// Get the value for the trailer with the given name.
402    ///
403    /// If there are multiple values for this header, only one is returned, which may be
404    /// any of the values. See [`BodyHandleExt::get_trailer_values`] if you need to get
405    /// all of the values.
406    ///
407    /// There are several options in the return value, ignoring the common tailer case
408    /// in which you must read more data from the body:
409    ///    * Ok(Some(value)): The trailer existed, and had the given value
410    ///    * Ok(None): A trailer with that name does not exist
411    ///    * Err(BufferSizeError): The trailer existed, but the provided max length was
412    ///      too small.
413    ///
414    /// In the last case, [`BufferSizeError`] should provide information about what
415    /// size buffer will be required to hold the requested data.
416    #[doc = include_str!("../docs/snippets/trailers.md")]
417    #[doc = include_str!("../docs/snippets/experimental.md")]
418    fn get_trailer_value(
419        &self,
420        name: &HeaderName,
421        max_len: usize,
422    ) -> Result<Result<Option<HeaderValue>, BufferSizeError>, BodyHandleError>;
423
424    /// Get all the values associated with the trailer with the given name.
425    ///
426    /// As opposed to [`BodyHandleExt::get_trailer_value`], this function returns all
427    /// of the values for this trailer.
428    #[doc = include_str!("../docs/snippets/trailers.md")]
429    #[doc = include_str!("../docs/snippets/experimental.md")]
430    fn get_trailer_values<'a>(
431        &'a self,
432        name: &'a HeaderName,
433        max_len: usize,
434    ) -> Result<Box<dyn Iterator<Item = Result<HeaderValue, BufferSizeError>> + 'a>, BodyHandleError>;
435}
436
437/// Extensions to the [`crate::http::body::Body`] type to support trailers.
438pub trait BodyExt {
439    /// Append the given trailer name and value to this body.
440    ///
441    /// Trailers will be sent once the body send is complete. This function will not
442    /// discard any existing values for the trailer; this value will simply be appended
443    /// to the set of existing values.
444    #[doc = include_str!("../docs/snippets/experimental.md")]
445    fn append_trailer(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue);
446
447    /// Get the trailers associated with this body.
448    ///
449    /// Note that modifying this [`HeaderMap`] will have no effect on the trailers
450    /// for the body; in order to add trailers, you will need to use [`BodyExt::append_trailer`].
451    /// If you need to remove a trailer, the only current mechanism to do so is to
452    /// create a fresh body, and then copy over the trailers you wish to maintain.
453    /// (If this inconvenient, please contact your Fastly support team, and mention
454    /// this shortcoming.)
455    #[doc = include_str!("../docs/snippets/trailers.md")]
456    #[doc = include_str!("../docs/snippets/experimental.md")]
457    fn get_trailers(&mut self) -> Result<HeaderMap, Error>;
458}
459
460/// Extensions to the [`crate::http::body::StreamingBody`] type to support trailers.
461pub trait StreamingBodyExt {
462    /// Append the given trailer name and value to this body.
463    ///
464    /// Trailers will be sent once the body send is complete. This function will not
465    /// discard any existing values for the trailer; this value will simply be appended
466    /// to the set of existing values.
467    #[doc = include_str!("../docs/snippets/experimental.md")]
468    fn append_trailer(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue);
469
470    /// Get the trailers associated with this body.
471    ///
472    /// Note that modifying this [`HeaderMap`] will have no effect on the trailers
473    /// for the body; in order to add trailers, you will need to use [`BodyExt::append_trailer`].
474    /// If you need to remove a trailer, the only current mechanism to do so is to
475    /// create a fresh body, and then copy over the trailers you wish to maintain.
476    /// (If this inconvenient, please contact your Fastly support team, and mention
477    /// this shortcoming.)
478    #[doc = include_str!("../docs/snippets/trailers.md")]
479    #[doc = include_str!("../docs/snippets/experimental.md")]
480    fn finish_with_trailers(self, trailers: &HeaderMap) -> Result<(), std::io::Error>;
481}