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