pub(crate) mod handle;
pub(crate) use self::handle::handles_to_response;
use self::handle::ResponseHandle;
use super::body::{self, Body, StreamingBody};
use super::Request;
use crate::backend::Backend;
use crate::convert::{Borrowable, ToHeaderName, ToHeaderValue, ToStatusCode};
use crate::error::BufferSizeError;
use crate::handle::BodyHandle;
use crate::limits;
use http::header::{HeaderMap, HeaderName, HeaderValue};
use http::{StatusCode, Version};
use mime::Mime;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::io::BufRead;
#[derive(Debug)]
pub struct Response {
version: Version,
status: StatusCode,
headers: HeaderMap,
body: Option<Body>,
fastly_metadata: Option<FastlyResponseMetadata>,
}
impl Response {
pub fn new() -> Self {
Self {
version: Version::HTTP_11,
status: StatusCode::OK,
headers: HeaderMap::new(),
body: None,
fastly_metadata: None,
}
}
pub fn is_from_backend(&self) -> bool {
self.fastly_metadata.is_some()
}
pub fn clone_without_body(&self) -> Response {
Self {
version: self.version,
status: self.status,
headers: self.headers.clone(),
body: None,
fastly_metadata: self.fastly_metadata.clone(),
}
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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() {
for chunk in self.take_body().read_chunks(4096) {
let chunk = chunk.expect("can read body chunk");
new_resp.get_body_mut().write_bytes(&chunk);
self.get_body_mut().write_bytes(&chunk);
}
}
new_resp
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/body-argument.md")
)]
pub fn from_body(body: impl Into<Body>) -> Self {
Self::new().with_body(body)
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/body-argument.md")
)]
pub fn from_status(status: impl ToStatusCode) -> Self {
Self::new().with_status(status)
}
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()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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)
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/body-argument.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/discards-body.md")
)]
pub fn set_body(&mut self, body: impl Into<Body>) {
self.body = Some(body.into());
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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);
}
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/buffers-body-reqresp.md")
)]
pub fn into_body_bytes(mut self) -> Vec<u8> {
self.take_body_bytes()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/buffers-body-reqresp.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/panics-reqresp-intobody-utf8.md")
)]
pub fn into_body_str(mut self) -> String {
self.take_body_str()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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_str(mut self, body: &str) -> Self {
self.set_body_str(body);
self
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/discards-body.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/sets-text-plain.md")
)]
pub fn set_body_str(&mut self, body: &str) {
self.body = Some(Body::from(body));
self.set_content_type(mime::TEXT_PLAIN_UTF_8);
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/buffers-body-reqresp.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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()
}
}
pub fn read_body_lines(&mut self) -> std::io::Lines<&mut Body> {
self.get_body_mut().lines()
}
pub fn with_body_bytes(mut self, body: &[u8]) -> Self {
self.set_body_bytes(body);
self
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/discards-body.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/sets-app-octet-stream.md")
)]
pub fn set_body_bytes(&mut self, body: &[u8]) {
self.body = Some(Body::from(body));
self.set_content_type(mime::APPLICATION_OCTET_STREAM);
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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<'a>(
&'a mut self,
chunk_size: usize,
) -> impl Iterator<Item = Result<Vec<u8>, std::io::Error>> + 'a {
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)
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/discards-body.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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)
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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(())
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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())
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
pub fn contains_header(&self, name: impl ToHeaderName) -> bool {
self.headers.contains_key(name.into_borrowable().as_ref())
}
pub fn with_header(mut self, name: impl ToHeaderName, value: impl ToHeaderValue) -> Self {
self.set_header(name, value);
self
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/panics-reqresp-header-utf8.md")
)]
pub fn get_header_str<'a>(&self, name: impl ToHeaderName) -> Option<&str> {
let name = name.into_borrowable();
if let Some(hdr) = self.get_header(name.as_ref()) {
Some(
hdr.to_str().unwrap_or_else(|_| {
panic!("invalid UTF-8 HTTP header value for header: {}", name)
}),
)
} else {
None
}
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
pub fn get_header<'a>(&self, name: impl ToHeaderName) -> Option<&HeaderValue> {
self.headers.get(name.into_borrowable().as_ref())
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/panics-reqresp-headers-utf8.md")
)]
pub fn get_header_all_str<'a>(&self, name: impl ToHeaderName) -> Vec<&str> {
let name = name.into_borrowable();
self.get_header_all(name.as_ref())
.map(|v| {
v.to_str()
.unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {}", name))
})
.collect()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
pub fn get_header_all<'a>(
&'a self,
name: impl ToHeaderName,
) -> impl Iterator<Item = &'a HeaderValue> {
self.headers.get_all(name.into_borrowable().as_ref()).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.headers.keys()
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-value-argument.md")
)]
pub fn set_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
self.headers.insert(name.into_owned(), value.into_owned());
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-value-argument.md")
)]
pub fn append_header(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
self.headers
.append(name.into_borrowable().as_ref(), value.into_owned());
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
pub fn remove_header(&mut self, name: impl ToHeaderName) -> Option<HeaderValue> {
self.headers.remove(name.into_borrowable().as_ref())
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/removes-one-header.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/header-name-argument.md")
)]
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../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();
if let Some(hdr) = self.remove_header(name.as_ref()) {
Some(
hdr.to_str()
.map(|s| s.to_owned())
.unwrap_or_else(|_| panic!("non-UTF-8 HTTP header value for header: {}", name)),
)
} else {
None
}
}
pub fn with_status(mut self, status: impl ToStatusCode) -> Self {
self.set_status(status);
self
}
pub fn get_status(&self) -> StatusCode {
self.status
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../docs/snippets/statuscode-argument.md")
)]
pub fn set_status(&mut self, status: impl ToStatusCode) {
self.status = 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.version
}
pub fn set_version(&mut self, version: Version) {
self.version = version;
}
pub fn get_backend_name(&self) -> Option<&str> {
self.get_backend().map(|be| be.name())
}
pub fn get_backend(&self) -> Option<&Backend> {
self.fastly_metadata.as_ref().and_then(|md| md.backend())
}
pub fn get_backend_request(&self) -> Option<&Request> {
self.fastly_metadata.as_ref().and_then(|md| md.sent_req())
}
pub fn take_backend_request(&mut self) -> Option<Request> {
self.fastly_metadata
.as_mut()
.and_then(|md| md.take_sent_req())
}
pub(crate) fn set_fastly_metadata(&mut self, md: FastlyResponseMetadata) {
self.fastly_metadata = Some(md);
}
pub fn send_to_client(self) {
let res = self.send_to_client_impl(false, true);
debug_assert!(res.is_none());
}
#[deprecated(since = "0.6.0", note = "renamed to `Response::send_to_client()`")]
pub fn send_downstream(self) {
self.send_to_client()
}
pub fn stream_to_client(self) -> StreamingBody {
let res = self.send_to_client_impl(true, true);
res.expect("streaming body is present")
}
#[deprecated(since = "0.6.0", note = "renamed to `Response::stream_to_client()`")]
pub fn send_downstream_streaming(self) -> StreamingBody {
self.stream_to_client()
}
#[doc(hidden)]
pub fn send_to_client_impl(
self,
streaming: bool,
panic_on_multiple_send: bool,
) -> Option<StreamingBody> {
use std::sync::atomic::{AtomicBool, Ordering};
static SENT: AtomicBool = AtomicBool::new(false);
if panic_on_multiple_send && SENT.swap(true, Ordering::SeqCst) {
panic!("cannot send more than one client response per execution");
}
let (resp_handle, body_handle) = self.into_handles();
if streaming {
Some(resp_handle.stream_to_client(body_handle).into())
} else {
resp_handle.send_to_client(body_handle);
None
}
}
pub fn from_handles(
resp_handle: ResponseHandle,
body_handle: BodyHandle,
) -> Result<Self, BufferSizeError> {
let mut resp = Response::new()
.with_status(resp_handle.get_status())
.with_version(resp_handle.get_version());
let resp_limits = limits::RESPONSE_LIMITS.read().unwrap();
for name in resp_handle.get_header_names_impl(
limits::DEFAULT_MAX_HEADER_NAME_BYTES,
resp_limits.max_header_name_bytes,
) {
let name = name.expect("response header names too large");
for value in resp_handle.get_header_values_impl(
&name,
limits::DEFAULT_MAX_HEADER_VALUE_BYTES,
resp_limits.max_header_value_bytes,
) {
let value = value.expect("response header values too large");
resp.append_header(&name, value);
}
}
Ok(resp.with_body(body_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 = ResponseHandle::new();
resp_handle.set_status(self.status);
resp_handle.set_version(self.version);
for name in self.headers.keys() {
resp_handle.set_header_values(name, self.headers.get_all(name));
}
(resp_handle, body_handle)
}
}
#[derive(Debug)]
struct FastlyExts {
fastly_metadata: Option<FastlyResponseMetadata>,
}
impl Into<http::Response<Body>> for Response {
fn into(self) -> http::Response<Body> {
let mut resp = http::Response::new(self.body.unwrap_or_else(|| Body::new()));
resp.extensions_mut().insert(FastlyExts {
fastly_metadata: self.fastly_metadata,
});
*resp.headers_mut() = self.headers;
*resp.status_mut() = self.status;
*resp.version_mut() = self.version;
resp
}
}
impl From<http::Response<Body>> for Response {
fn from(from: http::Response<Body>) -> Self {
let (mut parts, body) = from.into_parts();
let fastly_metadata = parts
.extensions
.remove()
.and_then(|exts: FastlyExts| exts.fastly_metadata);
Response {
version: parts.version,
status: parts.status,
headers: parts.headers,
body: Some(body),
fastly_metadata,
}
}
}
#[derive(Debug)]
pub(crate) struct FastlyResponseMetadata {
backend: Backend,
sent_req: Option<Request>,
}
impl Clone for FastlyResponseMetadata {
fn clone(&self) -> Self {
Self {
backend: self.backend.clone(),
sent_req: self.sent_req.as_ref().map(Request::clone_without_body),
}
}
}
impl FastlyResponseMetadata {
pub fn new(backend: Backend, sent_req: Request) -> Self {
Self {
backend,
sent_req: Some(sent_req),
}
}
pub fn backend(&self) -> Option<&Backend> {
Some(&self.backend)
}
pub fn sent_req(&self) -> Option<&Request> {
self.sent_req.as_ref()
}
pub(crate) fn take_sent_req(&mut self) -> Option<Request> {
self.sent_req.take()
}
}
#[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)*);
}};
}