pub(crate) mod handle;
use self::handle::ResponseHandle;
use super::cache;
use super::request::BackgroundRevalidation;
use super::{
body::{self, Body, StreamingBody},
LazyHandle, Request,
};
use crate::http::header::CDN_LOOP;
use crate::{
backend::Backend,
convert::{Borrowable, ToHeaderName, ToHeaderValue, ToStatusCode},
experimental::BodyExt,
handle::BodyHandle,
};
use fastly_shared::{FramingHeadersMode, HttpKeepaliveMode};
use http::header::{self, HeaderName, HeaderValue};
use http::{StatusCode, Version};
use mime::Mime;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::sync::atomic::AtomicBool;
use std::time::Duration;
use std::{
borrow::Cow,
io::{BufRead, Write},
net::SocketAddr,
};
#[derive(Debug)]
pub struct Response {
pub(crate) lazy_handle: LazyHandle<ResponseHandle>,
body: Option<Body>,
pub(crate) metadata: FastlyResponseMetadata,
pub(crate) sent_req: Option<Request>,
pub(crate) background_revalidation: Option<BackgroundRevalidation>,
}
#[derive(Clone, Debug)]
pub(crate) struct FastlyResponseMetadata {
framing_headers_mode: FramingHeadersMode,
http_keepalive_mode: HttpKeepaliveMode,
remote_addr: Option<SocketAddr>,
pub(crate) backend: Option<Backend>,
pub(crate) cache_options: Option<cache::WriteOptions>,
pub(crate) cache_storage_action: Option<cache::HttpStorageAction>,
pub(crate) cache_hits: Option<u64>,
}
impl FastlyResponseMetadata {
fn new() -> Self {
Self {
remote_addr: None,
framing_headers_mode: FramingHeadersMode::Automatic,
http_keepalive_mode: HttpKeepaliveMode::Automatic,
backend: None,
cache_options: None,
cache_storage_action: None,
cache_hits: None,
}
}
}
impl Default for Response {
fn default() -> Self {
Self::new()
}
}
impl Response {
pub fn new() -> Self {
Self {
lazy_handle: LazyHandle::detached()
.with_field(StatusCode::OK)
.with_field(Version::HTTP_11)
.finish(),
body: None,
metadata: FastlyResponseMetadata::new(),
sent_req: None,
background_revalidation: None,
}
}
pub fn is_from_backend(&self) -> bool {
self.metadata.backend.is_some()
}
pub fn clone_without_body(&self) -> Response {
Self {
lazy_handle: self.lazy_handle.clone(),
body: None,
metadata: self.metadata.clone(),
sent_req: self.sent_req.as_ref().map(Request::clone_without_body),
background_revalidation: None,
}
}
#[doc = include_str!("../../docs/snippets/clones-body.md")]
pub fn clone_with_body(&mut self) -> Response {
let mut new_resp = self.clone_without_body();
if self.has_body() {
let mut body = self.take_body();
for chunk in body.read_chunks(4096) {
let chunk = chunk.expect("can read body chunk");
new_resp
.get_body_mut()
.write_all(&chunk)
.expect("failed to write to cloned body");
self.get_body_mut()
.write_all(&chunk)
.expect("failed to write to cloned body");
}
if let Ok(trailers) = body.get_trailers() {
for (k, v) in trailers.iter() {
self.get_body_mut().append_trailer(k, v);
new_resp.get_body_mut().append_trailer(k, v);
}
}
}
new_resp
}
#[doc = include_str!("../../docs/snippets/body-argument.md")]
pub fn from_body(body: impl Into<Body>) -> Self {
Self::new().with_body(body)
}
#[doc = include_str!("../../docs/snippets/body-argument.md")]
pub fn from_status(status: impl ToStatusCode) -> Self {
Self::new().with_status(status)
}
pub fn see_other(destination: impl ToHeaderValue) -> Self {
Self::new()
.with_status(StatusCode::SEE_OTHER)
.with_header(header::LOCATION, destination)
}
pub fn redirect(destination: impl ToHeaderValue) -> Self {
Self::new()
.with_status(StatusCode::PERMANENT_REDIRECT)
.with_header(header::LOCATION, destination)
}
pub fn temporary_redirect(destination: impl ToHeaderValue) -> Self {
Self::new()
.with_status(StatusCode::TEMPORARY_REDIRECT)
.with_header(header::LOCATION, destination)
}
pub fn with_body(mut self, body: impl Into<Body>) -> Self {
self.set_body(body);
self
}
pub fn has_body(&self) -> bool {
self.body.is_some()
}
#[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
pub fn get_body_mut(&mut self) -> &mut Body {
self.body.get_or_insert_with(Body::new)
}
pub fn try_get_body_mut(&mut self) -> Option<&mut Body> {
self.body.as_mut()
}
pub fn get_body_prefix_mut(&mut self, length: usize) -> body::Prefix<'_> {
self.get_body_mut().get_prefix_mut(length)
}
pub fn get_body_prefix_str_mut(&mut self, length: usize) -> body::PrefixString<'_> {
self.get_body_mut().get_prefix_str_mut(length)
}
pub fn try_get_body_prefix_str_mut(
&mut self,
length: usize,
) -> Result<body::PrefixString<'_>, std::str::Utf8Error> {
self.get_body_mut().try_get_prefix_str_mut(length)
}
#[doc = include_str!("../../docs/snippets/body-argument.md")]
#[doc = include_str!("../../docs/snippets/discards-body.md")]
pub fn set_body(&mut self, body: impl Into<Body>) {
self.body = Some(body.into());
}
#[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
pub fn take_body(&mut self) -> Body {
self.body.take().unwrap_or_else(Body::new)
}
pub fn try_take_body(&mut self) -> Option<Body> {
self.body.take()
}
#[doc = include_str!("../../docs/snippets/body-append-constant-time.md")]
pub fn append_body(&mut self, other: Body) {
if let Some(ref mut body) = &mut self.body {
body.append(other);
} else {
self.body = Some(other);
}
}
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
pub fn into_body_bytes(mut self) -> Vec<u8> {
self.take_body_bytes()
}
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
#[doc = include_str!("../../docs/snippets/panics-reqresp-intobody-utf8.md")]
pub fn into_body_str(mut self) -> String {
self.take_body_str()
}
#[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
pub fn into_body_str_lossy(mut self) -> String {
self.take_body_str_lossy()
}
#[doc = include_str!("../../docs/snippets/creates-empty-body.md")]
pub fn into_body(self) -> Body {
self.body.unwrap_or_else(Body::new)
}
pub fn try_into_body(self) -> Option<Body> {
self.body
}
pub fn with_body_text_plain(mut self, body: &str) -> Self {
self.set_body_text_plain(body);
self
}
#[doc = include_str!("../../docs/snippets/discards-body.md")]
#[doc = include_str!("../../docs/snippets/sets-text-plain.md")]
pub fn set_body_text_plain(&mut self, body: &str) {
self.body = Some(Body::from(body));
self.set_content_type(mime::TEXT_PLAIN_UTF_8);
}
pub fn with_body_text_html(mut self, body: &str) -> Self {
self.set_body_text_html(body);
self
}
#[doc = include_str!("../../docs/snippets/discards-body.md")]
#[doc = include_str!("../../docs/snippets/sets-text-html.md")]
pub fn set_body_text_html(&mut self, body: &str) {
self.body = Some(Body::from(body));
self.set_content_type(mime::TEXT_HTML_UTF_8);
}
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
#[doc = include_str!("../../docs/snippets/panics-reqresp-takebody-utf8.md")]
pub fn take_body_str(&mut self) -> String {
if let Some(body) = self.try_take_body() {
body.into_string()
} else {
String::new()
}
}
#[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
pub fn take_body_str_lossy(&mut self) -> String {
if let Some(body) = self.try_take_body() {
String::from_utf8_lossy(&body.into_bytes()).to_string()
} else {
String::new()
}
}
pub fn read_body_lines(&mut self) -> std::io::Lines<&mut Body> {
self.get_body_mut().lines()
}
pub fn with_body_octet_stream(mut self, body: &[u8]) -> Self {
self.set_body_octet_stream(body);
self
}
#[doc = include_str!("../../docs/snippets/discards-body.md")]
#[doc = include_str!("../../docs/snippets/sets-app-octet-stream.md")]
pub fn set_body_octet_stream(&mut self, body: &[u8]) {
self.body = Some(Body::from(body));
self.set_content_type(mime::APPLICATION_OCTET_STREAM);
}
#[doc = include_str!("../../docs/snippets/buffers-body-reqresp.md")]
pub fn take_body_bytes(&mut self) -> Vec<u8> {
if let Some(body) = self.try_take_body() {
body.into_bytes()
} else {
Vec::new()
}
}
pub fn read_body_chunks(
&mut self,
chunk_size: usize,
) -> impl Iterator<Item = Result<Vec<u8>, std::io::Error>> + '_ {
self.get_body_mut().read_chunks(chunk_size)
}
pub fn with_body_json(mut self, value: &impl Serialize) -> Result<Self, serde_json::Error> {
self.set_body_json(value)?;
Ok(self)
}
#[doc = include_str!("../../docs/snippets/discards-body.md")]
#[doc = include_str!("../../docs/snippets/sets-app-json.md")]
pub fn set_body_json(&mut self, value: &impl Serialize) -> Result<(), serde_json::Error> {
self.body = Some(Body::new());
serde_json::to_writer(self.get_body_mut(), value)?;
self.set_content_type(mime::APPLICATION_JSON);
Ok(())
}
pub fn take_body_json<T: DeserializeOwned>(&mut self) -> Result<T, serde_json::Error> {
if let Some(body) = self.try_take_body() {
serde_json::from_reader(body)
} else {
serde_json::from_reader(std::io::empty())
}
}
pub fn with_body_form(
mut self,
value: &impl Serialize,
) -> Result<Self, serde_urlencoded::ser::Error> {
self.set_body_form(value)?;
Ok(self)
}
#[doc = include_str!("../../docs/snippets/discards-body.md")]
pub fn set_body_form(
&mut self,
value: &impl Serialize,
) -> Result<(), serde_urlencoded::ser::Error> {
self.body = Some(Body::new());
let s = serde_urlencoded::to_string(value)?;
self.set_body(s);
self.set_content_type(mime::APPLICATION_WWW_FORM_URLENCODED);
Ok(())
}
#[doc = include_str!("../../docs/snippets/returns-deserializeowned.md")]
pub fn take_body_form<T: DeserializeOwned>(
&mut self,
) -> Result<T, serde_urlencoded::de::Error> {
if let Some(body) = self.try_take_body() {
serde_urlencoded::from_reader(body)
} else {
serde_urlencoded::from_reader(std::io::empty())
}
}
pub fn get_content_type(&self) -> Option<Mime> {
self.get_header_str(http::header::CONTENT_TYPE)
.and_then(|v| v.parse().ok())
}
pub fn with_content_type(mut self, mime: Mime) -> Self {
self.set_content_type(mime);
self
}
pub fn set_content_type(&mut self, mime: Mime) {
self.set_header(http::header::CONTENT_TYPE, mime.as_ref())
}
pub fn get_content_length(&self) -> Option<usize> {
self.get_header(http::header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn contains_header(&self, name: impl ToHeaderName) -> bool {
!self.lazy_handle.get_header_values(name).is_empty()
}
pub fn with_header(mut self, name: impl ToHeaderName, value: impl ToHeaderValue) -> Self {
self.append_header(name, value);
self
}
pub fn with_set_header(mut self, name: impl ToHeaderName, value: impl ToHeaderValue) -> Self {
self.set_header(name, value);
self
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
#[doc = include_str!("../../docs/snippets/panics-reqresp-header-utf8.md")]
pub fn get_header_str(&self, name: impl ToHeaderName) -> Option<&str> {
let name = name.into_borrowable();
if let Some(hdr) = self.get_header(name.as_ref()) {
Some(
std::str::from_utf8(hdr.as_bytes()).unwrap_or_else(|_| {
panic!("invalid UTF-8 HTTP header value for header: {name}")
}),
)
} else {
None
}
}
#[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn get_header_str_lossy(&self, name: impl ToHeaderName) -> Option<Cow<'_, str>> {
self.get_header(name)
.map(|hdr| String::from_utf8_lossy(hdr.as_bytes()))
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn get_header(&self, name: impl ToHeaderName) -> Option<&HeaderValue> {
self.lazy_handle.get_header_values(name).first()
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
#[doc = include_str!("../../docs/snippets/panics-reqresp-headers-utf8.md")]
pub fn get_header_all_str(&self, name: impl ToHeaderName) -> Vec<&str> {
let name = name.into_borrowable();
self.get_header_all(name.as_ref())
.map(|v| {
std::str::from_utf8(v.as_bytes())
.unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {name}"))
})
.collect()
}
pub fn get_headers(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue)> {
self.lazy_handle.iter()
}
#[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn get_header_all_str_lossy(&self, name: impl ToHeaderName) -> Vec<Cow<'_, str>> {
self.get_header_all(name)
.map(|hdr| String::from_utf8_lossy(hdr.as_bytes()))
.collect()
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn get_header_all(&self, name: impl ToHeaderName) -> impl Iterator<Item = &HeaderValue> {
self.lazy_handle.get_header_values(name).iter()
}
pub fn get_header_names_str(&self) -> Vec<&str> {
self.get_header_names().map(|n| n.as_str()).collect()
}
pub fn get_header_names(&self) -> impl Iterator<Item = &HeaderName> {
self.lazy_handle.get_header_names()
}
#[doc = include_str!("../../docs/snippets/header-name-value-argument.md")]
pub fn set_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
self.lazy_handle
.set_header(name.into_owned(), value.into_owned());
}
#[doc = include_str!("../../docs/snippets/header-name-value-argument.md")]
pub fn append_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
self.lazy_handle
.append_header_value(name.into_borrowable().as_ref(), value);
}
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn remove_header(&mut self, name: impl ToHeaderName) -> Option<HeaderValue> {
self.lazy_handle
.remove_header(name.into_borrowable().as_ref())
}
#[doc = include_str!("../../docs/snippets/removes-one-header.md")]
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
#[doc = include_str!("../../docs/snippets/panics-reqresp-remove-header-utf8.md")]
pub fn remove_header_str(&mut self, name: impl ToHeaderName) -> Option<String> {
let name = name.into_borrowable();
self.remove_header(name.as_ref()).map(|hdr| {
std::str::from_utf8(hdr.as_bytes())
.map(|s| s.to_owned())
.unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {name}"))
})
}
#[doc = include_str!("../../docs/snippets/utf8-replacement.md")]
#[doc = include_str!("../../docs/snippets/removes-one-header.md")]
#[doc = include_str!("../../docs/snippets/header-name-argument.md")]
pub fn remove_header_str_lossy(&mut self, name: impl ToHeaderName) -> Option<String> {
self.remove_header(name)
.map(|hdr| String::from_utf8_lossy(hdr.as_bytes()).into_owned())
}
pub fn with_status(mut self, status: impl ToStatusCode) -> Self {
self.set_status(status);
self
}
pub fn get_status(&self) -> StatusCode {
*self.lazy_handle.get_field::<StatusCode>()
}
#[doc = include_str!("../../docs/snippets/statuscode-argument.md")]
pub fn set_status(&mut self, status: impl ToStatusCode) {
self.lazy_handle
.put_field::<StatusCode>(status.to_status_code());
}
pub fn with_version(mut self, version: Version) -> Self {
self.set_version(version);
self
}
pub fn get_version(&self) -> Version {
*self.lazy_handle.get_field::<Version>()
}
pub fn set_version(&mut self, version: Version) {
self.lazy_handle.put_field::<Version>(version);
}
pub fn set_framing_headers_mode(&mut self, mode: FramingHeadersMode) {
self.metadata.framing_headers_mode = mode;
}
pub fn with_framing_headers_mode(mut self, mode: FramingHeadersMode) -> Self {
self.set_framing_headers_mode(mode);
self
}
#[doc(hidden)]
pub fn set_http_keepalive_mode(&mut self, mode: HttpKeepaliveMode) {
self.metadata.http_keepalive_mode = mode;
}
#[doc(hidden)]
pub fn with_http_keepalive_mode(mut self, mode: HttpKeepaliveMode) -> Self {
self.set_http_keepalive_mode(mode);
self
}
pub fn get_backend_name(&self) -> Option<&str> {
self.get_backend().map(|be| be.name())
}
pub fn get_backend(&self) -> Option<&Backend> {
self.metadata.backend.as_ref()
}
pub fn get_backend_addr(&self) -> Option<&SocketAddr> {
self.metadata.remote_addr.as_ref()
}
pub fn get_backend_request(&self) -> Option<&Request> {
self.sent_req.as_ref()
}
pub fn take_backend_request(&mut self) -> Option<Request> {
self.sent_req.take()
}
#[doc = include_str!("../../docs/snippets/explicit-send-fastly-main.md")]
pub fn send_to_client(self) {
let res = self.send_to_client_impl(false, true);
debug_assert!(res.is_none());
}
#[doc = include_str!("../../docs/snippets/explicit-send-fastly-main.md")]
pub fn stream_to_client(self) -> StreamingBody {
let res = self.send_to_client_impl(true, true);
res.expect("streaming body is present")
}
#[doc(hidden)]
pub fn send_to_client_impl(
mut self,
streaming: bool,
panic_on_multiple_send: bool,
) -> Option<StreamingBody> {
assert_single_downstream_response_is_sent(Some(self.get_status()), panic_on_multiple_send);
let revalidation = self.background_revalidation.take();
let (resp_handle, body_handle) = self.into_handles();
let ret = if streaming {
Some(resp_handle.stream_to_client(body_handle).into())
} else {
resp_handle.send_to_client(body_handle);
None
};
drop(revalidation);
ret
}
pub fn from_handles(resp_handle: ResponseHandle, body_handle: BodyHandle) -> Self {
Response {
body: Some(body_handle.into()),
..Self::from_response_handle(resp_handle)
}
}
pub(crate) fn from_response_handle(resp_handle: ResponseHandle) -> Self {
Response {
body: None,
metadata: FastlyResponseMetadata {
remote_addr: resp_handle.remote_addr().ok().flatten(),
..FastlyResponseMetadata::new()
},
lazy_handle: LazyHandle::from_handle(resp_handle)
.with_field_lazy::<StatusCode>()
.with_field_lazy::<Version>()
.finish(),
sent_req: None,
background_revalidation: None,
}
}
pub(crate) fn from_backend_resp(
resp_handle: ResponseHandle,
body_handle: BodyHandle,
backend: Backend,
sent_req: Request,
) -> Self {
let mut resp = Self::from_handles(resp_handle, body_handle);
resp.metadata.backend = Some(backend);
resp.sent_req = Some(sent_req);
resp
}
pub(crate) fn get_response_handle(&self) -> &ResponseHandle {
self.lazy_handle.get_handle()
}
pub(crate) fn into_response_handle(self) -> ResponseHandle {
self.lazy_handle.into_handle()
}
pub fn into_handles(mut self) -> (ResponseHandle, BodyHandle) {
let body_handle = if let Some(body) = self.try_take_body() {
body.into_handle()
} else {
BodyHandle::new()
};
let mut resp_handle = self.lazy_handle.into_handle();
resp_handle.set_framing_headers_mode(self.metadata.framing_headers_mode);
let _ = resp_handle.set_http_keepalive_mode(self.metadata.http_keepalive_mode);
(resp_handle, body_handle)
}
pub(crate) fn with_fastly_cache_headers(mut self, original_req: &Request) -> Self {
use super::header::fastly::*;
if let Some(hits) = self.metadata.cache_hits {
self.lazy_handle
.append_header_value_with_comma(&X_CACHE, VAL_HIT);
self.lazy_handle
.append_header_value_with_comma(&X_CACHE_HITS, hits.to_string());
} else {
self.lazy_handle
.append_header_value_with_comma(&X_CACHE, VAL_MISS);
self.lazy_handle
.append_header_value_with_comma(&X_CACHE_HITS, VAL_0);
}
let cdn_loop_visits_fastly = original_req
.get_header_all_str(&CDN_LOOP)
.into_iter()
.flat_map(|header| header.split(","))
.any(|cdn_info| cdn_info.trim().starts_with("Fastly"));
let keep_surrogate_headers = cdn_loop_visits_fastly
|| original_req.contains_header(FASTLY_FF)
|| original_req.contains_header(FASTLY_DEBUG);
if !keep_surrogate_headers {
self.remove_header(SURROGATE_KEY);
self.remove_header(SURROGATE_CONTROL);
}
self
}
fn was_cached(&self) -> bool {
matches!(
self.metadata.cache_storage_action,
Some(cache::HttpStorageAction::Insert | cache::HttpStorageAction::Update)
)
}
pub fn get_ttl(&self) -> Option<Duration> {
if self.was_cached() {
return None;
}
let options = self.metadata.cache_options.as_ref()?;
Some(options.max_age.saturating_sub(options.initial_age))
}
pub fn get_age(&self) -> Option<Duration> {
if self.was_cached() {
return None;
}
Some(self.metadata.cache_options.as_ref()?.initial_age)
}
pub fn get_stale_while_revalidate(&self) -> Option<Duration> {
if self.was_cached() {
return None;
}
Some(self.metadata.cache_options.as_ref()?.stale_while_revalidate)
}
}
impl From<Response> for http::Response<Body> {
fn from(mut val: Response) -> Self {
let mut resp = http::Response::new(val.body.take().unwrap_or_else(Body::new));
*resp.status_mut() = val.get_status();
*resp.version_mut() = val.get_version();
resp.extensions_mut().insert(val.metadata);
*resp.headers_mut() = val.lazy_handle.into();
resp
}
}
impl From<http::Response<Body>> for Response {
fn from(from: http::Response<Body>) -> Self {
let (mut parts, body) = from.into_parts();
let metadata: FastlyResponseMetadata = parts
.extensions
.remove()
.unwrap_or_else(FastlyResponseMetadata::new);
Response {
lazy_handle: LazyHandle::detached()
.with_field(parts.status)
.with_field(parts.version)
.with_headers(parts.headers)
.finish(),
body: Some(body),
metadata,
sent_req: None,
background_revalidation: None,
}
}
}
impl From<(ResponseHandle, BodyHandle)> for Response {
fn from(pair: (ResponseHandle, BodyHandle)) -> Self {
Self::from_handles(pair.0, pair.1)
}
}
#[macro_export]
macro_rules! panic_with_status {
() => {
$crate::panic_with_status!($crate::http::StatusCode::INTERNAL_SERVER_ERROR)
};
($status:expr) => {{
$crate::Response::new().with_status($status).send_to_client_impl(false, false);
panic!();
}};
($status:expr, $($arg:tt)*) => {{
$crate::Response::new().with_status($status).send_to_client_impl(false, false);
panic!($($arg)*);
}};
}
static SENT: AtomicBool = AtomicBool::new(false);
#[doc(hidden)]
pub(crate) fn assert_single_downstream_response_is_sent(
status: Option<http::StatusCode>,
panic_on_multiple_send: bool,
) {
use std::sync::atomic::Ordering;
if SENT.load(Ordering::SeqCst) && panic_on_multiple_send {
panic!("cannot send more than one client response per execution");
}
if let Some(status) = status {
if status != http::StatusCode::from_u16(103).unwrap() {
SENT.swap(true, Ordering::SeqCst);
}
}
}
#[doc(hidden)]
pub(crate) fn clear_downstream_response() {
use std::sync::atomic::Ordering;
SENT.store(false, Ordering::SeqCst);
}