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}