Skip to main content

fastly/http/
request.rs

1//! HTTP requests.
2use self::handle::{ContentEncodings, RequestHandle};
3use super::body::{self, Body, StreamingBody};
4use super::cache::{self, CacheKey, HttpCacheError, HttpCacheHandle};
5use super::response::{assert_single_downstream_response_is_sent, Response};
6use crate::convert::{Borrowable, ToBackend, ToHeaderName, ToHeaderValue, ToMethod, ToUrl};
7use crate::error::{anyhow, BufferSizeError};
8use crate::experimental::BodyExt;
9use crate::handle::{BodyHandle, ResponseHandle};
10use crate::http::{CandidateResponse, LazyHandle};
11use crate::image_optimizer;
12use bytes::Bytes;
13use fastly_shared::{CacheOverride, ClientCertVerifyResult, FastlyStatus, FramingHeadersMode};
14use fastly_sys::fastly_http_req::SendErrorDetail;
15use fastly_sys::fastly_image_optimizer::{
16    transform_image_optimizer_request, ImageOptimizerErrorDetail, ImageOptimizerTransformConfig,
17    ImageOptimizerTransformConfigOptions,
18};
19use http::header::{HeaderName, HeaderValue};
20use http::{Method, Version};
21use mime::Mime;
22use pending::PendingRequestHandle;
23use serde::de::DeserializeOwned;
24use serde::Serialize;
25use std::borrow::Cow;
26use std::fmt::Display;
27use std::io::{BufRead, Write};
28use std::net::IpAddr;
29use std::ops::Deref;
30use std::sync::Arc;
31use thiserror::Error;
32use url::Url;
33
34pub use pending::{select, PendingRequest, PollResult};
35
36pub(crate) mod handle;
37pub(crate) mod pending;
38
39/// An HTTP request, including body, headers, method, and URL.
40///
41/// # Getting the client request
42///
43/// Call [`Request::from_client()`] to get the client request being handled by this execution of the
44/// Compute program.
45///
46/// # Creation and conversion
47///
48/// New requests can be created programmatically with [`Request::new()`]. In addition, there are
49/// convenience constructors like [`Request::get()`] which automatically select the appropriate
50/// method.
51///
52/// For interoperability with other Rust libraries, [`Request`] can be converted to and from the
53/// [`http`] crate's [`http::Request`] type using the [`From`][`Self::from()`] and
54/// [`Into`][`Into::into()`] traits.
55///
56/// # Sending backend requests
57///
58/// Requests can be sent to a backend in blocking or asynchronous fashion using
59/// [`send()`][`Self::send()`], [`send_async()`][`Self::send_async()`], or
60/// [`send_async_streaming()`][`Self::send_async_streaming()`].
61///
62/// # Builder-style methods
63///
64/// [`Request`] can be used as a
65/// [builder](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html), allowing requests to
66/// be constructed and used through method chaining. Methods with the `with_` name prefix, such as
67/// [`with_header()`][`Self::with_header()`], return `Self` to allow chaining. The builder style is
68/// typically most useful when constructing and using a request in a single expression. For example:
69///
70/// ```no_run
71/// # use fastly::{Error, Request};
72/// # fn f() -> Result<(), Error> {
73/// Request::get("https://example.com")
74///     .with_header("my-header", "hello!")
75///     .with_header("my-other-header", "Здравствуйте!")
76///     .send("example_backend")?;
77/// # Ok(()) }
78/// ```
79///
80/// # Setter methods
81///
82/// Setter methods, such as [`set_header()`][`Self::set_header()`], are prefixed by `set_`, and can
83/// be used interchangeably with the builder-style methods, allowing you to mix and match styles
84/// based on what is most convenient for your program. Setter methods tend to work better than
85/// builder-style methods when constructing a request involves conditional branches or loops. For
86/// example:
87///
88/// ```no_run
89/// # use fastly::{Error, Request};
90/// # fn f(needs_translation: bool) -> Result<(), Error> {
91/// let mut req = Request::get("https://example.com").with_header("my-header", "hello!");
92/// if needs_translation {
93///     req.set_header("my-other-header", "Здравствуйте!");
94/// }
95/// req.send("example_backend")?;
96/// # Ok(()) }
97/// ```
98#[derive(Debug)]
99pub struct Request {
100    lazy_handle: LazyHandle<RequestHandle>,
101    body: Option<Body>,
102    pub(crate) metadata: FastlyRequestMetadata,
103}
104
105/// Anything that we need to make a full roundtrip through the `http` types that doesn't have a more
106/// concrete corresponding type.
107#[derive(Debug, Clone)]
108pub(crate) struct FastlyRequestMetadata {
109    pub(crate) cache_override: CacheOverride,
110    is_from_client: bool,
111    auto_decompress_response: ContentEncodings,
112    framing_headers_mode: FramingHeadersMode,
113    pub(crate) override_cache_key: Option<CacheKeyGen>,
114    before_send: Option<BeforeSend>,
115    after_send: Option<AfterSend>,
116    image_opto_options: Option<image_optimizer::ImageOptimizerOptions>,
117}
118
119impl FastlyRequestMetadata {
120    fn new() -> Self {
121        Self {
122            cache_override: CacheOverride::default(),
123            is_from_client: false,
124            auto_decompress_response: ContentEncodings::empty(),
125            framing_headers_mode: FramingHeadersMode::Automatic,
126            override_cache_key: None,
127            before_send: None,
128            after_send: None,
129            image_opto_options: None,
130        }
131    }
132
133    fn invoke_before_send(&self, req: &mut Request) -> Result<(), SendErrorCause> {
134        if let Some(f) = &self.before_send {
135            (f.before_send)(req)
136        } else {
137            Ok(())
138        }
139    }
140
141    fn flush_to_handle(&self, req_handle: &mut RequestHandle) {
142        req_handle.set_cache_override(&self.cache_override);
143        req_handle.set_auto_decompress_response(self.auto_decompress_response);
144        req_handle.set_framing_headers_mode(self.framing_headers_mode);
145    }
146}
147
148#[derive(Debug, Clone)]
149struct FastlyClientMetadata {
150    client_ip_addr: Option<IpAddr>,
151    server_ip_addr: Option<IpAddr>,
152    client_request_id: Option<String>,
153    fastly_key_is_valid: bool,
154}
155
156#[derive(Debug, Clone)]
157struct FastlyClientFingerprintMetadata {
158    client_h2_fingerprint: Option<String>,
159    client_oh_fingerprint: Option<String>,
160    client_compliance_region: Option<String>,
161    client_ddos_detected: Option<bool>,
162}
163
164#[derive(Debug, Clone)]
165struct FastlyClientTlsMetadata {
166    client_tls_client_hello: Bytes,
167    client_tls_client_servername: Option<String>,
168    client_tls_ja3_md5: Option<[u8; 16]>,
169    client_tls_ja4: Option<String>,
170    #[allow(unused)]
171    client_tls_raw_certificate_bytes: Option<Bytes>,
172    client_tls_cert_verify_result: Option<ClientCertVerifyResult>,
173    #[allow(unused)]
174    client_tls_cipher_openssl_name_bytes: Bytes,
175    #[allow(unused)]
176    client_tls_protocol_bytes: Option<Bytes>,
177}
178
179#[derive(Debug, Clone)]
180struct FastlyOriginalHeadersMetadata {
181    count: u32,
182    header_names: Result<Vec<String>, BufferSizeError>,
183}
184
185/// Categories of detected bots.
186#[derive(Debug, Clone, PartialEq, Eq)]
187#[non_exhaustive]
188pub enum BotCategory {
189    /// Bot detection was not executed, or no bot was detected.
190    None,
191    /// A suspected bot.
192    Suspected,
193    /// Tools that make content accessible (e.g., screen readers).
194    Accessibility,
195    /// Crawlers used for training AIs and LLMs, generally used for building AI models or indexes.
196    AiCrawler,
197    /// Fetchers used by AIs and LLMs for enriching results in response to a user query.
198    AiFetcher,
199    /// Tools that extract content from websites to be used elsewhere.
200    ContentFetcher,
201    /// Tools that access your website to monitor things like performance, uptime, and proving domain control.
202    MonitoringSiteTools,
203    /// Crawlers from online marketing platforms (e.g., Facebook, Pinterest).
204    OnlineMarketing,
205    /// Tools that access your website to show a preview of the page in other online services and social media platforms.
206    PagePreview,
207    /// Integration with other platforms by accessing the website's API, notably Webhooks.
208    PlatformIntegrations,
209    /// Commercial and academic tools that collect and analyze data for research purposes.
210    Research,
211    /// Crawlers that index your website for search engines.
212    SearchEngineCrawler,
213    /// Tools that support search engine optimization tasks (e.g., link analysis, ranking).
214    SearchEngineSpecialization,
215    /// Security analysis tools that inspect your website for vulnerabilities, misconfigurations and other security features.
216    SecurityTools,
217    /// The detected bot belongs to a category not recognized by this SDK version.
218    Unknown,
219}
220
221impl From<u32> for BotCategory {
222    fn from(value: u32) -> Self {
223        use BotCategory::*;
224        match value {
225            0 => None,
226            1 => Suspected,
227            2 => Accessibility,
228            3 => AiCrawler,
229            4 => AiFetcher,
230            5 => ContentFetcher,
231            6 => MonitoringSiteTools,
232            7 => OnlineMarketing,
233            8 => PagePreview,
234            9 => PlatformIntegrations,
235            10 => Research,
236            11 => SearchEngineCrawler,
237            12 => SearchEngineSpecialization,
238            13 => SecurityTools,
239            _ => Unknown,
240        }
241    }
242}
243
244impl Display for BotCategory {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        match self {
247            Self::None => write!(f, "None"),
248            Self::Suspected => write!(f, "Suspected"),
249            Self::Accessibility => write!(f, "Accessibility"),
250            Self::AiCrawler => write!(f, "AiCrawler"),
251            Self::AiFetcher => write!(f, "AiFetcher"),
252            Self::ContentFetcher => write!(f, "ContentFetcher"),
253            Self::MonitoringSiteTools => write!(f, "MonitoringSiteTools"),
254            Self::OnlineMarketing => write!(f, "OnlineMarketing"),
255            Self::PagePreview => write!(f, "PagePreview"),
256            Self::PlatformIntegrations => write!(f, "PlatformIntegrations"),
257            Self::Research => write!(f, "Research"),
258            Self::SearchEngineCrawler => write!(f, "SearchEngineCrawler"),
259            Self::SearchEngineSpecialization => write!(f, "SearchEngineSpecialization"),
260            Self::SecurityTools => write!(f, "SecurityTools"),
261            Self::Unknown => write!(f, "Unknown"),
262        }
263    }
264}
265
266#[derive(Debug, Clone)]
267struct FastlyBotDetectionMetadata {
268    bot_analyzed: bool,
269    bot_detected: bool,
270    bot_name_bytes: Option<Bytes>,
271    category_name_bytes: Option<Bytes>,
272    category_kind: Option<BotCategory>,
273    bot_verified: Option<bool>,
274}
275
276#[derive(Clone)]
277pub(crate) enum CacheKeyGen {
278    Lazy(Arc<dyn Fn(&Request) -> CacheKey + Send + Sync>),
279    Set(CacheKey),
280}
281
282impl std::fmt::Debug for CacheKeyGen {
283    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
284        match self {
285            CacheKeyGen::Lazy(f) => fmt.write_fmt(format_args!("Lazy({:?})", Arc::as_ptr(f))),
286            CacheKeyGen::Set(k) => {
287                const DIGITS: &[u8] = b"0123456789ABCDEF";
288                let mut hex = [0; 64];
289                for (i, b) in k.iter().enumerate() {
290                    hex[i * 2] = DIGITS[(b >> 4) as usize];
291                    hex[i * 2 + 1] = DIGITS[(b & 0xf) as usize];
292                }
293                fmt.write_fmt(format_args!("Set({})", std::str::from_utf8(&hex).unwrap()))
294            }
295        }
296    }
297}
298
299#[derive(Clone)]
300struct BeforeSend {
301    before_send: Arc<dyn Fn(&mut Request) -> Result<(), SendErrorCause> + Send + Sync>,
302}
303
304impl std::fmt::Debug for BeforeSend {
305    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
306        fmt.write_str("<closure>")
307    }
308}
309
310#[derive(Clone)]
311struct AfterSend {
312    after_send: Arc<dyn Fn(&mut CandidateResponse) -> Result<(), SendErrorCause> + Send + Sync>,
313}
314
315impl std::fmt::Debug for AfterSend {
316    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
317        fmt.write_str("<closure>")
318    }
319}
320
321impl Request {
322    /// Get the client request being handled by this execution of the Compute program.
323    ///
324    /// # Panics
325    ///
326    /// This method panics if the client request has already been retrieved by this method or by
327    /// [the low-level handle API][`crate::handle`].
328    ///
329    /// # Incompatibility with [`fastly::main`][`crate::main`]
330    ///
331    /// This method cannot be used with [`fastly::main`][`crate::main`], as that attribute
332    /// implicitly calls [`Request::from_client()`] to populate the request argument. Use an
333    /// undecorated `main()` function instead, along with [`Response::send_to_client()`] or
334    /// [`Response::stream_to_client()`] to send a response to the client.
335    pub fn from_client() -> Request {
336        let (req_handle, body_handle) = self::handle::client_request_and_body();
337        let mut req = Request::from_handles(req_handle, Some(body_handle));
338        req.metadata.is_from_client = true;
339        req
340    }
341
342    /// Return `true` if this request is from the client of this execution of the Compute
343    /// program.
344    pub fn is_from_client(&self) -> bool {
345        self.metadata.is_from_client
346    }
347
348    /// Create a new request with the given method and URL, no headers, and an empty body.
349    ///
350    /// # Argument type conversion
351    ///
352    /// The method and URL arguments can be any types that implement [`ToMethod`] and [`ToUrl`],
353    /// respectively. See those traits for details on which types can be used and when panics may
354    /// arise during conversion.
355    pub fn new(method: impl ToMethod, url: impl ToUrl) -> Self {
356        Self {
357            lazy_handle: LazyHandle::detached()
358                .with_field(Version::HTTP_11)
359                .with_field(method.into_owned())
360                .with_field(url.into_owned())
361                .finish(),
362            body: None,
363            metadata: FastlyRequestMetadata::new(),
364        }
365    }
366
367    /// Make a new request with the same method, url, headers, and version of this request, but no
368    /// body.
369    ///
370    /// If you also need to clone the request body, use
371    /// [`clone_with_body()`][`Self::clone_with_body()`]
372    ///
373    /// # Examples
374    ///
375    /// ```no_run
376    /// # use fastly::Request;
377    /// let original = Request::post("https://example.com")
378    ///     .with_header("hello", "world!")
379    ///     .with_body("hello");
380    /// let new = original.clone_without_body();
381    /// assert_eq!(original.get_method(), new.get_method());
382    /// assert_eq!(original.get_url(), new.get_url());
383    /// assert_eq!(original.get_header("hello"), new.get_header("hello"));
384    /// assert_eq!(original.get_version(), new.get_version());
385    /// assert!(original.has_body());
386    /// assert!(!new.has_body());
387    /// ```
388    pub fn clone_without_body(&self) -> Request {
389        Self {
390            lazy_handle: self.lazy_handle.clone(),
391            body: None,
392            metadata: self.metadata.clone(),
393        }
394    }
395
396    /// Clone this request by reading in its body, and then writing the same body to the original
397    /// and the cloned request.
398    ///
399    /// This method requires mutable access to this request because reading from and writing to the
400    /// body can involve an HTTP connection.
401    ///
402    #[doc = include_str!("../../docs/snippets/clones-body.md")]
403    ///
404    /// # Examples
405    ///
406    /// ```no_run
407    /// # use fastly::Request;
408    /// let mut original = Request::post("https://example.com")
409    ///     .with_header("hello", "world!")
410    ///     .with_body("hello");
411    /// let mut new = original.clone_with_body();
412    /// assert_eq!(original.get_method(), new.get_method());
413    /// assert_eq!(original.get_url(), new.get_url());
414    /// assert_eq!(original.get_header("hello"), new.get_header("hello"));
415    /// assert_eq!(original.get_version(), new.get_version());
416    /// assert_eq!(original.take_body_bytes(), new.take_body_bytes());
417    /// ```
418    pub fn clone_with_body(&mut self) -> Request {
419        let mut new_req = self.clone_without_body();
420        if self.has_body() {
421            let mut body = self.take_body();
422
423            for chunk in body.read_chunks(4096) {
424                let chunk = chunk.expect("can read body chunk");
425                new_req
426                    .get_body_mut()
427                    .write_all(&chunk)
428                    .expect("failed to write to cloned body");
429                self.get_body_mut()
430                    .write_all(&chunk)
431                    .expect("failed to write to cloned body");
432            }
433
434            new_req
435                .get_body_mut()
436                .flush()
437                .expect("failed to flush cloned body");
438            self.get_body_mut()
439                .flush()
440                .expect("failed to flush cloned body");
441
442            if let Ok(trailers) = body.get_trailers() {
443                for (k, v) in trailers.iter() {
444                    self.get_body_mut().append_trailer(k, v);
445                    new_req.get_body_mut().append_trailer(k, v);
446                }
447            }
448        }
449        new_req
450    }
451
452    /// Create a new `GET` [`Request`] with the given URL, no headers, and an empty body.
453    ///
454    #[doc = include_str!("../../docs/snippets/url-argument.md")]
455    pub fn get(url: impl ToUrl) -> Self {
456        Self::new(Method::GET, url)
457    }
458
459    /// Create a new `HEAD` [`Request`] with the given URL, no headers, and an empty body.
460    ///
461    #[doc = include_str!("../../docs/snippets/url-argument.md")]
462    pub fn head(url: impl ToUrl) -> Self {
463        Self::new(Method::HEAD, url)
464    }
465
466    /// Create a new `POST` [`Request`] with the given URL, no headers, and an empty body.
467    ///
468    #[doc = include_str!("../../docs/snippets/url-argument.md")]
469    pub fn post(url: impl ToUrl) -> Self {
470        Self::new(Method::POST, url)
471    }
472
473    /// Create a new `PUT` [`Request`] with the given URL, no headers, and an empty body.
474    ///
475    #[doc = include_str!("../../docs/snippets/url-argument.md")]
476    pub fn put(url: impl ToUrl) -> Self {
477        Self::new(Method::PUT, url)
478    }
479
480    /// Create a new `DELETE` [`Request`] with the given URL, no headers, and an empty body.
481    ///
482    #[doc = include_str!("../../docs/snippets/url-argument.md")]
483    pub fn delete(url: impl ToUrl) -> Self {
484        Self::new(Method::DELETE, url)
485    }
486
487    /// Create a new `CONNECT` [`Request`] with the given URL, no headers, and an empty body.
488    ///
489    #[doc = include_str!("../../docs/snippets/url-argument.md")]
490    pub fn connect(url: impl ToUrl) -> Self {
491        Self::new(Method::CONNECT, url)
492    }
493
494    /// Create a new `OPTIONS` [`Request`] with the given URL, no headers, and an empty body.
495    ///
496    #[doc = include_str!("../../docs/snippets/url-argument.md")]
497    pub fn options(url: impl ToUrl) -> Self {
498        Self::new(Method::OPTIONS, url)
499    }
500
501    /// Create a new `TRACE` [`Request`] with the given URL, no headers, and an empty body.
502    ///
503    #[doc = include_str!("../../docs/snippets/url-argument.md")]
504    pub fn trace(url: impl ToUrl) -> Self {
505        Self::new(Method::TRACE, url)
506    }
507
508    /// Create a new `PATCH` [`Request`] with the given URL, no headers, and an empty body.
509    ///
510    #[doc = include_str!("../../docs/snippets/url-argument.md")]
511    pub fn patch(url: impl ToUrl) -> Self {
512        Self::new(Method::PATCH, url)
513    }
514
515    /// Builder-style equivalent of [`set_before_send()`][`Self::set_before_send()`].
516    pub fn with_before_send(
517        mut self,
518        f: impl Fn(&mut Request) -> Result<(), SendErrorCause> + Send + Sync + 'static,
519    ) -> Self {
520        self.set_before_send(f);
521        self
522    }
523
524    /// Sets a callback to be invoked if a request misses the cache,
525    /// allowing the request to be modified beforehand.
526    ///
527    /// This callback is useful when, for example, a backend requires an
528    /// additional header to be inserted, but that header is expensive to
529    /// produce. The callback will only be invoked if the original request
530    /// cannot be responded to from the cache, so the header is only computed
531    /// when it is truly needed.
532    ///
533    /// Note that the callback is only invoked if the request is a candidate for caching; it is not
534    /// invoked if the request is explicitly to the backend with [`Request::set_pass()`].
535    pub fn set_before_send(
536        &mut self,
537        f: impl Fn(&mut Request) -> Result<(), SendErrorCause> + Send + Sync + 'static,
538    ) {
539        self.metadata.before_send = Some(BeforeSend {
540            before_send: Arc::new(f),
541        })
542    }
543
544    /// Builder-style equivalent of [`set_after_send()`][`Self::set_after_send()`].
545    pub fn with_after_send(
546        mut self,
547        f: impl Fn(&mut CandidateResponse) -> Result<(), SendErrorCause> + Send + Sync + 'static,
548    ) -> Self {
549        self.set_after_send(f);
550        self
551    }
552
553    /// Sets a callback to be invoked after a response is returned from a
554    /// backend, but before it is stored into the cache.
555    ///
556    /// This callback allows for cache properties like TTL to be customized
557    /// beyond what the backend resopnse headers specify. It also allows for the
558    /// response itself to be modified prior to storing into the cache.
559    ///
560    /// Note that the callback is only invoked if the request is a candidate for caching; it is not
561    /// invoked if the request is explicitly to the backend with [`Request::set_pass()`].
562    pub fn set_after_send(
563        &mut self,
564        f: impl Fn(&mut CandidateResponse) -> Result<(), SendErrorCause> + Send + Sync + 'static,
565    ) {
566        self.metadata.after_send = Some(AfterSend {
567            after_send: Arc::new(f),
568        })
569    }
570
571    /// Retrieve a reponse for the request, either from cache or by sending it to the given backend
572    /// server. Returns once the response headers have been received, or an error occurs.
573    ///
574    #[doc = include_str!("../../docs/snippets/backend-argument.md")]
575    ///
576    /// # Examples
577    ///
578    /// Sending the client request to a backend without modification:
579    ///
580    /// ```no_run
581    /// # use fastly::Request;
582    /// let backend_resp = Request::from_client().send("example_backend").expect("request succeeds");
583    /// assert!(backend_resp.get_status().is_success());
584    /// ```
585    ///
586    /// Sending a synthetic request:
587    ///
588    /// ```no_run
589    /// # use fastly::Request;
590    /// let backend_resp = Request::get("https://example.com")
591    ///     .send("example_backend")
592    ///     .expect("request succeeds");
593    /// assert!(backend_resp.get_status().is_success());
594    /// ```
595    pub fn send(mut self, backend: impl ToBackend) -> Result<Response, SendError> {
596        let backend = backend.into_owned();
597        match validate_request(&self).and_then(|_| self.send_impl(backend.name())) {
598            Ok(mut resp) => {
599                resp.sent_req = Some(self);
600                resp.metadata.backend = Some(backend);
601                Ok(resp)
602            }
603            Err(err) => Err(SendError::new(backend.name(), self, err)),
604        }
605    }
606
607    fn must_use_guest_caching(&self) -> bool {
608        self.metadata.before_send.is_some() || self.metadata.after_send.is_some()
609    }
610
611    fn get_caching_mode(&self) -> Result<CachingMode, SendErrorCause> {
612        if self.metadata.cache_override.is_pass() {
613            // Pass requests have to go through the host for now, because we
614            // don't have SDK access to the direct pass setting
615            Ok(CachingMode::HostCaching)
616        } else if self.get_method() == "PURGE" {
617            // We don't yet implement guest-side interpretation of URL purges
618            Ok(CachingMode::HostCaching)
619        } else if let Some(io_opts) = &self.metadata.image_opto_options {
620            // Requests meant for Image Optimizer should not be cached at this point,
621            // as the caching behavior is determined by the origin image, which is
622            // fetched after the request reaches the Image Optimizer WASM service.
623            // The WASM service uses cache_on_behalf to insert the result into
624            // the service's cache.
625            Ok(CachingMode::ImageOptimizer(Box::new(io_opts.clone())))
626        } else if self.lazy_handle.get_handle().must_use_host_caching() {
627            // If the service cannot use guest-side caching, we cannot support send hooks:
628            if self.must_use_guest_caching() {
629                Err(SendErrorCause::HttpCacheApiUnsupported)
630            } else {
631                Ok(CachingMode::HostCaching)
632            }
633        } else {
634            Ok(CachingMode::GuestCaching)
635        }
636    }
637
638    fn send_impl(&mut self, backend: &str) -> Result<Response, SendErrorCause> {
639        match self.get_caching_mode()? {
640            CachingMode::GuestCaching => {
641                if !self.is_cacheable() {
642                    return self
643                        .take_request_handle()
644                        .send_without_caching(self.take_body_handle(), backend)
645                        .map(|r| Response::from(r).with_fastly_cache_headers(self));
646                }
647
648                let options = cache::LookupOptions {
649                    override_key: self.get_override_cache_key(),
650                    backend_name: backend.to_owned(),
651                };
652                // Clear out the cache key so that it's not inserted as a header
653                // (which is used for host-side caching mode)
654                self.metadata.override_cache_key = None;
655
656                let cache_handle =
657                    cache::transaction_lookup(self.lazy_handle.get_handle(), &options)?;
658                // force the lookup to await in the host, retrieving any errors synchronously
659                cache_handle.wait()?;
660
661                // is there a "usable" cached response (i.e. fresh or within SWR period)
662                if let Ok(mut resp) = cache_handle.get_found_response(true) {
663                    // if this is during SWR, we may be the "lucky winner" who is
664                    // tasked with performing a background revalidation
665                    if cache_handle.must_insert_or_update() {
666                        // since we don't have full `async fn` support, we begin
667                        // revalidation now, and put the data needed to complete it
668                        // into the _response_ we are about to return. when the
669                        // `background_revalidation` field is dropped (e.g. after
670                        // calling `Response::send_to_client()`), it will wait for
671                        // the backend response (if one hasn't arrived yet), invoke
672                        // any after-send hooks, and complete the cache transaction.
673                        resp.background_revalidation = self
674                            .send_async_for_caching(cache_handle, backend)
675                            .ok()
676                            .map(|pending| pending.into_background_revalidation());
677                    }
678
679                    // Meanwhile, whether fresh or in SWR, we can immediately return
680                    // the cached response:
681                    Ok(resp.with_fastly_cache_headers(self))
682                } else if !cache_handle.must_insert_or_update() {
683                    // Request collapsing has been disabled: pass the _original_ request through to the
684                    // origin without updating the cache.
685                    self.take_request_handle()
686                        .send_without_caching(self.take_body_handle(), backend)
687                        .map(|r| Response::from(r).with_fastly_cache_headers(self))
688                } else {
689                    self.send_async_for_caching(cache_handle, backend)?
690                        .into_candidate()?
691                        .apply_and_stream_back()
692                        .map(|r| r.with_fastly_cache_headers(self))
693                }
694            }
695            CachingMode::HostCaching => self
696                .take_request_handle()
697                .send(self.take_body_handle(), backend)
698                .map(Response::from),
699            CachingMode::ImageOptimizer(opts) => {
700                let req_handle = self.take_request_handle();
701                let mut error_detail = ImageOptimizerErrorDetail::uninitialized();
702                let mut image_opto_config_opts = ImageOptimizerTransformConfigOptions::empty();
703                image_opto_config_opts
704                    .insert(ImageOptimizerTransformConfigOptions::SDK_CLAIMS_OPTS);
705
706                let sdk_claims_opts = match opts.to_claims_opts() {
707                    Ok(o) => o,
708                    Err(e) => return Err(SendErrorCause::Custom(e.into_anyhow())),
709                };
710
711                let mut image_opto_config = ImageOptimizerTransformConfig::default();
712                image_opto_config.sdk_claims_opts = sdk_claims_opts.as_ptr();
713                image_opto_config.sdk_claims_opts_len = sdk_claims_opts.len() as u32;
714
715                let orig_req_body_handle = BodyHandle::INVALID;
716                let mut resp_handle_out = ResponseHandle::INVALID;
717                let mut body_handle_out = BodyHandle::INVALID;
718                let status;
719                unsafe {
720                    status = transform_image_optimizer_request(
721                        req_handle.as_u32(),
722                        orig_req_body_handle.as_u32(),
723                        backend.as_ptr(),
724                        backend.len(),
725                        image_opto_config_opts,
726                        &image_opto_config,
727                        &mut error_detail,
728                        resp_handle_out.as_u32_mut(),
729                        body_handle_out.as_u32_mut(),
730                    );
731                }
732
733                if status == FastlyStatus::UNSUPPORTED {
734                    return Err(SendErrorCause::ImageOptimizerUnsupported);
735                }
736
737                if status != FastlyStatus::OK {
738                    return Err(SendErrorCause::InternalError(Some(status)));
739                }
740
741                if resp_handle_out.is_invalid() || body_handle_out.is_invalid() {
742                    return Err(SendErrorCause::InternalError(Some(FastlyStatus::INVAL)));
743                }
744                Ok(Response::from((resp_handle_out, body_handle_out)))
745            }
746        }
747    }
748
749    /// Actually send a backend request, applying guest-side caching hooks.
750    ///
751    /// Returns a `PendingBackendRequestForCaching` (i.e. an async response) so
752    /// that completion can be performed asynchronously for background
753    /// revalidation.
754    fn send_async_for_caching(
755        &mut self,
756        cache_handle: HttpCacheHandle,
757        backend: &str,
758    ) -> Result<PendingBackendRequestForCaching, SendErrorCause> {
759        let mut req = Request::from(cache_handle.get_suggested_backend_request()?)
760            .with_body(self.take_body())
761            .with_metadata(self.metadata.clone());
762        self.metadata.invoke_before_send(&mut req)?;
763        let final_cache_override = req.metadata.cache_override.clone();
764        let (req_handle, req_body_handle) = req.into_handles();
765        let pending_req_handle = req_handle
766            .send_async_without_caching(req_body_handle.unwrap_or_else(BodyHandle::new), backend)?;
767        Ok(PendingBackendRequestForCaching {
768            cache_handle,
769            pending_req_handle,
770            after_send: self.metadata.after_send.clone(),
771            cache_override: final_cache_override,
772        })
773    }
774
775    /// Begin sending the request to the given backend server, and return a [`PendingRequest`] that
776    /// can yield the backend response or an error.
777    ///
778    /// This method returns as soon as the request begins sending to the backend, and transmission
779    /// of the request body and headers will continue in the background.
780    ///
781    /// This method allows for sending more than one request at once and receiving their responses
782    /// in arbitrary orders. See [`PendingRequest`] for more details on how to wait on, poll, or
783    /// select between pending requests.
784    ///
785    /// This method is also useful for sending requests where the response is unimportant, but the
786    /// request may take longer than the Compute program is able to run, as the request will
787    /// continue sending even after the program that initiated it exits.
788    ///
789    /// Note that the implementation of `send_async` does not currently support also using
790    /// [Request::set_before_send] or [Request::set_after_send]. Attempts to call this method after
791    /// also setting the callbacks will result in an [SendErrorCause::HttpCacheApiUnsupported]
792    /// error.
793    ///
794    #[doc = include_str!("../../docs/snippets/backend-argument.md")]
795    ///
796    /// # Examples
797    ///
798    /// Sending a request to two backends and returning whichever response finishes first:
799    ///
800    /// ```no_run
801    /// # use fastly::Request;
802    /// let backend_resp_1 = Request::get("https://example.com/")
803    ///     .send_async("example_backend_1")
804    ///     .expect("request 1 begins sending");
805    /// let backend_resp_2 = Request::get("https://example.com/")
806    ///     .send_async("example_backend_2")
807    ///     .expect("request 2 begins sending");
808    /// let (resp, _) = fastly::http::request::select(vec![backend_resp_1, backend_resp_2]);
809    /// resp.expect("request succeeds").send_to_client();
810    /// ```
811    ///
812    /// Sending a long-running request and ignoring its result so that the program can exit before
813    /// it completes:
814    ///
815    /// ```no_run
816    /// # use fastly::Request;
817    /// # let some_large_file = vec![0u8];
818    /// let _ = Request::post("https://example.com")
819    ///     .with_body(some_large_file)
820    ///     .send_async("example_backend");
821    /// ```
822    pub fn send_async(self, backend: impl ToBackend) -> Result<PendingRequest, SendError> {
823        let backend = backend.into_owned();
824
825        // programmable HTTP caching is not currently support for async sending
826        if self.must_use_guest_caching() {
827            return Err(SendError::new(
828                backend.name(),
829                self,
830                SendErrorCause::HttpCacheApiUnsupported,
831            ));
832        }
833
834        // TODO 2024-07-31: this now forces request headers to be copied an extra
835        // time, while it used to be ~free. Once async fn support is added and
836        // pending request select is no longer present, we should be able to drop
837        // this forced clone and let users clone if needed.
838        let cloned = self.clone_without_body();
839
840        match validate_request(&self).and_then(|_| {
841            let (req_handle, body_handle) = self.into_handles();
842            let body_handle = body_handle.unwrap_or_else(BodyHandle::new);
843            req_handle.send_async(body_handle, backend.name())
844        }) {
845            Ok(pending_req_handle) => Ok(PendingRequest::new(pending_req_handle, backend, cloned)),
846            Err(err) => Err(SendError::new(backend.name(), cloned, err)),
847        }
848    }
849
850    /// Begin sending the request to the given backend server, and return a [`PendingRequest`] that
851    /// can yield the backend response or an error along with a [`StreamingBody`] that can accept
852    /// further data to send.
853    ///
854    /// The backend connection is only closed once [`StreamingBody::finish()`] is called. The
855    /// [`PendingRequest`] will not yield a [`Response`] until the [`StreamingBody`] is finished.
856    ///
857    /// This method is most useful for programs that do some sort of processing or inspection of a
858    /// potentially-large client request body. Streaming allows the program to operate on small
859    /// parts of the body rather than having to read it all into memory at once.
860    ///
861    /// This method returns as soon as the request begins sending to the backend, and transmission
862    /// of the request body and headers will continue in the background.
863    ///
864    /// Note that the implementation of `send_async_streaming` does not currently support also using
865    /// [Request::set_before_send] or [Request::set_after_send]. Attempts to call this method after
866    /// also setting the callbacks will result in an [SendErrorCause::HttpCacheApiUnsupported]
867    /// error.
868    ///
869    /// # Examples
870    ///
871    /// Count the number of lines in a UTF-8 client request body while sending it to the backend:
872    ///
873    /// ```no_run
874    /// # use fastly::Request;
875    /// # fn f() -> Result<(), Box<dyn std::error::Error>> {
876    /// use std::io::{BufRead, Write};
877    ///
878    /// let mut req = Request::from_client();
879    /// // Take the body so we can iterate through its lines later
880    /// let req_body = req.take_body();
881    /// // Start sending the client request to the client with a now-empty body
882    /// let (mut backend_body, pending_req) = req
883    ///     .send_async_streaming("example_backend")
884    ///     .expect("request begins sending");
885    ///
886    /// let mut num_lines = 0;
887    /// for line in req_body.lines() {
888    ///     let line = line?;
889    ///     num_lines += 1;
890    ///     // Write the line to the streaming backend body
891    ///     backend_body.write_all(line.as_bytes())?;
892    /// }
893    /// // Finish the streaming body to allow the backend connection to close
894    /// backend_body.finish()?;
895    ///
896    /// println!("client request body contained {} lines", num_lines);
897    /// # Ok(()) }
898    /// ```
899    pub fn send_async_streaming(
900        self,
901        backend: impl ToBackend,
902    ) -> Result<(StreamingBody, PendingRequest), SendError> {
903        let backend = backend.into_owned();
904
905        // programmable HTTP caching is not currently support for async sending
906        if self.must_use_guest_caching() {
907            return Err(SendError::new(
908                backend.name(),
909                self,
910                SendErrorCause::HttpCacheApiUnsupported,
911            ));
912        }
913
914        // TODO 2024-07-31: this now forces request headers to be copied an extra
915        // time, while it used to be ~free. Once async fn support is added and
916        // pending request select is no longer present, we should be able to drop
917        // this forced clone and let users clone if needed.
918        let cloned = self.clone_without_body();
919
920        match validate_request(&self).and_then(|_| {
921            let (req_handle, body_handle) = self.into_handles();
922            let body_handle = body_handle.unwrap_or_else(BodyHandle::new);
923            req_handle.send_async_streaming(body_handle, backend.name())
924        }) {
925            Ok((streaming_body_handle, pending_req_handle)) => {
926                let pending_req = PendingRequest::new(pending_req_handle, backend, cloned);
927                Ok((streaming_body_handle.into(), pending_req))
928            }
929            Err(err) => Err(SendError::new(backend.name(), cloned, err)),
930        }
931    }
932
933    /// Builder-style equivalent of [`set_body()`][`Self::set_body()`].
934    pub fn with_body(mut self, body: impl Into<Body>) -> Self {
935        self.set_body(body);
936        self
937    }
938
939    /// Returns `true` if this request has a body.
940    pub fn has_body(&self) -> bool {
941        self.body.is_some()
942    }
943
944    /// Get a mutable reference to the body of this request.
945    ///
946    #[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
947    ///
948    /// # Examples
949    ///
950    /// ```no_run
951    /// # use fastly::Request;
952    /// # fn f() -> Result<(), Box<dyn std::error::Error>> {
953    /// use std::io::Write;
954    ///
955    /// let mut req = Request::post("https://example.com").with_body("hello,");
956    /// write!(req.get_body_mut(), " world!")?;
957    /// assert_eq!(&req.into_body_str(), "hello, world!");
958    /// # Ok(()) }
959    /// ```
960    pub fn get_body_mut(&mut self) -> &mut Body {
961        self.body.get_or_insert_with(Body::new)
962    }
963
964    /// Get a shared reference to the body of this request if it has one, otherwise return `None`.
965    ///
966    /// # Examples
967    ///
968    /// ```no_run
969    /// # use fastly::Request;
970    /// # fn f() -> Result<(), Box<dyn std::error::Error>> {
971    /// use std::io::Write;
972    ///
973    /// let mut req = Request::post("https://example.com");
974    /// assert!(req.try_get_body_mut().is_none());
975    ///
976    /// req.set_body("hello,");
977    /// write!(req.try_get_body_mut().expect("body now exists"), " world!")?;
978    /// assert_eq!(&req.into_body_str(), "hello, world!");
979    /// # Ok(()) }
980    /// ```
981    pub fn try_get_body_mut(&mut self) -> Option<&mut Body> {
982        self.body.as_mut()
983    }
984
985    /// Get a prefix of this request's body containing up to the given number of bytes.
986    ///
987    /// See [`Body::get_prefix_mut()`] for details.
988    pub fn get_body_prefix_mut(&mut self, length: usize) -> body::Prefix<'_> {
989        self.get_body_mut().get_prefix_mut(length)
990    }
991
992    /// Get a prefix of this request's body as a string containing up to the given number of bytes.
993    ///
994    /// See [`Body::get_prefix_str_mut()`] for details.
995    ///
996    /// # Panics
997    ///
998    /// If the prefix contains invalid UTF-8 bytes, this function will panic. The exception to this
999    /// is if the bytes are invalid because a multi-byte codepoint is cut off by the requested
1000    /// prefix length. In this case, the invalid bytes are left off the end of the prefix.
1001    ///
1002    /// To explicitly handle the possibility of invalid UTF-8 bytes, use
1003    /// [`try_get_body_prefix_str_mut()`][`Self::try_get_body_prefix_str_mut()`], which returns an
1004    /// error on failure rather than panicking.
1005    pub fn get_body_prefix_str_mut(&mut self, length: usize) -> body::PrefixString<'_> {
1006        self.get_body_mut().get_prefix_str_mut(length)
1007    }
1008
1009    /// Try to get a prefix of the body as a string containing up to the given number of bytes.
1010    ///
1011    /// See [`Body::try_get_prefix_str_mut()`] for details.
1012    pub fn try_get_body_prefix_str_mut(
1013        &mut self,
1014        length: usize,
1015    ) -> Result<body::PrefixString<'_>, std::str::Utf8Error> {
1016        self.get_body_mut().try_get_prefix_str_mut(length)
1017    }
1018
1019    /// Set the given value as the request's body.
1020    #[doc = include_str!("../../docs/snippets/body-argument.md")]
1021    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1022    pub fn set_body(&mut self, body: impl Into<Body>) {
1023        self.body = Some(body.into());
1024    }
1025
1026    /// Take and return the body from this request.
1027    ///
1028    /// After calling this method, this request will no longer have a body.
1029    ///
1030    #[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
1031    pub fn take_body(&mut self) -> Body {
1032        self.body.take().unwrap_or_else(Body::new)
1033    }
1034
1035    /// Take and return the body from this request if it has one, otherwise return `None`.
1036    ///
1037    /// After calling this method, this request will no longer have a body.
1038    pub fn try_take_body(&mut self) -> Option<Body> {
1039        self.body.take()
1040    }
1041
1042    fn take_body_handle(&mut self) -> BodyHandle {
1043        self.take_body().into_handle()
1044    }
1045
1046    /// Append another [`Body`] to the body of this request without reading or writing any body
1047    /// contents.
1048    ///
1049    /// If this request does not have a body, the appended body is set as the request's body.
1050    ///
1051    #[doc = include_str!("../../docs/snippets/body-append-constant-time.md")]
1052    ///
1053    /// This method should be used when combining bodies that have not necessarily been read yet,
1054    /// such as the body of the client. To append contents that are already in memory as strings or
1055    /// bytes, you should instead use [`get_body_mut()`][`Self::get_body_mut()`] to write the
1056    /// contents to the end of the body.
1057    ///
1058    /// # Examples
1059    ///
1060    /// ```no_run
1061    /// # use fastly::Request;
1062    /// # fn f() -> Result<(), Box<dyn std::error::Error>> {
1063    /// let mut req = Request::post("https://example.com").with_body("hello! client says: ");
1064    /// req.append_body(Request::from_client().into_body());
1065    /// req.send("example_backend")?;
1066    /// # Ok(()) }
1067    /// ```
1068    pub fn append_body(&mut self, other: Body) {
1069        if let Some(ref mut body) = &mut self.body {
1070            body.append(other);
1071        } else {
1072            self.body = Some(other);
1073        }
1074    }
1075
1076    /// Consume the request and return its body as a byte vector.
1077    ///
1078    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1079    ///
1080    /// # Examples
1081    ///
1082    /// ```no_run
1083    /// # use fastly::Request;
1084    /// let req = Request::post("https://example.com").with_body_text_plain("hello, world!");
1085    /// let bytes = req.into_body_bytes();
1086    /// assert_eq!(&bytes, b"hello, world!");
1087    pub fn into_body_bytes(mut self) -> Vec<u8> {
1088        self.take_body_bytes()
1089    }
1090
1091    /// Consume the request and return its body as a string.
1092    ///
1093    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1094    ///
1095    /// # Panics
1096    ///
1097    #[doc = include_str!("../../docs/snippets/panics-reqresp-intobody-utf8.md")]
1098    ///
1099    /// # Examples
1100    ///
1101    /// ```no_run
1102    /// # use fastly::Request;
1103    /// let req = Request::post("https://example.com").with_body("hello, world!");
1104    /// let string = req.into_body_str();
1105    /// assert_eq!(&string, "hello, world!");
1106    /// ```
1107    pub fn into_body_str(mut self) -> String {
1108        self.take_body_str()
1109    }
1110
1111    /// Consume the request and return its body as a string, including invalid characters.
1112    ///
1113    #[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
1114    ///
1115    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1116    ///
1117    /// # Examples
1118    ///
1119    /// ```no_run
1120    /// # use fastly::Request;
1121    /// let mut req = Request::post("https://example.com");
1122    /// req.set_body_octet_stream(b"\xF0\x90\x80 hello, world!");
1123    /// let string = req.into_body_str_lossy();
1124    /// assert_eq!(&string, "� hello, world!");
1125    /// ```
1126    pub fn into_body_str_lossy(mut self) -> String {
1127        self.take_body_str_lossy()
1128    }
1129
1130    /// Consume the request and return its body.
1131    ///
1132    #[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
1133    pub fn into_body(self) -> Body {
1134        self.body.unwrap_or_else(Body::new)
1135    }
1136
1137    /// Consume the request and return its body if it has one, otherwise return `None`.
1138    pub fn try_into_body(self) -> Option<Body> {
1139        self.body
1140    }
1141
1142    /// Builder-style equivalent of [`set_body_text_plain()`][`Self::set_body_text_plain()`].
1143    pub fn with_body_text_plain(mut self, body: &str) -> Self {
1144        self.set_body_text_plain(body);
1145        self
1146    }
1147
1148    /// Set the given string as the request's body with content type `text/plain; charset=UTF-8`.
1149    ///
1150    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1151    #[doc = include_str!("../../docs/snippets/sets-text-plain.md")]
1152    ///
1153    /// # Examples
1154    ///
1155    /// ```no_run
1156    /// # use fastly::Request;
1157    /// let mut req = Request::post("https://example.com");
1158    /// req.set_body_text_plain("hello, world!");
1159    /// assert_eq!(req.get_content_type(), Some(fastly::mime::TEXT_PLAIN_UTF_8));
1160    /// assert_eq!(&req.into_body_str(), "hello, world!");
1161    /// ```
1162    pub fn set_body_text_plain(&mut self, body: &str) {
1163        self.body = Some(Body::from(body));
1164        self.set_content_type(mime::TEXT_PLAIN_UTF_8);
1165    }
1166
1167    /// Builder-style equivalent of [`set_body_text_html()`][`Self::set_body_text_html()`].
1168    pub fn with_body_text_html(mut self, body: &str) -> Self {
1169        self.set_body_text_html(body);
1170        self
1171    }
1172
1173    /// Set the given string as the request's body with content type `text/html; charset=UTF-8`.
1174    ///
1175    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1176    #[doc = include_str!("../../docs/snippets/sets-text-html.md")]
1177    ///
1178    /// # Examples
1179    ///
1180    /// ```no_run
1181    /// # use fastly::Request;
1182    /// let mut req = Request::post("https://example.com");
1183    /// req.set_body_text_html("<p>hello, world!</p>");
1184    /// assert_eq!(req.get_content_type(), Some(fastly::mime::TEXT_HTML_UTF_8));
1185    /// assert_eq!(&req.into_body_str(), "<p>hello, world!</p>");
1186    /// ```
1187    pub fn set_body_text_html(&mut self, body: &str) {
1188        self.body = Some(Body::from(body));
1189        self.set_content_type(mime::TEXT_HTML_UTF_8);
1190    }
1191
1192    /// Take and return the body from this request as a string.
1193    ///
1194    /// After calling this method, this request will no longer have a body.
1195    ///
1196    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1197    ///
1198    /// # Panics
1199    ///
1200    #[doc = include_str!("../../docs/snippets/panics-reqresp-takebody-utf8.md")]
1201    ///
1202    /// # Examples
1203    ///
1204    /// ```no_run
1205    /// # use fastly::Request;
1206    /// let mut req = Request::post("https://example.com").with_body("hello, world!");
1207    /// let string = req.take_body_str();
1208    /// assert!(req.try_take_body().is_none());
1209    /// assert_eq!(&string, "hello, world!");
1210    /// ```
1211    pub fn take_body_str(&mut self) -> String {
1212        if let Some(body) = self.try_take_body() {
1213            body.into_string()
1214        } else {
1215            String::new()
1216        }
1217    }
1218
1219    /// Take and return the body from this request as a string, including invalid characters.
1220    ///
1221    #[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
1222    ///
1223    /// After calling this method, this request will no longer have a body.
1224    ///
1225    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1226    ///
1227    /// # Examples
1228    ///
1229    /// ```no_run
1230    /// # use fastly::Request;
1231    /// let mut req = Request::post("https://example.com");
1232    /// req.set_body_octet_stream(b"\xF0\x90\x80 hello, world!");
1233    /// let string = req.take_body_str_lossy();
1234    /// assert!(req.try_take_body().is_none());
1235    /// assert_eq!(&string, "� hello, world!");
1236    /// ```
1237    pub fn take_body_str_lossy(&mut self) -> String {
1238        if let Some(body) = self.try_take_body() {
1239            String::from_utf8_lossy(&body.into_bytes()).to_string()
1240        } else {
1241            String::new()
1242        }
1243    }
1244
1245    /// Return a [`Lines`][`std::io::Lines`] iterator that reads the request body a line at a time.
1246    ///
1247    /// # Examples
1248    ///
1249    /// ```no_run
1250    /// # use fastly::{Body, Request};
1251    /// use std::io::Write;
1252    ///
1253    /// fn remove_es(req: &mut Request) -> Result<(), Box<dyn std::error::Error>> {
1254    ///     let mut no_es = Body::new();
1255    ///     for line in req.read_body_lines() {
1256    ///         writeln!(no_es, "{}", line.unwrap().replace("e", ""))?;
1257    ///     }
1258    ///     req.set_body(no_es);
1259    ///     Ok(())
1260    /// }
1261    /// ```
1262    pub fn read_body_lines(&mut self) -> std::io::Lines<&mut Body> {
1263        self.get_body_mut().lines()
1264    }
1265
1266    /// Builder-style equivalent of [`set_body_octet_stream()`][`Self::set_body_octet_stream()`].
1267    pub fn with_body_octet_stream(mut self, body: &[u8]) -> Self {
1268        self.set_body_octet_stream(body);
1269        self
1270    }
1271
1272    /// Set the given bytes as the request's body.
1273    ///
1274    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1275    #[doc = include_str!("../../docs/snippets/sets-app-octet-stream.md")]
1276    ///
1277    /// # Examples
1278    ///
1279    /// ```no_run
1280    /// # use fastly::Request;
1281    /// let mut req = Request::post("https://example.com");
1282    /// req.set_body_octet_stream(b"hello, world!");
1283    /// assert_eq!(req.get_content_type(), Some(fastly::mime::APPLICATION_OCTET_STREAM));
1284    /// assert_eq!(&req.into_body_bytes(), b"hello, world!");
1285    /// ```
1286    pub fn set_body_octet_stream(&mut self, body: &[u8]) {
1287        self.body = Some(Body::from(body));
1288        self.set_content_type(mime::APPLICATION_OCTET_STREAM);
1289    }
1290
1291    /// Take and return the body from this request as a string.
1292    ///
1293    /// After calling this method, this request will no longer have a body.
1294    ///
1295    #[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
1296    ///
1297    /// # Examples
1298    ///
1299    /// ```no_run
1300    /// # use fastly::Request;
1301    /// let mut req = Request::post("https://example.com").with_body_text_plain("hello, world!");
1302    /// let bytes = req.take_body_bytes();
1303    /// assert!(req.try_take_body().is_none());
1304    /// assert_eq!(&bytes, b"hello, world!");
1305    /// ```
1306    pub fn take_body_bytes(&mut self) -> Vec<u8> {
1307        if let Some(body) = self.try_take_body() {
1308            body.into_bytes()
1309        } else {
1310            Vec::new()
1311        }
1312    }
1313
1314    /// Return an iterator that reads the request body in chunks of at most the given number of
1315    /// bytes.
1316    ///
1317    /// If `chunk_size` does not evenly divide the length of the body, then the last chunk will not
1318    /// have length `chunk_size`.
1319    ///
1320    /// # Examples
1321    ///
1322    /// ```no_run
1323    /// # use fastly::{Body, Request};
1324    /// use std::io::Write;
1325    /// fn remove_0s(req: &mut Request) -> Result<(), Box<dyn std::error::Error>> {
1326    ///     let mut no_0s = Body::new();
1327    ///     for chunk in req.read_body_chunks(4096) {
1328    ///         let mut chunk = chunk?;
1329    ///         chunk.retain(|b| *b != 0);
1330    ///         no_0s.write_all(&chunk)?;
1331    ///     }
1332    ///     req.set_body(no_0s);
1333    ///     Ok(())
1334    /// }
1335    /// ```
1336    pub fn read_body_chunks(
1337        &mut self,
1338        chunk_size: usize,
1339    ) -> impl Iterator<Item = Result<Vec<u8>, std::io::Error>> + '_ {
1340        self.get_body_mut().read_chunks(chunk_size)
1341    }
1342
1343    /// Builder-style equivalent of [`set_body_json()`][Self::set_body_json()`].
1344    pub fn with_body_json(mut self, value: &impl Serialize) -> Result<Self, serde_json::Error> {
1345        self.set_body_json(value)?;
1346        Ok(self)
1347    }
1348
1349    /// Convert the given value to JSON and set that JSON as the request's body.
1350    ///
1351    /// The given value must implement [`serde::Serialize`]. You can either implement that trait for
1352    /// your own custom type, or use [`serde_json::Value`] to create untyped JSON values. See
1353    /// [`serde_json`] for details.
1354    ///
1355    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1356    ///
1357    /// # Content type
1358    ///
1359    /// This method sets the content type to `application/json`.
1360    ///
1361    /// # Errors
1362    ///
1363    /// This method returns [`serde_json::Error`] if serialization fails.
1364    ///
1365    /// # Examples
1366    ///
1367    /// Using a type that derives [`serde::Serialize`]:
1368    ///
1369    /// ```no_run
1370    /// # use fastly::Request;
1371    /// #[derive(serde::Serialize)]
1372    /// struct MyData {
1373    ///     name: String,
1374    ///     count: u64,
1375    /// }
1376    /// let my_data = MyData { name: "Computers".to_string(), count: 1024 };
1377    /// let mut req = Request::post("https://example.com");
1378    /// req.set_body_json(&my_data).unwrap();
1379    /// assert_eq!(req.get_content_type(), Some(fastly::mime::APPLICATION_JSON));
1380    /// assert_eq!(&req.into_body_str(), r#"{"name":"Computers","count":1024}"#);
1381    /// ```
1382    ///
1383    /// Using untyped JSON and the [`serde_json::json`] macro:
1384    ///
1385    /// ```no_run
1386    /// # use fastly::Request;
1387    /// let my_data = serde_json::json!({
1388    ///     "name": "Computers",
1389    ///     "count": 1024,
1390    /// });
1391    /// let mut req = Request::post("https://example.com");
1392    /// req.set_body_json(&my_data).unwrap();
1393    /// assert_eq!(req.get_content_type(), Some(fastly::mime::APPLICATION_JSON));
1394    /// assert_eq!(&req.into_body_str(), r#"{"count":1024,"name":"Computers"}"#);
1395    /// ```
1396    pub fn set_body_json(&mut self, value: &impl Serialize) -> Result<(), serde_json::Error> {
1397        self.body = Some(Body::new());
1398        serde_json::to_writer(self.get_body_mut(), value)?;
1399        self.set_content_type(mime::APPLICATION_JSON);
1400        Ok(())
1401    }
1402
1403    /// Take the request body and attempt to parse it as a JSON value.
1404    ///
1405    /// The return type must implement [`serde::Deserialize`] without any non-static lifetimes. You
1406    /// can either implement that trait for your own custom type, or use [`serde_json::Value`] to
1407    /// deserialize untyped JSON values. See [`serde_json`] for details.
1408    ///
1409    /// After calling this method, this request will no longer have a body.
1410    ///
1411    /// # Errors
1412    ///
1413    /// This method returns [`serde_json::Error`] if deserialization fails.
1414    ///
1415    /// # Examples
1416    ///
1417    /// Using a type that derives [`serde::de::DeserializeOwned`]:
1418    ///
1419    /// ```no_run
1420    /// # use fastly::Request;
1421    /// #[derive(serde::Deserialize)]
1422    /// struct MyData {
1423    ///     name: String,
1424    ///     count: u64,
1425    /// }
1426    /// let mut req = Request::post("https://example.com")
1427    ///     .with_body(r#"{"name":"Computers","count":1024}"#);
1428    /// let my_data = req.take_body_json::<MyData>().unwrap();
1429    /// assert_eq!(&my_data.name, "Computers");
1430    /// assert_eq!(my_data.count, 1024);
1431    /// ```
1432    ///
1433    /// Using untyped JSON with [`serde_json::Value`]:
1434    ///
1435    /// ```no_run
1436    /// # use fastly::Request;
1437    /// let my_data = serde_json::json!({
1438    ///     "name": "Computers",
1439    ///     "count": 1024,
1440    /// });
1441    /// let mut req = Request::post("https://example.com")
1442    ///     .with_body(r#"{"name":"Computers","count":1024}"#);
1443    /// let my_data = req.take_body_json::<serde_json::Value>().unwrap();
1444    /// assert_eq!(my_data["name"].as_str(), Some("Computers"));
1445    /// assert_eq!(my_data["count"].as_u64(), Some(1024));
1446    /// ```
1447    pub fn take_body_json<T: DeserializeOwned>(&mut self) -> Result<T, serde_json::Error> {
1448        if let Some(body) = self.try_take_body() {
1449            serde_json::from_reader(body)
1450        } else {
1451            serde_json::from_reader(std::io::empty())
1452        }
1453    }
1454
1455    /// Builder-style equivalent of [`set_body_form()`][`Self::set_body_form()`].
1456    pub fn with_body_form(
1457        mut self,
1458        value: &impl Serialize,
1459    ) -> Result<Self, serde_urlencoded::ser::Error> {
1460        self.set_body_form(value)?;
1461        Ok(self)
1462    }
1463
1464    /// Convert the given value to `application/x-www-form-urlencoded` format and set that data as
1465    /// the request's body.
1466    ///
1467    /// The given value must implement [`serde::Serialize`]; see the trait documentation for
1468    /// details.
1469    ///
1470    #[doc = include_str!("../../docs/snippets/discards-body.md")]
1471    ///
1472    /// # Content type
1473    ///
1474    /// This method sets the content type to `application/x-www-form-urlencoded`.
1475    ///
1476    /// # Errors
1477    ///
1478    /// This method returns [`serde_urlencoded::ser::Error`] if serialization fails.
1479    ///
1480    /// # Examples
1481    ///
1482    /// ```no_run
1483    /// # use fastly::Request;
1484    /// #[derive(serde::Serialize)]
1485    /// struct MyData {
1486    ///     name: String,
1487    ///     count: u64,
1488    /// }
1489    /// let my_data = MyData { name: "Computers".to_string(), count: 1024 };
1490    /// let mut req = Request::post("https://example.com");
1491    /// req.set_body_form(&my_data).unwrap();
1492    /// assert_eq!(req.get_content_type(), Some(fastly::mime::APPLICATION_WWW_FORM_URLENCODED));
1493    /// assert_eq!(&req.into_body_str(), "name=Computers&count=1024");
1494    /// ```
1495    pub fn set_body_form(
1496        &mut self,
1497        value: &impl Serialize,
1498    ) -> Result<(), serde_urlencoded::ser::Error> {
1499        self.body = Some(Body::new());
1500        let s = serde_urlencoded::to_string(value)?;
1501        self.set_body(s);
1502        self.set_content_type(mime::APPLICATION_WWW_FORM_URLENCODED);
1503        Ok(())
1504    }
1505
1506    /// Take the request body and attempt to parse it as a `application/x-www-form-urlencoded`
1507    /// formatted string.
1508    ///
1509    #[doc = include_str!("../../docs/snippets/returns-deserializeowned.md")]
1510    ///
1511    /// After calling this method, this request will no longer have a body.
1512    ///
1513    /// # Errors
1514    ///
1515    /// This method returns [`serde_urlencoded::de::Error`] if deserialization fails.
1516    ///
1517    /// # Examples
1518    ///
1519    /// ```no_run
1520    /// # use fastly::Request;
1521    /// #[derive(serde::Deserialize)]
1522    /// struct MyData {
1523    ///     name: String,
1524    ///     count: u64,
1525    /// }
1526    /// let mut req = Request::post("https://example.com").with_body("name=Computers&count=1024");
1527    /// let my_data = req.take_body_form::<MyData>().unwrap();
1528    /// assert_eq!(&my_data.name, "Computers");
1529    /// assert_eq!(my_data.count, 1024);
1530    /// ```
1531    pub fn take_body_form<T: DeserializeOwned>(
1532        &mut self,
1533    ) -> Result<T, serde_urlencoded::de::Error> {
1534        if let Some(body) = self.try_take_body() {
1535            serde_urlencoded::from_reader(body)
1536        } else {
1537            serde_urlencoded::from_reader(std::io::empty())
1538        }
1539    }
1540
1541    /// Get the MIME type described by the request's
1542    /// [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
1543    /// header, or `None` if that header is absent.
1544    ///
1545    /// # Panics
1546    ///
1547    /// This method panics if the `Content-Type` header is present, but its value cannot be parsed
1548    /// as a valid MIME type. To handle the possibility of an invalid MIME type, use `get_header()`
1549    /// to access the value directly.
1550    ///
1551    /// # Examples
1552    ///
1553    /// ```no_run
1554    /// # use fastly::Request;
1555    /// let req = Request::post("https://example.com").with_body_text_plain("hello, world!");
1556    /// assert_eq!(req.get_content_type(), Some(fastly::mime::TEXT_PLAIN_UTF_8));
1557    /// ```
1558    pub fn get_content_type(&self) -> Option<Mime> {
1559        self.get_header_str(http::header::CONTENT_TYPE).map(|v| {
1560            v.parse()
1561                .unwrap_or_else(|_| panic!("invalid MIME type in Content-Type header: {v}"))
1562        })
1563    }
1564
1565    /// Builder-style equivalent of [`set_content_type()`][`Self::set_content_type()`].
1566    pub fn with_content_type(mut self, mime: Mime) -> Self {
1567        self.set_content_type(mime);
1568        self
1569    }
1570
1571    /// Set the MIME type described by the request's
1572    /// [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
1573    /// header.
1574    ///
1575    /// Any existing `Content-Type` header values will be overwritten.
1576    ///
1577    /// # Examples
1578    ///
1579    /// ```no_run
1580    /// # use fastly::Request;
1581    /// let mut req = Request::post("https://example.com").with_body("hello,world!");
1582    /// req.set_content_type(fastly::mime::TEXT_CSV_UTF_8);
1583    /// ```
1584    pub fn set_content_type(&mut self, mime: Mime) {
1585        self.set_header(http::header::CONTENT_TYPE, mime.as_ref())
1586    }
1587
1588    /// Get the value of the request's
1589    /// [`Content-Length`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
1590    /// header, if it exists.
1591    pub fn get_content_length(&self) -> Option<usize> {
1592        self.get_header(http::header::CONTENT_LENGTH)
1593            .and_then(|v| v.to_str().ok())
1594            .and_then(|v| v.parse().ok())
1595    }
1596
1597    /// Returns whether the given header name is present in the request.
1598    ///
1599    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1600    ///
1601    /// # Examples
1602    ///
1603    /// ```no_run
1604    /// # use fastly::Request;
1605    /// let req = Request::get("https://example.com").with_header("hello", "world!");
1606    /// assert!(req.contains_header("hello"));
1607    /// assert!(!req.contains_header("not-present"));
1608    /// ```
1609    pub fn contains_header(&self, name: impl ToHeaderName) -> bool {
1610        !self.lazy_handle.get_header_values(name).is_empty()
1611    }
1612
1613    /// Builder-style equivalent of [`append_header()`][`Self::append_header()`].
1614    pub fn with_header(mut self, name: impl ToHeaderName, value: impl ToHeaderValue) -> Self {
1615        self.append_header(name, value);
1616        self
1617    }
1618
1619    /// Builder-style equivalent of [`set_header()`][`Self::set_header()`].
1620    pub fn with_set_header(mut self, name: impl ToHeaderName, value: impl ToHeaderValue) -> Self {
1621        self.set_header(name, value);
1622        self
1623    }
1624
1625    /// Get the value of a header as a string, or `None` if the header is not present.
1626    ///
1627    /// If there are multiple values for the header, only one is returned, which may be any of the
1628    /// values. See [`get_header_all_str()`][`Self::get_header_all_str()`] if you need to get all of
1629    /// the values.
1630    ///
1631    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1632    ///
1633    /// # Panics
1634    ///
1635    #[doc = include_str!("../../docs/snippets/panics-reqresp-header-utf8.md")]
1636    ///
1637    /// # Examples
1638    ///
1639    /// ```no_run
1640    /// # use fastly::Request;
1641    /// let req = Request::get("https://example.com").with_header("hello", "world!");
1642    /// assert_eq!(req.get_header_str("hello"), Some("world"));
1643    /// ```
1644    pub fn get_header_str(&self, name: impl ToHeaderName) -> Option<&str> {
1645        let name = name.into_borrowable();
1646        if let Some(hdr) = self.get_header(name.as_ref()) {
1647            Some(
1648                std::str::from_utf8(hdr.as_bytes())
1649                    .unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {name}")),
1650            )
1651        } else {
1652            None
1653        }
1654    }
1655
1656    /// Get the value of a header as a string, including invalid characters, or `None` if the header
1657    /// is not present.
1658    ///
1659    #[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
1660    ///
1661    /// If there are multiple values for the header, only one is returned, which may be any of the
1662    /// values. See [`get_header_all_str_lossy()`][`Self::get_header_all_str_lossy()`] if you need
1663    /// to get all of the values.
1664    ///
1665    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1666    ///
1667    /// # Examples
1668    ///
1669    /// ```no_run
1670    /// # use fastly::Request;
1671    /// # use http::header::HeaderValue;
1672    /// # use std::borrow::Cow;
1673    /// let header_value = HeaderValue::from_bytes(b"\xF0\x90\x80 world!").unwrap();
1674    /// let req = Request::get("https://example.com").with_header("hello", header_value);
1675    /// assert_eq!(req.get_header_str_lossy("hello"), Some(Cow::from("� world")));
1676    /// ```
1677    pub fn get_header_str_lossy(&self, name: impl ToHeaderName) -> Option<Cow<'_, str>> {
1678        self.get_header(name)
1679            .map(|hdr| String::from_utf8_lossy(hdr.as_bytes()))
1680    }
1681
1682    /// Get the value of a header, or `None` if the header is not present.
1683    ///
1684    /// If there are multiple values for the header, only one is returned, which may be any of the
1685    /// values. See [`get_header_all()`][`Self::get_header_all()`] if you need to get all of the
1686    /// values.
1687    ///
1688    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1689    ///
1690    /// # Examples
1691    ///
1692    /// Handling UTF-8 values explicitly:
1693    ///
1694    /// ```no_run
1695    /// # use fastly::Request;
1696    /// # use fastly::http::HeaderValue;
1697    /// let req = Request::get("https://example.com").with_header("hello", "world!");
1698    /// assert_eq!(req.get_header("hello"), Some(&HeaderValue::from_static("world")));
1699    /// ```
1700    ///
1701    /// Safely handling invalid UTF-8 values:
1702    ///
1703    /// ```no_run
1704    /// # use fastly::Request;
1705    /// let invalid_utf8 = &"🐈".as_bytes()[0..3];
1706    /// let req = Request::get("https://example.com").with_header("hello", invalid_utf8);
1707    /// assert_eq!(req.get_header("hello").unwrap().as_bytes(), invalid_utf8);
1708    /// ```
1709    pub fn get_header(&self, name: impl ToHeaderName) -> Option<&HeaderValue> {
1710        self.lazy_handle.get_header_values(name).first()
1711    }
1712
1713    /// Get all values of a header as strings, or an empty vector if the header is not present.
1714    ///
1715    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1716    ///
1717    /// # Panics
1718    ///
1719    #[doc = include_str!("../../docs/snippets/panics-reqresp-headers-utf8.md")]
1720    ///
1721    /// # Examples
1722    ///
1723    /// ```no_run
1724    /// # use fastly::Request;
1725    /// let req = Request::get("https://example.com")
1726    ///     .with_header("hello", "world!")
1727    ///     .with_header("hello", "universe!");
1728    /// let values = req.get_header_all_str("hello");
1729    /// assert_eq!(values.len(), 2);
1730    /// assert!(values.contains(&"world!"));
1731    /// assert!(values.contains(&"universe!"));
1732    /// ```
1733    pub fn get_header_all_str(&self, name: impl ToHeaderName) -> Vec<&str> {
1734        let name = name.into_borrowable();
1735        self.get_header_all(name.as_ref())
1736            .map(|v| {
1737                std::str::from_utf8(v.as_bytes())
1738                    .unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {name}"))
1739            })
1740            .collect()
1741    }
1742
1743    /// Get all values of a header as strings, including invalid characters, or an empty vector if the header is not present.
1744    ///
1745    #[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
1746    ///
1747    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1748    ///
1749    /// # Examples
1750    ///
1751    /// ```no_run
1752    /// # use std::borrow::Cow;
1753    /// # use http::header::HeaderValue;
1754    /// # use fastly::Request;
1755    /// let world_value = HeaderValue::from_bytes(b"\xF0\x90\x80 world!").unwrap();
1756    /// let universe_value = HeaderValue::from_bytes(b"\xF0\x90\x80 universe!").unwrap();
1757    /// let req = Request::get("https://example.com")
1758    ///     .with_header("hello", world_value)
1759    ///     .with_header("hello", universe_value);
1760    /// let values = req.get_header_all_str_lossy("hello");
1761    /// assert_eq!(values.len(), 2);
1762    /// assert!(values.contains(&Cow::from("� world!")));
1763    /// assert!(values.contains(&Cow::from("� universe!")));
1764    /// ```
1765    pub fn get_header_all_str_lossy(&self, name: impl ToHeaderName) -> Vec<Cow<'_, str>> {
1766        self.get_header_all(name)
1767            .map(|hdr| String::from_utf8_lossy(hdr.as_bytes()))
1768            .collect()
1769    }
1770
1771    /// Get an iterator of all the values of a header.
1772    ///
1773    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1774    ///
1775    /// # Examples
1776    ///
1777    /// You can turn the iterator into collection, like [`Vec`]:
1778    ///
1779    /// ```no_run
1780    /// # use fastly::Request;
1781    /// # use fastly::http::HeaderValue;
1782    /// let invalid_utf8 = &"🐈".as_bytes()[0..3];
1783    /// let req = Request::get("https://example.com")
1784    ///     .with_header("hello", "world!")
1785    ///     .with_header("hello", invalid_utf8);
1786    ///
1787    /// let values: Vec<&HeaderValue> = req.get_header_all("hello").collect();
1788    /// assert_eq!(values.len(), 2);
1789    /// assert!(values.contains(&&HeaderValue::from_static("world!")));
1790    /// assert!(values.contains(&&HeaderValue::from_bytes(invalid_utf8).unwrap()));
1791    /// ```
1792    ///
1793    /// You can use the iterator in a loop:
1794    ///
1795    /// ```no_run
1796    /// # use fastly::Request;
1797    /// let invalid_utf8 = &"🐈".as_bytes()[0..3];
1798    /// let req = Request::get("https://example.com")
1799    ///     .with_header("hello", "world!")
1800    ///     .with_header("hello", invalid_utf8);
1801    ///
1802    /// for value in req.get_header_all("hello") {
1803    ///     if let Ok(s) = std::str::from_utf8(value.as_bytes()) {
1804    ///         println!("hello, {}", s);
1805    ///     } else {
1806    ///         println!("hello, invalid UTF-8!");
1807    ///     }
1808    /// }
1809    /// ```
1810    pub fn get_header_all(&self, name: impl ToHeaderName) -> impl Iterator<Item = &HeaderValue> {
1811        self.lazy_handle.get_header_values(name).iter()
1812    }
1813
1814    /// Get an iterator of all the request's header names and values.
1815    ///
1816    /// # Examples
1817    ///
1818    /// You can turn the iterator into a collection, like [`Vec`]:
1819    ///
1820    /// ```no_run
1821    /// # use fastly::Request;
1822    /// # use fastly::http::header::{HeaderName, HeaderValue};
1823    /// let req = Request::get("https://example.com")
1824    ///     .with_header("hello", "world!")
1825    ///     .with_header("hello", "universe!");
1826    ///
1827    /// let headers: Vec<(&HeaderName, &HeaderValue)> = req.get_headers().collect();
1828    /// assert_eq!(headers.len(), 2);
1829    /// assert!(headers.contains(&(&HeaderName::from_static("hello"), &HeaderValue::from_static("world!"))));
1830    /// assert!(headers.contains(&(&HeaderName::from_static("hello"), &HeaderValue::from_static("universe!"))));
1831    /// ```
1832    ///
1833    /// You can use the iterator in a loop:
1834    ///
1835    /// ```no_run
1836    /// # use fastly::Request;
1837    /// let req = Request::get("https://example.com")
1838    ///     .with_header("hello", "world!")
1839    ///     .with_header("hello", "universe!");
1840    ///
1841    /// for (n, v) in req.get_headers() {
1842    ///     println!("Header -  {}: {:?}", n, v);
1843    /// }
1844    /// ```
1845    pub fn get_headers(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
1846        self.lazy_handle.iter()
1847    }
1848
1849    /// Get all of the request's header names as strings, or an empty vector if no headers are
1850    /// present.
1851    ///
1852    /// # Examples
1853    ///
1854    /// ```no_run
1855    /// # use fastly::Request;
1856    /// let req = Request::get("https://example.com")
1857    ///     .with_header("hello", "world!")
1858    ///     .with_header("goodbye", "latency!");
1859    /// let names = req.get_header_names_str();
1860    /// assert_eq!(names.len(), 2);
1861    /// assert!(names.contains(&"hello"));
1862    /// assert!(names.contains(&"goodbye"));
1863    /// ```
1864    pub fn get_header_names_str(&self) -> Vec<&str> {
1865        self.get_header_names().map(|n| n.as_str()).collect()
1866    }
1867
1868    /// Get an iterator of all the request's header names.
1869    ///
1870    /// # Examples
1871    ///
1872    /// You can turn the iterator into collection, like [`Vec`]:
1873    ///
1874    /// ```no_run
1875    /// # use fastly::Request;
1876    /// # use fastly::http::header::HeaderName;
1877    /// let req = Request::get("https://example.com")
1878    ///     .with_header("hello", "world!")
1879    ///     .with_header("goodbye", "latency!");
1880    ///
1881    /// let values: Vec<&HeaderName> = req.get_header_names().collect();
1882    /// assert_eq!(values.len(), 2);
1883    /// assert!(values.contains(&&HeaderName::from_static("hello")));
1884    /// assert!(values.contains(&&HeaderName::from_static("goodbye")));
1885    /// ```
1886    ///
1887    /// You can use the iterator in a loop:
1888    ///
1889    /// ```no_run
1890    /// # use fastly::Request;
1891    /// let req = Request::get("https://example.com")
1892    ///     .with_header("hello", "world!")
1893    ///     .with_header("goodbye", "latency!");
1894    ///
1895    /// for name in req.get_header_names() {
1896    ///     println!("saw header: {:?}", name);
1897    /// }
1898    /// ```
1899    pub fn get_header_names(&self) -> impl Iterator<Item = &HeaderName> {
1900        self.lazy_handle.get_header_names()
1901    }
1902
1903    /// Set a request header to the given value, discarding any previous values for the given
1904    /// header name.
1905    ///
1906    #[doc = include_str!("../../docs/snippets/header-name-value-argument.md")]
1907    ///
1908    /// # Examples
1909    ///
1910    /// ```no_run
1911    /// # use fastly::Request;
1912    /// let mut req = Request::get("https://example.com");
1913    ///
1914    /// req.set_header("hello", "world!");
1915    /// assert_eq!(req.get_header_str("hello"), Some("world!"));
1916    ///
1917    /// req.set_header("hello", "universe!");
1918    ///
1919    /// let values = req.get_header_all_str("hello");
1920    /// assert_eq!(values.len(), 1);
1921    /// assert!(!values.contains(&"world!"));
1922    /// assert!(values.contains(&"universe!"));
1923    /// ```
1924    pub fn set_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
1925        self.lazy_handle
1926            .set_header(name.into_owned(), value.into_owned());
1927    }
1928
1929    /// Add a request header with given value.
1930    ///
1931    /// Unlike [`set_header()`][`Self::set_header()`], this does not discard existing values for the
1932    /// same header name.
1933    ///
1934    #[doc = include_str!("../../docs/snippets/header-name-value-argument.md")]
1935    ///
1936    /// # Examples
1937    ///
1938    /// ```no_run
1939    /// # use fastly::Request;
1940    /// let mut req = Request::get("https://example.com");
1941    ///
1942    /// req.set_header("hello", "world!");
1943    /// assert_eq!(req.get_header_str("hello"), Some("world!"));
1944    ///
1945    /// req.append_header("hello", "universe!");
1946    ///
1947    /// let values = req.get_header_all_str("hello");
1948    /// assert_eq!(values.len(), 2);
1949    /// assert!(values.contains(&"world!"));
1950    /// assert!(values.contains(&"universe!"));
1951    /// ```
1952    pub fn append_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
1953        self.lazy_handle
1954            .append_header_value(name.into_borrowable().as_ref(), value);
1955    }
1956
1957    /// Remove all request headers of the given name, and return one of the removed header values
1958    /// if any were present.
1959    ///
1960    #[doc = include_str!("../../docs/snippets/removes-one-header.md")]
1961    ///
1962    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1963    ///
1964    /// # Examples
1965    ///
1966    /// ```no_run
1967    /// # use fastly::Request;
1968    /// # use fastly::http::HeaderValue;
1969    /// let mut req = Request::get("https://example.com").with_header("hello", "world!");
1970    /// assert_eq!(req.get_header_str("hello"), Some("world!"));
1971    /// assert_eq!(req.remove_header("hello"), Some(HeaderValue::from_static("world!")));
1972    /// assert!(req.remove_header("not-present").is_none());
1973    /// ```
1974    pub fn remove_header(&mut self, name: impl ToHeaderName) -> Option<HeaderValue> {
1975        self.lazy_handle
1976            .remove_header(name.into_borrowable().as_ref())
1977    }
1978
1979    /// Remove all request headers of the given name, and return one of the removed header values as
1980    /// a string if any were present.
1981    ///
1982    #[doc = include_str!("../../docs/snippets/removes-one-header.md")]
1983    ///
1984    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
1985    ///
1986    /// # Panics
1987    ///
1988    #[doc = include_str!("../../docs/snippets/panics-reqresp-remove-header-utf8.md")]
1989    ///
1990    /// # Examples
1991    ///
1992    /// ```no_run
1993    /// # use fastly::Request;
1994    /// let mut req = Request::get("https://example.com").with_header("hello", "world!");
1995    /// assert_eq!(req.get_header_str("hello"), Some("world!"));
1996    /// assert_eq!(req.remove_header_str("hello"), Some("world!".to_string()));
1997    /// assert!(req.remove_header_str("not-present").is_none());
1998    /// ```
1999    pub fn remove_header_str(&mut self, name: impl ToHeaderName) -> Option<String> {
2000        let name = name.into_borrowable();
2001        self.remove_header(name.as_ref()).map(|hdr| {
2002            std::str::from_utf8(hdr.as_bytes())
2003                .map(|s| s.to_owned())
2004                .unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {name}"))
2005        })
2006    }
2007
2008    /// Remove all request headers of the given name, and return one of the removed header values
2009    /// as a string, including invalid characters, if any were present.
2010    ///
2011    #[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
2012    ///
2013    #[doc = include_str!("../../docs/snippets/removes-one-header.md")]
2014    ///
2015    #[doc = include_str!("../../docs/snippets/header-name-argument.md")]
2016    ///
2017    /// # Examples
2018    ///
2019    /// ```no_run
2020    /// # use fastly::Request;
2021    /// # use http::header::HeaderValue;
2022    /// # use std::borrow::Cow;
2023    /// let header_value = HeaderValue::from_bytes(b"\xF0\x90\x80 world!").unwrap();
2024    /// let mut req = Request::get("https://example.com")
2025    ///     .with_header("hello", header_value);
2026    /// assert_eq!(req.get_header_str_lossy("hello"), Some(Cow::from("� world")));
2027    /// assert_eq!(req.remove_header_str_lossy("hello"), Some(String::from("� world")));
2028    /// assert!(req.remove_header_str_lossy("not-present").is_none());
2029    /// ```
2030    pub fn remove_header_str_lossy(&mut self, name: impl ToHeaderName) -> Option<String> {
2031        self.remove_header(name)
2032            .map(|hdr| String::from_utf8_lossy(hdr.as_bytes()).into_owned())
2033    }
2034
2035    /// Builder-style equivalent of [`set_method()`][`Self::set_method()`].
2036    pub fn with_method(mut self, method: impl ToMethod) -> Self {
2037        self.set_method(method);
2038        self
2039    }
2040
2041    /// Get the request method as a string.
2042    ///
2043    /// # Examples
2044    ///
2045    /// ```no_run
2046    /// # use fastly::Request;
2047    /// let req = Request::get("https://example.com");
2048    /// assert_eq!(req.get_method_str(), "GET");
2049    pub fn get_method_str(&self) -> &str {
2050        self.get_method().as_str()
2051    }
2052
2053    /// Get the request method.
2054    ///
2055    /// # Examples
2056    ///
2057    /// ```no_run
2058    /// # use fastly::Request;
2059    /// use fastly::http::Method;
2060    /// fn log_method(req: &Request) {
2061    ///     match req.get_method() {
2062    ///         &Method::GET | &Method::HEAD => println!("method was a GET or HEAD"),
2063    ///         &Method::POST => println!("method was a POST"),
2064    ///         _ => println!("method was something else"),
2065    ///     }
2066    /// }
2067    pub fn get_method(&self) -> &Method {
2068        self.lazy_handle.get_field::<Method>()
2069    }
2070
2071    /// Set the request method.
2072    ///
2073    #[doc = include_str!("../../docs/snippets/method-argument.md")]
2074    ///
2075    /// # Examples
2076    ///
2077    /// ```no_run
2078    /// # use fastly::Request;
2079    /// use fastly::http::Method;
2080    ///
2081    /// let mut req = Request::get("https://example.com");
2082    /// req.set_method(Method::POST);
2083    /// assert_eq!(req.get_method(), &Method::POST);
2084    /// ```
2085    pub fn set_method(&mut self, method: impl ToMethod) {
2086        self.lazy_handle.put_field::<Method>(method.into_owned());
2087    }
2088
2089    /// Builder-style equivalent of [`set_url()`][`Self::set_url()`].
2090    pub fn with_url(mut self, url: impl ToUrl) -> Self {
2091        self.set_url(url);
2092        self
2093    }
2094
2095    /// Get the request URL as a string.
2096    ///
2097    /// # Examples
2098    ///
2099    /// ```no_run
2100    /// # use fastly::Request;
2101    /// let req = Request::get("https://example.com");
2102    /// assert_eq!(req.get_url_str(), "https://example.com");
2103    /// ```
2104    pub fn get_url_str(&self) -> &str {
2105        self.get_url().as_str()
2106    }
2107
2108    /// Get a shared reference to the request URL.
2109    ///
2110    /// # Examples
2111    ///
2112    /// ```no_run
2113    /// # use fastly::Request;
2114    /// let req = Request::get("https://example.com/hello#world");
2115    /// let url = req.get_url();
2116    /// assert_eq!(url.host_str(), Some("example.com"));
2117    /// assert_eq!(url.path(), "/hello");
2118    /// assert_eq!(url.fragment(), Some("world"));
2119    /// ```
2120    pub fn get_url(&self) -> &Url {
2121        self.lazy_handle.get_field::<Url>()
2122    }
2123
2124    /// Get a mutable reference to the request URL.
2125    ///
2126    /// # Examples
2127    ///
2128    /// ```no_run
2129    /// # use fastly::Request;
2130    /// let mut req = Request::get("https://example.com/");
2131    ///
2132    /// let mut url = req.get_url_mut();
2133    /// url.set_path("/hello");
2134    /// url.set_fragment(Some("world"));
2135    /// drop(url);
2136    ///
2137    /// assert_eq!(req.get_url_str(), "https://example.com/hello#world");
2138    /// ```
2139    pub fn get_url_mut(&mut self) -> impl std::ops::DerefMut<Target = Url> + '_ {
2140        self.lazy_handle.get_field_mut::<Url>()
2141    }
2142
2143    /// Set the request URL.
2144    ///
2145    #[doc = include_str!("../../docs/snippets/url-argument.md")]
2146    pub fn set_url(&mut self, url: impl ToUrl) {
2147        self.lazy_handle.put_field::<Url>(url.into_owned());
2148    }
2149
2150    /// Get the path component of the request URL.
2151    ///
2152    /// # Examples
2153    ///
2154    /// ```no_run
2155    /// # use fastly::Request;
2156    /// let req = Request::get("https://example.com/hello#world");
2157    /// assert_eq!(req.get_path(), "/hello");
2158    /// ```
2159    pub fn get_path(&self) -> &str {
2160        self.get_url().path()
2161    }
2162
2163    /// Builder-style equivalent of [`set_path()`][`Self::set_path()`].
2164    pub fn with_path(mut self, path: &str) -> Self {
2165        self.set_path(path);
2166        self
2167    }
2168
2169    /// Set the path component of the request URL.
2170    ///
2171    /// # Examples
2172    ///
2173    /// ```no_run
2174    /// # use fastly::Request;
2175    /// let mut req = Request::get("https://example.com/");
2176    /// req.set_path("/hello");
2177    /// assert_eq!(req.get_url_str(), "https://example.com/hello");
2178    /// ```
2179    pub fn set_path(&mut self, path: &str) {
2180        self.get_url_mut().set_path(path);
2181    }
2182
2183    /// Get the query component of the request URL, if it exists, as a percent-encoded ASCII string.
2184    ///
2185    /// This is a shorthand for `self.get_url().query()`; see [`Url::query()`] for details and other
2186    /// query manipulation functions.
2187    pub fn get_query_str(&self) -> Option<&str> {
2188        self.get_url().query()
2189    }
2190
2191    /// Get the value of a query parameter in the request's URL.
2192    ///
2193    /// This assumes that the query string is a `&` separated list of `parameter=value` pairs.  The
2194    /// value of the first occurrence of `parameter` is returned. No URL decoding is performed.
2195    ///
2196    /// # Examples
2197    ///
2198    /// ```no_run
2199    /// # use fastly::Request;
2200    /// let req = Request::get("https://example.com/page?foo=bar&baz=qux");
2201    /// assert_eq!(req.get_query_parameter("foo"), Some("bar"));
2202    /// assert_eq!(req.get_query_parameter("baz"), Some("qux"));
2203    /// assert_eq!(req.get_query_parameter("other"), None);
2204    /// ```
2205    pub fn get_query_parameter(&self, parameter: &str) -> Option<&str> {
2206        self.get_url().query().and_then(|qs| {
2207            qs.split('&').find_map(|part| {
2208                part.strip_prefix(parameter)
2209                    .and_then(|maybe| maybe.strip_prefix('='))
2210            })
2211        })
2212    }
2213
2214    /// Attempt to parse the query component of the request URL into the specified datatype.
2215    ///
2216    #[doc = include_str!("../../docs/snippets/returns-deserializeowned.md")]
2217    ///
2218    /// # Errors
2219    ///
2220    /// This method returns [`serde_urlencoded::de::Error`] if deserialization fails.
2221    ///
2222    /// # Examples
2223    ///
2224    /// Parsing into a vector of string pairs:
2225    ///
2226    /// ```no_run
2227    /// # use fastly::Request;
2228    /// let req = Request::get("https://example.com/foo?hello=%F0%9F%8C%90!&bar=baz");
2229    /// let pairs: Vec<(String, String)> = req.get_query().unwrap();
2230    /// assert_eq!((pairs[0].0.as_str(), pairs[0].1.as_str()), ("hello", "🌐!"));
2231    /// ```
2232    ///
2233    /// Parsing into a mapping between strings (note that duplicates are removed since
2234    /// [`HashMap`][`std::collections::HashMap`] is not a multimap):
2235    ///
2236    /// ```no_run
2237    /// # use fastly::Request;
2238    /// use std::collections::HashMap;
2239    /// let req = Request::get("https://example.com/foo?hello=%F0%9F%8C%90!&bar=baz&bar=quux");
2240    /// let map: HashMap<String, String> = req.get_query().unwrap();
2241    /// assert_eq!(map.len(), 2);
2242    /// assert_eq!(map["hello"].as_str(), "🌐!");
2243    /// ```
2244    ///
2245    /// Parsing into a custom type that derives [`serde::de::Deserialize`]:
2246    ///
2247    /// ```no_run
2248    /// # use fastly::Request;
2249    /// #[derive(serde::Deserialize)]
2250    /// struct MyData {
2251    ///     name: String,
2252    ///     count: u64,
2253    /// }
2254    /// let mut req = Request::get("https://example.com/?name=Computers&count=1024");
2255    /// let my_data = req.take_body_form::<MyData>().unwrap();
2256    /// assert_eq!(&my_data.name, "Computers");
2257    /// assert_eq!(my_data.count, 1024);
2258    /// ```
2259    pub fn get_query<T: DeserializeOwned>(&self) -> Result<T, serde_urlencoded::de::Error> {
2260        serde_urlencoded::from_str(self.get_url().query().unwrap_or(""))
2261    }
2262
2263    /// Builder-style equivalent of [`set_query_str()`][`Self::set_query_str()`].
2264    pub fn with_query_str(mut self, query: impl AsRef<str>) -> Self {
2265        self.set_query_str(query);
2266        self
2267    }
2268
2269    /// Set the query string of the request URL query component to the given string, performing
2270    /// percent-encoding if necessary.
2271    ///
2272    /// # Examples
2273    ///
2274    /// ```no_run
2275    /// # use fastly::Request;
2276    /// let mut req = Request::get("https://example.com/foo");
2277    /// req.set_query_str("hello=🌐!&bar=baz");
2278    /// assert_eq!(req.get_url_str(), "https://example.com/foo?hello=%F0%9F%8C%90!&bar=baz");
2279    /// ```
2280    pub fn set_query_str(&mut self, query: impl AsRef<str>) {
2281        self.get_url_mut().set_query(Some(query.as_ref()))
2282    }
2283
2284    /// Builder-style equivalent of [`set_query()`][`Self::set_query()`].
2285    pub fn with_query(
2286        mut self,
2287        query: &impl Serialize,
2288    ) -> Result<Self, serde_urlencoded::ser::Error> {
2289        self.set_query(query)?;
2290        Ok(self)
2291    }
2292
2293    /// Convert the given value to `application/x-www-form-urlencoded` format and set that data as
2294    /// the request URL query component.
2295    ///
2296    /// The given value must implement [`serde::Serialize`]; see the trait documentation for
2297    /// details.
2298    ///
2299    /// # Errors
2300    ///
2301    /// This method returns [`serde_urlencoded::ser::Error`] if serialization fails.
2302    ///
2303    /// # Examples
2304    ///
2305    /// ```no_run
2306    /// # use fastly::Request;
2307    /// #[derive(serde::Serialize)]
2308    /// struct MyData {
2309    ///     name: String,
2310    ///     count: u64,
2311    /// }
2312    /// let my_data = MyData { name: "Computers".to_string(), count: 1024 };
2313    /// let mut req = Request::get("https://example.com/foo");
2314    /// req.set_query(&my_data).unwrap();
2315    /// assert_eq!(req.get_url_str(), "https://example.com/foo?name=Computers&count=1024");
2316    /// ```
2317    pub fn set_query(
2318        &mut self,
2319        query: &impl Serialize,
2320    ) -> Result<(), serde_urlencoded::ser::Error> {
2321        let s = serde_urlencoded::to_string(query)?;
2322        self.get_url_mut().set_query(Some(&s));
2323        Ok(())
2324    }
2325
2326    /// Remove the query component from the request URL, if one exists.
2327    ///
2328    /// # Examples
2329    ///
2330    /// ```no_run
2331    /// # use fastly::Request;
2332    /// let mut req = Request::get("https://example.com/foo?hello=%F0%9F%8C%90!&bar=baz");
2333    /// req.remove_query();
2334    /// assert_eq!(req.get_url_str(), "https://example.com/foo");
2335    /// ```
2336    pub fn remove_query(&mut self) {
2337        self.get_url_mut().set_query(None);
2338    }
2339
2340    /// Builder-style equivalent of [`set_version()`][`Self::set_version()`].
2341    pub fn with_version(mut self, version: Version) -> Self {
2342        self.set_version(version);
2343        self
2344    }
2345
2346    /// Get the HTTP version of this request.
2347    pub fn get_version(&self) -> Version {
2348        *self.lazy_handle.get_field::<Version>()
2349    }
2350
2351    /// Set the HTTP version of this request.
2352    pub fn set_version(&mut self, version: Version) {
2353        self.lazy_handle.put_field::<Version>(version);
2354    }
2355
2356    /// Builder-style equivalent of [`set_pass()`][`Self::set_pass()`].
2357    pub fn with_pass(mut self, pass: bool) -> Self {
2358        self.set_pass(pass);
2359        self
2360    }
2361
2362    /// Set whether this request should be cached if sent to a backend.
2363    ///
2364    /// By default this is `false`, which means the backend will only be reached if a cached
2365    /// response is not available. Set this to `true` to send the request directly to the backend
2366    /// without caching.
2367    ///
2368    /// # Overrides
2369    ///
2370    #[doc = include_str!("../../docs/snippets/set-pass-override.md")]
2371    pub fn set_pass(&mut self, pass: bool) {
2372        self.metadata.cache_override.set_pass(pass);
2373    }
2374
2375    /// Builder-style equivalent of [`set_ttl()`][`Self::set_ttl()`].
2376    pub fn with_ttl(mut self, ttl: u32) -> Self {
2377        self.set_ttl(ttl);
2378        self
2379    }
2380
2381    /// Override the caching behavior of this request to use the given Time to Live (TTL), in seconds.
2382    ///
2383    /// # Overrides
2384    ///
2385    /// This overrides any previous [`Request::set_pass`] call and sets the `pass` behavior to
2386    /// `false`.
2387    ///
2388    #[doc = include_str!("../../docs/snippets/set-pass-override.md")]
2389    pub fn set_ttl(&mut self, ttl: u32) {
2390        self.metadata.cache_override.set_ttl(ttl);
2391    }
2392
2393    /// Builder-style equivalent of [`set_stale_while_revalidate()`][`Self::set_stale_while_revalidate()`].
2394    pub fn with_stale_while_revalidate(mut self, swr: u32) -> Self {
2395        self.set_stale_while_revalidate(swr);
2396        self
2397    }
2398
2399    /// Override the caching behavior of this request to use the given `stale-while-revalidate`
2400    /// time, in seconds.
2401    ///
2402    /// # Overrides
2403    ///
2404    /// This overrides the behavior specified in the response headers, and will override any
2405    /// previous [`Request::set_pass`] call by setting the `pass` behavior to `false`.
2406    ///
2407    #[doc = include_str!("../../docs/snippets/set-pass-override.md")]
2408    pub fn set_stale_while_revalidate(&mut self, swr: u32) {
2409        self.metadata.cache_override.set_stale_while_revalidate(swr);
2410    }
2411
2412    /// Builder-style equivalent of [`set_pci()`][`Self::set_pci()`].
2413    pub fn with_pci(mut self, pci: bool) -> Self {
2414        self.set_pci(pci);
2415        self
2416    }
2417
2418    /// Override the caching behavior of this request to enable or disable PCI/HIPAA-compliant
2419    /// non-volatile caching.
2420    ///
2421    /// By default, this is `false`, which means the request may not be PCI/HIPAA-compliant. Set it
2422    /// to `true` to enable compliant caching.
2423    ///
2424    /// See the [Fastly PCI-Compliant Caching and Delivery
2425    /// documentation](https://docs.fastly.com/products/pci-compliant-caching-and-delivery) for
2426    /// details.
2427    ///
2428    /// # Overrides
2429    ///
2430    /// This overrides any previous [`Request::set_pass`] call and sets the `pass` behavior to
2431    /// `false`.
2432    ///
2433    #[doc = include_str!("../../docs/snippets/set-pass-override.md")]
2434    pub fn set_pci(&mut self, pci: bool) {
2435        self.metadata.cache_override.set_pci(pci);
2436    }
2437
2438    /// Builder-style equivalent of [`set_surrogate_key()`][`Self::set_surrogate_key()`].
2439    pub fn with_surrogate_key(mut self, sk: HeaderValue) -> Self {
2440        self.set_surrogate_key(sk);
2441        self
2442    }
2443
2444    /// Override the caching behavior of this request to include the given surrogate key(s),
2445    /// provided as a header value.
2446    ///
2447    /// The header value can contain more than one surrogate key, separated by spaces.
2448    ///
2449    /// Surrogate keys must contain only printable ASCII characters (those between `0x21` and
2450    /// `0x7E`, inclusive). Any invalid keys will be ignored.
2451    ///
2452    /// See the [Fastly surrogate keys
2453    /// guide](https://docs.fastly.com/en/guides/purging-api-cache-with-surrogate-keys) for details.
2454    ///
2455    /// # Overrides
2456    ///
2457    /// This overrides any previous [`Request::set_pass`] call and sets the `pass` behavior to
2458    /// `false`, and extends (but does not replace) any `Surrogate-Key` response headers from the
2459    /// backend.
2460    ///
2461    #[doc = include_str!("../../docs/snippets/set-pass-override.md")]
2462    pub fn set_surrogate_key(&mut self, sk: HeaderValue) {
2463        self.metadata.cache_override.set_surrogate_key(sk);
2464    }
2465
2466    /// Returns the IP address of the client making the HTTP request.
2467    ///
2468    /// Returns `None` if this is not the client request.
2469    pub fn get_client_ip_addr(&self) -> Option<IpAddr> {
2470        self.get_client_metadata().and_then(|md| md.client_ip_addr)
2471    }
2472
2473    /// Returns the IP address on which this server received the HTTP request.
2474    ///
2475    /// Returns `None` if this is not the client request.
2476    pub fn get_server_ip_addr(&self) -> Option<IpAddr> {
2477        self.get_client_metadata().and_then(|md| md.server_ip_addr)
2478    }
2479
2480    /// Returns the client request's header names exactly as they were originally received.
2481    ///
2482    /// This includes both the original character cases, as well as the original order of the
2483    /// received headers.
2484    ///
2485    /// Returns `Ok(None)` if this is not the client request.
2486    pub fn get_original_header_names(&self) -> Result<Option<&[String]>, BufferSizeError> {
2487        self.get_original_headers_metadata()
2488            .map(|v| v.header_names.as_deref())
2489            .map(|r| r.map_err(Clone::clone))
2490            .transpose()
2491    }
2492
2493    /// Returns the number of headers in the client request as originally received.
2494    ///
2495    /// Returns `None` if this is not the client request.
2496    pub fn get_original_header_count(&self) -> Option<u32> {
2497        self.get_original_headers_metadata().map(|v| v.count)
2498    }
2499
2500    /// Get the HTTP/2 fingerprint of client request if available
2501    ///
2502    /// Returns `None` if this is not the client request.
2503    pub fn get_client_h2_fingerprint(&self) -> Option<&str> {
2504        self.get_client_fingerprint_metadata()
2505            .and_then(|v| v.client_h2_fingerprint.as_ref())
2506            .map(String::as_str)
2507    }
2508
2509    /// Get the request id of the current request if available
2510    ///
2511    /// Returns `None` if this is not the client request.
2512    pub fn get_client_request_id(&self) -> Option<&str> {
2513        self.get_client_metadata()
2514            .and_then(|md| md.client_request_id.as_ref())
2515            .map(String::as_str)
2516    }
2517
2518    /// Get the fingerprint of client original request headers if available
2519    ///
2520    /// Returns `None` if this is not the client request.
2521    pub fn get_client_oh_fingerprint(&self) -> Option<&str> {
2522        self.get_client_fingerprint_metadata()
2523            .and_then(|v| v.client_oh_fingerprint.as_ref())
2524            .map(String::as_str)
2525    }
2526
2527    /// Returns whether the request was tagged as contributing to a DDoS attack
2528    ///
2529    /// Returns `None` if this is not the client request.
2530    pub fn get_client_ddos_detected(&self) -> Option<bool> {
2531        self.get_client_fingerprint_metadata()
2532            .and_then(|v| v.client_ddos_detected)
2533    }
2534
2535    /// Get the raw bytes sent by the client in the TLS ClientHello message.
2536    ///
2537    /// See [RFC 5246](https://tools.ietf.org/html/rfc5246#section-7.4.1.2) for details.
2538    ///
2539    /// Returns `None` if this is not the client request.
2540    pub fn get_tls_client_hello(&self) -> Option<&[u8]> {
2541        self.get_client_tls_metadata()
2542            .map(|v| v.client_tls_client_hello.as_ref())
2543    }
2544
2545    /// Get the hostname sent by the client in the TLS ServerName Indication.
2546    ///
2547    /// See [RFC 6066](https://tools.ietf.org/html/rfc6066#section-3) for details.
2548    ///
2549    /// Returns `None` if this is not the client request.
2550    pub fn get_tls_client_servername(&self) -> Option<&str> {
2551        self.get_client_tls_metadata()
2552            .and_then(|v| v.client_tls_client_servername.as_deref())
2553    }
2554
2555    /// Get the JA3 hash of the TLS ClientHello message.
2556    ///
2557    /// Returns `None` if this is not available.
2558    pub fn get_tls_ja3_md5(&self) -> Option<[u8; 16]> {
2559        self.get_client_tls_metadata()
2560            .and_then(|v| v.client_tls_ja3_md5)
2561    }
2562
2563    /// Get the JA4 hash of the TLS ClientHello message.
2564    ///
2565    /// Returns `None` if this is not available.
2566    pub fn get_tls_ja4(&self) -> Option<&str> {
2567        self.get_client_tls_metadata()
2568            .and_then(|v| v.client_tls_ja4.as_deref())
2569    }
2570
2571    /// Get the compliance region in which this request is being processed.
2572    ///
2573    /// Returns `None` if this is not the client request.
2574    #[doc(hidden)]
2575    pub fn get_compliance_region(&self) -> Option<ComplianceRegion> {
2576        self.get_client_fingerprint_metadata()
2577            .and_then(|v| v.client_compliance_region.as_ref())
2578            .map(|v| match v.as_str() {
2579                "us" => ComplianceRegion::US,
2580                "eu" => ComplianceRegion::EU,
2581                "none" => ComplianceRegion::None,
2582                r => ComplianceRegion::Other(r.to_string()),
2583            })
2584    }
2585
2586    /// Get the raw client certificate in the mutual TLS handshake message as a
2587    /// string, panicking if it is not UTF-8.
2588    /// It is in PEM format.
2589    ///
2590    /// If this is not mTLS or available then this will return `Ok(None)`.
2591    pub fn get_tls_raw_client_certificate(&self) -> Result<Option<&str>, std::str::Utf8Error> {
2592        self.get_tls_raw_client_certificate_bytes()
2593            .map(std::str::from_utf8)
2594            .transpose()
2595    }
2596
2597    /// Like [`Self::get_tls_raw_client_certificate`], but supports non-UTF-8 byte sequences.
2598    pub fn get_tls_raw_client_certificate_bytes(&self) -> Option<&[u8]> {
2599        self.get_client_tls_metadata()
2600            .and_then(|v| v.client_tls_raw_certificate_bytes.as_deref())
2601    }
2602
2603    /// Returns the error code defined in ClientCertVerifyResult.
2604    ///
2605    /// If your service is set up for mTLS (see [this documentation](https://docs.fastly.com/en/guides/setting-up-mutual-tls-authentication)),
2606    /// then this will return information about the client's mTLS certificate, if provided.
2607    /// If the certificate is not provided, you will receive a `ClientCertVerifyResult::CertificateMissing`
2608    /// value, if the certificate properly matches your mTLS configuration, you will receive
2609    /// a `ClientCertVerifyResult::Ok`, etc.
2610    ///
2611    /// If your service is *not* set up for mTLS, this function will always return
2612    /// `Some(ClientCertVerifyResult::Ok)`.
2613    ///
2614    /// If you have a service that may be run with some domains that are mTLS protected,
2615    /// and some that are not mTLS protected, then we advise checking for both the existence
2616    /// of a certificate (by verifying that `get_tls_raw_client_certificate` or
2617    /// `get_tls_raw_client_certificate_bytes` return `Some`) before checking the result of
2618    /// this function. Doing both makes sure that you both received a certificate and that
2619    /// it was valid, and will work for both the mTLS-protected and non-mTLS-protected
2620    /// domains.
2621    ///
2622    /// This function returns `None` in the case that this `Request` is not from the
2623    /// client, but was instead generated by other parts of the code. (In other words,
2624    /// it will return `None` if this request was neither the argument to a function marked
2625    /// `fastly::main` nor generated with `Request::from_client`.)
2626    pub fn get_tls_client_cert_verify_result(&self) -> Option<ClientCertVerifyResult> {
2627        self.get_client_tls_metadata()
2628            .and_then(|v| v.client_tls_cert_verify_result)
2629    }
2630
2631    /// Get the cipher suite used to secure the client TLS connection, as a string,
2632    /// panicking if it is not UTF-8.
2633    ///
2634    /// The value returned will be consistent with the [OpenSSL
2635    /// name](https://testssl.sh/openssl-iana.mapping.html) for the cipher suite.
2636    ///
2637    /// Returns `None` if this is not the client request.
2638    ///
2639    /// # Examples
2640    ///
2641    /// ```no_run
2642    /// # use fastly::Request;
2643    /// assert_eq!(
2644    ///     Request::from_client().get_tls_cipher_openssl_name().unwrap(),
2645    ///     Some("ECDHE-RSA-AES128-GCM-SHA256"),
2646    /// );
2647    /// ```
2648    pub fn get_tls_cipher_openssl_name(&self) -> Result<Option<&str>, std::str::Utf8Error> {
2649        self.get_tls_cipher_openssl_name_bytes()
2650            .map(std::str::from_utf8)
2651            .transpose()
2652    }
2653
2654    /// Like [`Self::get_tls_cipher_openssl_name_bytes`], but supports non-UTF-8 byte strings.
2655    ///
2656    /// # Examples
2657    ///
2658    /// ```no_run
2659    /// # use fastly::Request;
2660    /// assert_eq!(
2661    ///     Request::from_client().get_tls_cipher_openssl_name_bytes(),
2662    ///     Some("ECDHE-RSA-AES128-GCM-SHA256".as_bytes()),
2663    /// );
2664    /// ```
2665    pub fn get_tls_cipher_openssl_name_bytes(&self) -> Option<&[u8]> {
2666        self.get_client_tls_metadata()
2667            .map(|v| v.client_tls_cipher_openssl_name_bytes.as_ref())
2668    }
2669
2670    /// Get the TLS protocol version used to secure the client TLS connection, as a string,
2671    /// panicking if it is not UTF-8.
2672    ///
2673    /// Returns `None` if this is not the client request.
2674    ///
2675    /// # Examples
2676    ///
2677    /// ```no_run
2678    /// # use fastly::Request;
2679    /// assert_eq!(Request::from_client().get_tls_protocol().unwrap(), Some("TLSv1.2"));
2680    /// ```
2681    pub fn get_tls_protocol(&self) -> Result<Option<&str>, std::str::Utf8Error> {
2682        self.get_tls_protocol_bytes()
2683            .map(std::str::from_utf8)
2684            .transpose()
2685    }
2686
2687    /// Like [`Self::get_tls_protocol`], but supports non-UTF-8 byte strings.
2688    ///
2689    /// # Examples
2690    ///
2691    /// ```no_run
2692    /// # use fastly::Request;
2693    /// assert_eq!(Request::from_client().get_tls_protocol_bytes(), Some("TLSv1.2".as_bytes()));
2694    /// ```
2695    pub fn get_tls_protocol_bytes(&self) -> Option<&[u8]> {
2696        self.get_client_tls_metadata()
2697            .and_then(|v| v.client_tls_protocol_bytes.as_deref())
2698    }
2699
2700    /// Set whether a `gzip`-encoded response to this request will be automatically decompressed.
2701    ///
2702    /// Enabling this will set the `Accept-Encoding` header before the request is sent, regardless
2703    /// of the original value in the request, to ensure that any values originally sent by a
2704    /// browser or other client get replaced with `gzip`, so that the backend will not try sending
2705    /// unsupported compression algorithms.
2706    ///
2707    /// If the response to this request is `gzip`-encoded, it will be presented in decompressed
2708    /// form, and the `Content-Encoding` and `Content-Length` headers will be removed.
2709    pub fn set_auto_decompress_gzip(&mut self, gzip: bool) {
2710        self.metadata
2711            .auto_decompress_response
2712            .set(ContentEncodings::GZIP, gzip);
2713    }
2714
2715    /// Builder-style equivalent of
2716    /// [`set_auto_decompress_gzip()`][`Self::set_auto_decompress_gzip()`].
2717    pub fn with_auto_decompress_gzip(mut self, gzip: bool) -> Self {
2718        self.set_auto_decompress_gzip(gzip);
2719        self
2720    }
2721
2722    /// Sets how `Content-Length` and `Transfer-Encoding` will be determined when sending this
2723    /// request.
2724    ///
2725    /// See [`FramingHeadersMode`] for details on the options.
2726    pub fn set_framing_headers_mode(&mut self, mode: FramingHeadersMode) {
2727        self.metadata.framing_headers_mode = mode;
2728    }
2729
2730    /// Builder-style equivalent of
2731    /// [`set_framing_headers_mode()`][`Self::set_framing_headers_mode()`].
2732    pub fn with_framing_headers_mode(mut self, mode: FramingHeadersMode) -> Self {
2733        self.set_framing_headers_mode(mode);
2734        self
2735    }
2736
2737    /// Create a [`Request`] from the low-level [`handle` API][`crate::handle`].
2738    pub fn from_handles(req_handle: RequestHandle, body_handle: Option<BodyHandle>) -> Self {
2739        Self {
2740            lazy_handle: LazyHandle::from_handle(req_handle)
2741                .with_field_lazy::<Version>()
2742                .with_field_lazy::<Method>()
2743                .with_field_lazy::<Url>()
2744                .with_field_lazy::<FastlyClientMetadata>()
2745                .with_field_lazy::<Option<FastlyClientTlsMetadata>>()
2746                .with_field_lazy::<Option<FastlyOriginalHeadersMetadata>>()
2747                .with_field_lazy::<FastlyClientFingerprintMetadata>()
2748                .with_field_lazy::<Option<FastlyBotDetectionMetadata>>()
2749                .finish(),
2750            body: body_handle.map(Into::into),
2751            metadata: FastlyRequestMetadata::new(),
2752        }
2753    }
2754
2755    /// Create a [`Request`] from the low-level [`handle` API][`crate::handle`],
2756    /// when the request is from a client (rather than an upstream request)
2757    pub(crate) fn from_client_handles(
2758        req_handle: fastly_sys::RequestHandle,
2759        body_handle: fastly_sys::BodyHandle,
2760    ) -> Self {
2761        // Since this is a new request, we clear the "have we sent a response" flag.
2762        crate::http::response::clear_downstream_response();
2763        let rh = RequestHandle { handle: req_handle };
2764        let bh = BodyHandle {
2765            handle: body_handle,
2766        };
2767        let mut req = Self::from_handles(rh, Some(bh));
2768        req.metadata.is_from_client = true;
2769        req
2770    }
2771
2772    fn take_request_handle(&mut self) -> RequestHandle {
2773        let mut req_handle = self.lazy_handle.take_handle();
2774        self.metadata.flush_to_handle(&mut req_handle);
2775        if let Some(exp_key_override) = self.get_override_cache_key() {
2776            use crate::experimental::RequestHandleCacheKey;
2777            req_handle.set_cache_key(&exp_key_override);
2778        }
2779        req_handle
2780    }
2781
2782    pub(crate) fn get_handles(&self) -> (&RequestHandle, Option<&BodyHandle>) {
2783        let rh = self.lazy_handle.get_handle();
2784        let bh = self.body.as_ref().map(|h| h.get_handle());
2785        (rh, bh)
2786    }
2787
2788    /// Convert a [`Request`] into the low-level [`handle` API][`crate::handle`].
2789    pub fn into_handles(mut self) -> (RequestHandle, Option<BodyHandle>) {
2790        let override_cache_key = self.get_override_cache_key();
2791        let body_handle = self.try_take_body().map(Body::into_handle);
2792        let mut req_handle = self.lazy_handle.into_handle();
2793        self.metadata.flush_to_handle(&mut req_handle);
2794
2795        if let Some(exp_key_override) = override_cache_key {
2796            use crate::experimental::RequestHandleCacheKey;
2797            req_handle.set_cache_key(&exp_key_override);
2798        }
2799
2800        (req_handle, body_handle)
2801    }
2802
2803    fn get_override_cache_key(&mut self) -> Option<CacheKey> {
2804        self.metadata
2805            .override_cache_key
2806            .take()
2807            .map(|gen| match gen {
2808                CacheKeyGen::Lazy(f) => f(self),
2809                CacheKeyGen::Set(k) => k,
2810            })
2811    }
2812
2813    /// Returns whether or not the client request had a `Fastly-Key` header which is valid for
2814    /// purging content for the service.
2815    ///
2816    /// This function ignores the current value of any `Fastly-Key` header for this request.
2817    pub fn fastly_key_is_valid(&self) -> bool {
2818        self.get_client_metadata()
2819            .map(|md| md.fastly_key_is_valid)
2820            .unwrap_or(false)
2821    }
2822
2823    /// Get the client metadata, retrieving it from the handle if necessary.
2824    fn get_client_metadata(&self) -> Option<&FastlyClientMetadata> {
2825        if !self.is_from_client() {
2826            return None;
2827        }
2828        Some(self.lazy_handle.get_field())
2829    }
2830
2831    /// Get the client fingerprint metadata, retrieving it from the handle if necessary.
2832    fn get_client_fingerprint_metadata(&self) -> Option<&FastlyClientFingerprintMetadata> {
2833        if !self.is_from_client() {
2834            return None;
2835        }
2836        Some(self.lazy_handle.get_field())
2837    }
2838
2839    /// Get the client TLS metadata, retrieving it from the handle if necessary.
2840    fn get_client_tls_metadata(&self) -> Option<&FastlyClientTlsMetadata> {
2841        if !self.is_from_client() {
2842            return None;
2843        }
2844        let v: &Option<FastlyClientTlsMetadata> = self.lazy_handle.get_field();
2845        v.as_ref()
2846    }
2847
2848    /// Get the metadata about the original headers of this request, retrieving it from the
2849    /// handle if necessary.
2850    fn get_original_headers_metadata(&self) -> Option<&FastlyOriginalHeadersMetadata> {
2851        if !self.is_from_client() {
2852            return None;
2853        }
2854        let v: &Option<FastlyOriginalHeadersMetadata> = self.lazy_handle.get_field();
2855        v.as_ref()
2856    }
2857
2858    fn get_bot_detection_metadata(&self) -> Option<&FastlyBotDetectionMetadata> {
2859        if !self.is_from_client() {
2860            return None;
2861        }
2862        let v: &Option<FastlyBotDetectionMetadata> = self.lazy_handle.get_field();
2863        v.as_ref()
2864    }
2865
2866    /// Whether bot detection analysis was performed for this request.
2867    pub fn get_bot_analyzed(&self) -> bool {
2868        self.get_bot_detection_metadata()
2869            .map(|md| md.bot_analyzed)
2870            .unwrap_or(false)
2871    }
2872
2873    /// Whether a bot was detected in the request.
2874    pub fn get_bot_detected(&self) -> bool {
2875        self.get_bot_detection_metadata()
2876            .map(|md| md.bot_detected)
2877            .unwrap_or(false)
2878    }
2879
2880    /// A string identifying the specific bot detected (e.g., `GoogleBot`, `GPTBot`, `Bingbot`).
2881    /// Returns `None` if bot detection was not executed or no bot was detected.
2882    ///
2883    /// **Warning:** String values may change over time. Use this for logging or informational purposes.
2884    /// For conditional logic, use [`get_bot_category_kind()`][Self::get_bot_category_kind].
2885    pub fn get_bot_name(&self) -> Result<Option<&str>, std::str::Utf8Error> {
2886        self.get_bot_name_bytes()
2887            .map(std::str::from_utf8)
2888            .transpose()
2889    }
2890
2891    /// Like [`Self::get_bot_name`], but supports non-UTF-8 byte sequences.
2892    pub fn get_bot_name_bytes(&self) -> Option<&[u8]> {
2893        self.get_bot_detection_metadata()
2894            .and_then(|md| md.bot_name_bytes.as_deref())
2895    }
2896
2897    /// A string indicating the type of bot detected (e.g., `SEARCH-ENGINE-CRAWLER`, `AI-CRAWLER`,
2898    /// `SUSPECTED-BOT`). Returns `None` if bot detection was not executed.
2899    ///
2900    /// **Warning:** String values may change over time. Use this for logging or informational purposes.
2901    /// For conditional logic, use [`get_bot_category_kind()`][Self::get_bot_category_kind].
2902    pub fn get_bot_category(&self) -> Result<Option<&str>, std::str::Utf8Error> {
2903        self.get_bot_category_bytes()
2904            .map(std::str::from_utf8)
2905            .transpose()
2906    }
2907
2908    /// Like [`Self::get_bot_category`], but supports non-UTF-8 byte sequences.
2909    pub fn get_bot_category_bytes(&self) -> Option<&[u8]> {
2910        self.get_bot_detection_metadata()
2911            .and_then(|md| md.category_name_bytes.as_deref())
2912    }
2913
2914    /// An enum uniquely identifying the type of bot detected.
2915    /// Returns `None` if bot detection was not executed.
2916    pub fn get_bot_category_kind(&self) -> Option<BotCategory> {
2917        self.get_bot_detection_metadata()
2918            .and_then(|md| md.category_kind.to_owned())
2919    }
2920
2921    /// Whether the detected bot is a verified bot.
2922    /// Returns `None` if bot detection was not executed or no bot was detected.
2923    pub fn get_bot_verified(&self) -> Option<bool> {
2924        self.get_bot_detection_metadata()
2925            .and_then(|md| md.bot_verified)
2926    }
2927
2928    /// Pass the WebSocket directly to a backend.
2929    ///
2930    /// This can only be used on services that have the WebSockets feature enabled and on requests
2931    /// that are valid WebSocket requests.
2932    ///
2933    /// The sending completes in the background. Once this method has been called, no other
2934    /// response can be sent to this request, and the application can exit without affecting the
2935    /// send.
2936    pub fn handoff_websocket(self, backend: &str) -> Result<(), SendError> {
2937        assert_single_downstream_response_is_sent(None, true);
2938        let cloned = self.clone_without_body();
2939        let (req_handle, _) = self.into_handles();
2940        let status = self::handle::redirect_to_websocket_proxy(req_handle, backend);
2941        if status.is_err() {
2942            Err(SendError::new(
2943                backend,
2944                cloned,
2945                SendErrorCause::status(status),
2946            ))
2947        } else {
2948            Ok(())
2949        }
2950    }
2951
2952    /// Pass the request through the Fanout GRIP proxy and on to a backend.
2953    ///
2954    /// This can only be used on services that have the Fanout feature enabled.
2955    ///
2956    /// The sending completes in the background. Once this method has been called, no other
2957    /// response can be sent to this request, and the application can exit without affecting the
2958    /// send.
2959    pub fn handoff_fanout(self, backend: &str) -> Result<(), SendError> {
2960        assert_single_downstream_response_is_sent(None, true);
2961        let cloned = self.clone_without_body();
2962        let (req_handle, _) = self.into_handles();
2963        let status = self::handle::redirect_to_grip_proxy(req_handle, backend);
2964        if status.is_err() {
2965            Err(SendError::new(
2966                backend,
2967                cloned,
2968                SendErrorCause::status(status),
2969            ))
2970        } else {
2971            Ok(())
2972        }
2973    }
2974
2975    /// Send this request on behalf of another service.
2976    ///
2977    /// Running it on behalf of another service means that the cache store used for this request
2978    /// will be of that service, not the currently running one.
2979    #[doc = include_str!("../../docs/snippets/privileged_behalf.md")]
2980    pub fn on_behalf_of<S: AsRef<str>>(self, service: S) -> Result<Self, FastlyStatus> {
2981        let (mut req_handle, body_handle) = self.into_handles();
2982        req_handle.on_behalf_of(service.as_ref())?;
2983        Ok(Self::from_handles(req_handle, body_handle))
2984    }
2985
2986    pub(crate) fn with_metadata(mut self, metadata: FastlyRequestMetadata) -> Self {
2987        self.metadata = metadata;
2988        self
2989    }
2990
2991    /// Set the cache key to be used when attempting to satisfy this request from a cached response.
2992    ///
2993    /// The cache key must be exactly 32 bytes long.
2994    pub fn set_cache_key(&mut self, key: impl Into<Vec<u8>>) {
2995        self.metadata.override_cache_key = Some(CacheKeyGen::Set(key.into().into()));
2996    }
2997
2998    /// Builder-style equivalent of [`set_cache_key()`](Self::set_cache_key()).
2999    pub fn with_cache_key(mut self, key: impl Into<Vec<u8>>) -> Self {
3000        self.set_cache_key(key);
3001        self
3002    }
3003
3004    /// Gets whether the request is potentially cacheable.
3005    pub fn is_cacheable(&self) -> bool {
3006        self.lazy_handle.get_handle().is_cacheable()
3007    }
3008
3009    /// Builder-style equivalent of [`set_image_optimizer()`][`Self::set_image_optimizer()`].
3010    pub fn with_image_optimizer(mut self, options: image_optimizer::ImageOptimizerOptions) -> Self {
3011        self.set_image_optimizer(options);
3012        self
3013    }
3014
3015    /// Set [`image_optimizer::ImageOptimizerOptions`] for this request to proxy it
3016    /// to Image Optimizer.
3017    ///
3018    /// Transformations can be expressed through `ImageOptimizerOptions`
3019    /// or query parameters on the request. Options set on `ImageOptimizerOptions`
3020    /// take precedence over query parameters with the same name.
3021    ///
3022    /// It is not necessary to include additional shielding logic for Image Optimization,
3023    /// the required [`ImageOptimizerRegion`][`image_optimizer::ImageOptimizerRegion`]
3024    /// handles this for you. It is recommended to pick a region close to your origin.
3025    ///
3026    /// # Examples
3027    /// Send a request for `image.jpeg` with params `format=webp` to Image Optimizer.
3028    ///
3029    /// ```no_run
3030    /// # use fastly::{Error, Request};
3031    /// # use fastly::image_optimizer::{Format, ImageOptimizerOptions, ImageOptimizerRegion};
3032    /// # fn f() -> Result<(), Error> {
3033    /// let mut image_opto_opts = ImageOptimizerOptions::from_region(ImageOptimizerRegion::UsWest);
3034    /// image_opto_opts.format = Some(Format::WebP);
3035    /// let mut req = Request::get("https://example.com/image.jpeg");
3036    /// req.set_image_optimizer(image_opto_opts);
3037    /// // `origin` is a named backend on your service.
3038    /// req.send("origin")?;
3039    /// # Ok(())
3040    /// # }
3041    /// ```
3042    ///
3043    /// Override the `format` parameter on the request if it exists, turning it to `auto`.
3044    /// ```no_run
3045    /// # use fastly::{Error, Request, Response};
3046    /// # use fastly::image_optimizer::{Format, ImageOptimizerOptions, ImageOptimizerRegion};
3047    /// #[fastly::main]
3048    /// fn main(mut req: Request) -> Result<Response, Error> {
3049    /// let mut image_opto_opts = ImageOptimizerOptions::from_region(ImageOptimizerRegion::UsWest);
3050    /// if let Some(_param) = req.get_query_parameter("format") {
3051    ///     image_opto_opts.format = Some(Format::Auto);
3052    /// }
3053    /// req.set_image_optimizer(image_opto_opts);
3054    /// Ok(req.send("origin")?)
3055    /// }
3056    /// ```
3057    ///
3058    /// # Overrides
3059    ///
3060    /// At the moment, calling this function overrides custom caching behavior, such as
3061    /// [`Request::set_surrogate_key()`], [`Request::set_ttl()`] and [`Request::set_pci()`].
3062    /// Caching behavior is currently determined by the cache control headers set on the source
3063    /// image.
3064    ///
3065    /// Additionally, any before and after send hooks are not executed.
3066    pub fn set_image_optimizer(&mut self, options: image_optimizer::ImageOptimizerOptions) {
3067        self.metadata.image_opto_options = Some(options);
3068    }
3069}
3070
3071impl From<Request> for http::Request<Body> {
3072    fn from(val: Request) -> Self {
3073        let mut req = http::Request::new(val.body.unwrap_or_else(Body::new));
3074        req.extensions_mut().insert(val.metadata);
3075        *req.method_mut() = val.lazy_handle.get_field::<Method>().clone();
3076        *req.uri_mut() = String::from(val.lazy_handle.get_field::<Url>().as_str())
3077            .parse()
3078            .expect("Url to Uri conversion shouldn't fail, but did");
3079        *req.version_mut() = *val.lazy_handle.get_field::<Version>();
3080        *req.headers_mut() = val.lazy_handle.into();
3081        req
3082    }
3083}
3084
3085impl From<http::Request<Body>> for Request {
3086    fn from(from: http::Request<Body>) -> Self {
3087        let (mut parts, body) = from.into_parts();
3088        let metadata: FastlyRequestMetadata = parts
3089            .extensions
3090            .remove()
3091            .unwrap_or_else(FastlyRequestMetadata::new);
3092        Request {
3093            lazy_handle: LazyHandle::detached()
3094                .with_headers(parts.headers)
3095                .with_field(parts.version)
3096                .with_field(parts.method)
3097                .with_field(
3098                    Url::parse(&parts.uri.to_string())
3099                        .expect("Uri to Url conversion shouldn't fail, but did"),
3100                )
3101                .finish(),
3102            body: Some(body),
3103            metadata,
3104        }
3105    }
3106}
3107
3108impl From<RequestHandle> for Request {
3109    fn from(handle: RequestHandle) -> Self {
3110        Self::from_handles(handle, None)
3111    }
3112}
3113
3114/// The reason that a request sent to a backend failed.
3115#[non_exhaustive]
3116#[derive(Debug, Error)]
3117pub enum SendErrorCause {
3118    /// The system encountered a timeout when trying to find an IP address for the backend
3119    /// hostname.
3120    #[error("DNS timeout")]
3121    DnsTimeout,
3122    /// The system encountered a DNS error when trying to find an IP address for the backend
3123    /// hostname.
3124    ///
3125    /// The `rcode` and `info_code` fields may be set with more detail.
3126    // TODO ACF 2023-08-18: figure out what to do with the fields. probably would be best to seal
3127    // the details of `SendErrorCause` and make folks use accessor methods or `Display`
3128    #[error("DNS error (rcode={rcode:?}, info_code={info_code:?})")]
3129    DnsError {
3130        /// The [DNS rcode], if available.
3131        ///
3132        /// [DNS rcode]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
3133        rcode: Option<u16>,
3134        /// The [DNS info-code], if available.
3135        ///
3136        /// [DNS info-code]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes
3137        info_code: Option<u16>,
3138    },
3139    /// The system cannot determine which backend to use, or the specified backend was invalid.
3140    #[error("Destination not found")]
3141    DestinationNotFound,
3142    /// The system considers the backend to be unavailable; e.g., recent attempts to communicate
3143    /// with it may have failed, or a health check may indicate that it is down.
3144    #[error("Destination unavailable")]
3145    DestinationUnavailable,
3146    /// The system cannot find a route to the next-hop IP address.
3147    #[error("Destination IP unroutable")]
3148    DestinationIpUnroutable,
3149    /// The system's connection to the backend was refused.
3150    #[error("Connection refused")]
3151    ConnectionRefused,
3152    /// The system's connection to the backend was closed before a complete response was
3153    /// received.
3154    #[error("Connection terminated")]
3155    ConnectionTerminated,
3156    /// The system's attempt to open a connection to the backend timed out.
3157    #[error("Connection timeout")]
3158    ConnectionTimeout,
3159    /// The system is configured to limit the number of connections it has to the backend, and
3160    /// that limit has been exceeded.
3161    #[error("Connection limit reached")]
3162    ConnectionLimitReached,
3163    /// The system encountered a TLS error when communicating with the backend, either during the
3164    /// handshake or afterwards.
3165    #[error("TLS protocol error")]
3166    TlsProtocolError,
3167    /// The system encountered an error when verifying the certificate presented by the backend.
3168    #[error("TLS certificate error")]
3169    TlsCertificateError,
3170    /// The system received a TLS alert from the backend.
3171    ///
3172    /// The `alert_id` field will specific which alert occurred.
3173    #[error("TLS alert received (alert_id={alert_id:?})")]
3174    TlsAlertReceived {
3175        /// The [TLS alert value], if it is available.
3176        ///
3177        /// [TLS alert value]: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6
3178        alert_id: Option<u8>,
3179    },
3180    /// The system encountered an error with the backend TLS configuration.
3181    #[error("TLS configuration error")]
3182    TlsConfigurationError,
3183    /// The system received an incomplete response to the request from the backend.
3184    #[error("Incomplete HTTP response")]
3185    HttpIncompleteResponse,
3186    /// The system received a response to the request whose header section was considered too
3187    /// large.
3188    #[error("HTTP response header section too large")]
3189    HttpResponseHeaderSectionTooLarge,
3190    /// The system received a response to the request whose body was considered too large.
3191    #[error("HTTP response body too large")]
3192    HttpResponseBodyTooLarge,
3193    /// The system reached a configured time limit waiting for the complete response.
3194    #[error("HTTP response timeout")]
3195    HttpResponseTimeout,
3196    /// The system received a response to the request whose status code or reason phrase was invalid.
3197    #[error("HTTP response status invalid")]
3198    HttpResponseStatusInvalid,
3199    /// The process of negotiating an upgrade of the HTTP version between the system and backend
3200    /// failed.
3201    #[error("HTTP upgrade failed")]
3202    HttpUpgradeFailed,
3203    /// The system received an unexpected HTTP/2 error response from the backend.
3204    #[error("HTTP/2 stream error (frame_type={frame_type}, error_code={error_code})")]
3205    Http2StreamError {
3206        /// The [HTTP/2 Frame Type] that sent us an error on the HTTP/2 stream.
3207        ///
3208        /// [HTTP/2 Frame Type]: https://www.iana.org/assignments/http2-parameters/http2-parameters.xhtml#frame-type
3209        frame_type: u8,
3210        /// The [HTTP/2 Error Code] that was contained in the HTTP/2 frame.
3211        ///
3212        /// [HTTP/2 Error Code]: https://www.iana.org/assignments/http2-parameters/http2-parameters.xhtml#error-code
3213        error_code: u32,
3214    },
3215    /// The system encountered an HTTP protocol error when communicating with the backend. This
3216    /// error will only be used when a more specific one is not defined.
3217    #[error("HTTP protocol error")]
3218    HttpProtocolError,
3219    /// An invalid cache key was provided for the request.
3220    #[error("HTTP request cache key invalid")]
3221    HttpRequestCacheKeyInvalid,
3222    /// An invalid URI was provided for the request.
3223    #[error("HTTP request URI invalid")]
3224    HttpRequestUriInvalid,
3225    /// A caching limit was reached.
3226    #[error("HTTP caching limit exceeded")]
3227    HttpCacheLimitExceeded,
3228    /// A caching limit was reached.
3229    #[error("HTTP caching API is not enabled; please contact support for help")]
3230    HttpCacheApiUnsupported,
3231    /// An I/O error was encountered.
3232    #[error("I/O error: {0}")]
3233    IoError(#[from] std::io::Error),
3234    /// Image Optimizer is not supported on this service.
3235    #[error("Image Optimizer is not enabled on this service; please contact support for help")]
3236    ImageOptimizerUnsupported,
3237    /// The system encountered an unexpected internal error.
3238    #[error("Internal error (status={0:?})")]
3239    InternalError(Option<FastlyStatus>),
3240    /// A custom error occurred while processing the request.
3241    #[error("Custom HTTP send error: {0}")]
3242    Custom(#[source] anyhow::Error),
3243}
3244
3245impl SendErrorCause {
3246    pub(crate) fn status(cause: fastly_shared::FastlyStatus) -> Self {
3247        match cause {
3248            fastly_shared::FastlyStatus::HTTPINVALID => SendErrorCause::HttpProtocolError,
3249            fastly_shared::FastlyStatus::HTTPINCOMPLETE => SendErrorCause::HttpIncompleteResponse,
3250            fastly_shared::FastlyStatus::HTTPHEADTOOLARGE => {
3251                SendErrorCause::HttpResponseHeaderSectionTooLarge
3252            }
3253            fastly_shared::FastlyStatus::HTTPINVALIDSTATUS => {
3254                SendErrorCause::HttpResponseStatusInvalid
3255            }
3256            fastly_shared::FastlyStatus::INVAL => SendErrorCause::DestinationNotFound,
3257            other => SendErrorCause::InternalError(Some(other)),
3258        }
3259    }
3260
3261    /// Return `Err(SendErrorCause)` based on the provided `SendErrorDetail` ABI struct and optional
3262    /// `FastlyStatus`, or `Ok(())` if no error occurred.
3263    ///
3264    /// This is useful for `?`ing out of a function after calling an ABI function that returns both
3265    /// a `SendErrorDetail` and a `FastlyStatus`, allowing control flow to continue only if the call
3266    /// was successful.
3267    ///
3268    /// If `detail.tag` is `Ok`, or if `Some(FastlyStatus::OK)` is provided, the result is `Ok(())`.
3269    ///
3270    /// If no `FastlyStatus` is provided, the result is determined entirely from the
3271    /// `SendErrorDetail`. If both are provided, but the `SendErrorDetail` is uninitialized, the
3272    /// behavior falls back to the legacy `FastlyStatus`-determined behavior.
3273    pub(crate) fn detail_and_status(
3274        detail: SendErrorDetail,
3275        status: Option<FastlyStatus>,
3276    ) -> Result<(), Self> {
3277        use fastly_sys::fastly_http_req::{SendErrorDetailMask as Mask, SendErrorDetailTag as Tag};
3278        // if status is present and is OK, we're done
3279        if status.as_ref().map(FastlyStatus::is_ok).unwrap_or(false) {
3280            return Ok(());
3281        }
3282        match detail.tag {
3283            Tag::Uninitialized => {
3284                if let Some(status) = status {
3285                    // fall back to the legacy `FastlyStatus` logic if the details weren't populated
3286                    Err(SendErrorCause::status(status))
3287                } else {
3288                    // otherwise fall back to the generic internal error
3289                    Err(SendErrorCause::InternalError(None))
3290                }
3291            }
3292            Tag::Ok => Ok(()),
3293            Tag::DnsTimeout => Err(SendErrorCause::DnsTimeout),
3294            Tag::DnsError => {
3295                let rcode = if detail.mask.contains(Mask::DNS_ERROR_RCODE) {
3296                    Some(detail.dns_error_rcode)
3297                } else {
3298                    None
3299                };
3300                let info_code = if detail.mask.contains(Mask::DNS_ERROR_INFO_CODE) {
3301                    Some(detail.dns_error_info_code)
3302                } else {
3303                    None
3304                };
3305                Err(Self::DnsError { rcode, info_code })
3306            }
3307            Tag::DestinationNotFound => Err(SendErrorCause::DestinationNotFound),
3308            Tag::DestinationUnavailable => Err(SendErrorCause::DestinationUnavailable),
3309            Tag::DestinationIpUnroutable => Err(SendErrorCause::DestinationIpUnroutable),
3310            Tag::ConnectionRefused => Err(SendErrorCause::ConnectionRefused),
3311            Tag::ConnectionTerminated => Err(SendErrorCause::ConnectionTerminated),
3312            Tag::ConnectionTimeout => Err(SendErrorCause::ConnectionTimeout),
3313            Tag::ConnectionLimitReached => Err(SendErrorCause::ConnectionLimitReached),
3314            Tag::TlsProtocolError => Err(SendErrorCause::TlsProtocolError),
3315            Tag::TlsCertificateError => Err(SendErrorCause::TlsCertificateError),
3316            Tag::TlsAlertReceived => {
3317                let alert_id = if detail.mask.contains(Mask::TLS_ALERT_ID) {
3318                    Some(detail.tls_alert_id)
3319                } else {
3320                    None
3321                };
3322                Err(Self::TlsAlertReceived { alert_id })
3323            }
3324            Tag::TlsConfigurationError => Err(SendErrorCause::TlsConfigurationError),
3325            Tag::H2Error => {
3326                let (frame_type, error_code) = if detail.mask.contains(Mask::H2_ERROR) {
3327                    (detail.h2_error_frame, detail.h2_error_code)
3328                } else {
3329                    (0, 0)
3330                };
3331                Err(Self::Http2StreamError {
3332                    frame_type,
3333                    error_code,
3334                })
3335            }
3336            Tag::HttpIncompleteResponse => Err(SendErrorCause::HttpIncompleteResponse),
3337            Tag::HttpResponseHeaderSectionTooLarge => {
3338                Err(SendErrorCause::HttpResponseHeaderSectionTooLarge)
3339            }
3340            Tag::HttpResponseBodyTooLarge => Err(SendErrorCause::HttpResponseBodyTooLarge),
3341            Tag::HttpResponseTimeout => Err(SendErrorCause::HttpResponseTimeout),
3342            Tag::HttpResponseStatusInvalid => Err(SendErrorCause::HttpResponseStatusInvalid),
3343            Tag::HttpUpgradeFailed => Err(SendErrorCause::HttpUpgradeFailed),
3344            Tag::HttpProtocolError => Err(SendErrorCause::HttpProtocolError),
3345            Tag::HttpRequestCacheKeyInvalid => Err(SendErrorCause::HttpRequestCacheKeyInvalid),
3346            Tag::HttpRequestUriInvalid => Err(SendErrorCause::HttpRequestUriInvalid),
3347            Tag::InternalError => Err(SendErrorCause::InternalError(status)),
3348            other => Err(SendErrorCause::Custom(anyhow!(
3349                "unknown error tag: {other:?}"
3350            ))),
3351        }
3352    }
3353}
3354
3355impl From<HttpCacheError> for SendErrorCause {
3356    fn from(err: HttpCacheError) -> Self {
3357        match err {
3358            HttpCacheError::LimitExceeded => Self::HttpCacheLimitExceeded,
3359            HttpCacheError::InvalidOperation => Self::InternalError(Some(FastlyStatus::INVAL)),
3360            HttpCacheError::Unsupported => Self::InternalError(Some(FastlyStatus::UNSUPPORTED)),
3361            HttpCacheError::InvalidBackend => Self::DestinationNotFound,
3362            HttpCacheError::Other(fs) => Self::InternalError(Some(fs)),
3363        }
3364    }
3365}
3366
3367impl From<SendError> for SendErrorCause {
3368    fn from(err: SendError) -> Self {
3369        err.error
3370    }
3371}
3372
3373/// Indicates how a response can be looked up in cache.
3374enum CachingMode {
3375    /// Use guest-side caching.
3376    GuestCaching,
3377    /// Use host-side caching. This is meant for requests whose responses
3378    /// can't be looked up guest-side.
3379    HostCaching,
3380    /// Requests meant for Image Optimizer should skip guest and host caching.
3381    ImageOptimizer(Box<image_optimizer::ImageOptimizerOptions>),
3382}
3383
3384/// Indicates how a request and its data should be handled for services that
3385/// have been configured to only be processed within POPs located within a
3386/// specified compliance region for regulatory purposes.
3387#[derive(Clone, Debug, Eq, PartialEq)]
3388#[doc(hidden)]
3389pub enum ComplianceRegion {
3390    /// A request not limited to processing within a compliance region.
3391    None,
3392
3393    /// A request that should be processed within the European Union.
3394    EU,
3395
3396    /// A request that should be processed within the United States.
3397    US,
3398
3399    /// A request that should be processed within another compliance region.
3400    Other(String),
3401}
3402
3403impl AsRef<str> for ComplianceRegion {
3404    fn as_ref(&self) -> &str {
3405        match self {
3406            Self::None => "none",
3407            Self::EU => "eu",
3408            Self::US => "us",
3409            Self::Other(region) => region.as_str(),
3410        }
3411    }
3412}
3413
3414impl Deref for ComplianceRegion {
3415    type Target = str;
3416
3417    fn deref(&self) -> &Self::Target {
3418        self.as_ref()
3419    }
3420}
3421
3422/// An error that occurred while sending a request.
3423///
3424/// While the body of a request is always consumed when sent, you can recover the headers and other
3425/// request metadata of the request that failed using `SendError::into_sent_req()`.
3426///
3427/// use [`SendError::root_cause()`] to inspect details about what caused the error.
3428#[derive(Debug, Error)]
3429#[error("error sending request: {error} to backend {backend}")]
3430pub struct SendError {
3431    backend: String,
3432    // TODO 2024-07-31: this now forces request headers to be copied an extra
3433    // time, while it used to be ~free. Once async fn support is added and
3434    // pending request select is no longer present, we should be able to drop
3435    // this forced clone and let users clone if needed.
3436    //
3437    // We use an `http::Request` here because we need the value to be `Send` and
3438    // `Sync` for `SendError` to be a valid error type.
3439    //
3440    // Boxed because of its large size.
3441    sent_req: Box<http::Request<Body>>,
3442    #[source]
3443    error: SendErrorCause,
3444}
3445
3446impl SendError {
3447    pub(crate) fn new(
3448        backend: impl Into<String>,
3449        sent_req: Request,
3450        error: SendErrorCause,
3451    ) -> Self {
3452        SendError {
3453            backend: backend.into(),
3454            sent_req: Box::new(sent_req.into()),
3455            error,
3456        }
3457    }
3458
3459    /// Get the name of the backend that returned this error.
3460    pub fn backend_name(&self) -> &str {
3461        &self.backend
3462    }
3463
3464    /// Get the underlying cause of this `SendError`.
3465    ///
3466    /// This is the same cause that would be returned by `err.source().downcast_ref::<SendErrorCause>()`, but more direct.
3467    pub fn root_cause(&self) -> &SendErrorCause {
3468        &self.error
3469    }
3470
3471    /// Convert the error back into the request that was originally sent.
3472    ///
3473    /// Since the original request's body is consumed by sending it, the body in the returned
3474    /// request is empty. To add a new body to the request, use [`Request::with_body()`], for example:
3475    ///
3476    /// ```no_run
3477    /// # use fastly::{Body, Error, Request};
3478    /// # fn f(bereq: Request) -> Result<(), Error> {
3479    /// if let Err(e) = bereq.send("my_backend") {
3480    ///     let new_body = Body::from("something new");
3481    ///     let new_req = e.into_sent_req().with_body(new_body);
3482    ///     new_req.send("my_other_backend")?;
3483    /// }
3484    /// # Ok(())
3485    /// # }
3486    /// ```
3487    pub fn into_sent_req(self) -> Request {
3488        (*self.sent_req).into()
3489    }
3490}
3491
3492/// Check whether a request looks suitable for sending to a backend.
3493///
3494/// Note that this is *not* meant to be a filter for things that could cause security issues, it is
3495/// only meant to catch errors before the hostcalls do in order to yield friendlier error messages.
3496fn validate_request(req: &Request) -> Result<(), SendErrorCause> {
3497    let scheme_ok = req.get_url().scheme().eq_ignore_ascii_case("http")
3498        || req.get_url().scheme().eq_ignore_ascii_case("https");
3499    if scheme_ok && req.get_url().has_authority() {
3500        Ok(())
3501    } else {
3502        Err(SendErrorCause::HttpRequestUriInvalid)
3503    }
3504}
3505
3506/// A `PendingRequest` paired with an open cache transaction.
3507///
3508/// Represents an in-progress backend request that will be used to complete the
3509/// cache transaction.
3510#[derive(Debug)]
3511struct PendingBackendRequestForCaching {
3512    cache_handle: HttpCacheHandle,
3513    pending_req_handle: PendingRequestHandle,
3514    after_send: Option<AfterSend>,
3515    cache_override: CacheOverride,
3516}
3517
3518impl PendingBackendRequestForCaching {
3519    /// Synchronously complete the backend request, producing a
3520    /// `CandidateResponse` that has executed any after-send hooks.
3521    fn into_candidate(self) -> Result<CandidateResponse, SendErrorCause> {
3522        let (resp_handle, resp_body_handle) = self.pending_req_handle.wait()?;
3523        let mut candidate = CandidateResponse::new(
3524            self.cache_handle,
3525            &self.cache_override,
3526            resp_handle,
3527            resp_body_handle,
3528        )?;
3529        if let Some(f) = &self.after_send {
3530            (f.after_send)(&mut candidate)?;
3531        }
3532        Ok(candidate)
3533    }
3534
3535    /// Prepares this pending request to be used for backend revalidation;
3536    /// completion will automatically occur upon drop.
3537    fn into_background_revalidation(self) -> BackgroundRevalidation {
3538        BackgroundRevalidation {
3539            pending: Some(self),
3540        }
3541    }
3542}
3543
3544#[derive(Debug)]
3545pub(crate) struct BackgroundRevalidation {
3546    pending: Option<PendingBackendRequestForCaching>,
3547}
3548
3549impl Drop for BackgroundRevalidation {
3550    fn drop(&mut self) {
3551        // if a background revalidation fails, we just drop the error on the floor
3552        self.pending
3553            .take()
3554            .and_then(|p| p.into_candidate().ok())
3555            .and_then(|c| c.apply_in_background().ok());
3556    }
3557}